diff --git a/gnpy/example-data/extra_eqpt_config.json b/gnpy/example-data/extra_eqpt_config.json index 473084a47..dc5f33fc4 100644 --- a/gnpy/example-data/extra_eqpt_config.json +++ b/gnpy/example-data/extra_eqpt_config.json @@ -1,4 +1,29 @@ { + "Edfa": [ + { + "type_variety": "user_defined", + "type_def": "variable_gain", + "f_min": 192.0e12, + "f_max": 195.9e12, + "gain_flatmax": 25, + "gain_min": 15, + "p_max": 21, + "nf_min": 6, + "nf_max": 10, + "default_config_from_json": "user_edfa_config.json", + "out_voa_auto": false, + "allowed_for_design": true + }, { + "type_variety": "user_high_detail_model_example", + "type_def": "advanced_model", + "gain_flatmax": 25, + "gain_min": 15, + "p_max": 21, + "advanced_config_from_json": "std_medium_gain_advanced_config.json", + "out_voa_auto": false, + "allowed_for_design": false + } + ], "Transceiver": [ { "type_variety": "ZR400G", diff --git a/gnpy/tools/cli_examples.py b/gnpy/tools/cli_examples.py index d3f3c5c1a..92ff7edcc 100644 --- a/gnpy/tools/cli_examples.py +++ b/gnpy/tools/cli_examples.py @@ -22,14 +22,17 @@ from gnpy.core.parameters import SimParams from gnpy.core.utils import lin2db, pretty_summary_print, per_label_average, watt2dbm from gnpy.topology.request import (ResultElement, jsontocsv, BLOCKING_NOPATH) -from gnpy.tools.json_io import (load_equipment, load_network, load_json, load_requests, save_network, - requests_from_json, save_json, load_initial_spectrum, merge_equipment) +from gnpy.tools.json_io import (load_equipments_and_configs, load_network, load_json, load_requests, save_network, + requests_from_json, save_json, load_initial_spectrum, DEFAULT_EQPT_CONFIG) from gnpy.tools.plots import plot_baseline, plot_results from gnpy.tools.worker_utils import designed_network, transmission_simulation, planning _logger = logging.getLogger(__name__) _examples_dir = Path(__file__).parent.parent / 'example-data' +_default_config_files = ['example-data/std_medium_gain_advanced_config.json', + 'example-data/Juniper-BoosterHG.json', + 'parameters.DEFAULT_EDFA_CONFIG'] _help_footer = ''' This program is part of GNPy, https://github.com/TelecomInfraProject/oopt-gnpy @@ -49,12 +52,7 @@ def load_common_data(equipment_filename: Path, extra_equipment_filenames: List[P """Load common configuration from JSON files, merging additional equipment if provided.""" try: - extra_configs = {} - if extra_config_filenames: - extra_configs = {f.name: f for f in extra_config_filenames} - equipment = load_equipment(equipment_filename, extra_configs) - if extra_equipment_filenames: - merge_equipment(equipment, extra_equipment_filenames, extra_configs) + equipment = load_equipments_and_configs(equipment_filename, extra_equipment_filenames, extra_config_filenames) network = load_network(topology_filename, equipment) if save_raw_network_filename is not None: save_network(network, save_raw_network_filename) @@ -98,7 +96,7 @@ def _add_common_options(parser: argparse.ArgumentParser, network_default: Path): parser.add_argument('-v', '--verbose', action='count', default=0, help='Increase verbosity (can be specified several times)') parser.add_argument('-e', '--equipment', type=Path, metavar=_help_fname_json, - default=_examples_dir / 'eqpt_config.json', help='Equipment library') + default=DEFAULT_EQPT_CONFIG, help='Equipment library') parser.add_argument('--sim-params', type=Path, metavar=_help_fname_json, default=None, help='Path to the JSON containing simulation parameters (required for Raman). ' f'Example: {_examples_dir / "sim_params.json"}') @@ -110,15 +108,15 @@ def _add_common_options(parser: argparse.ArgumentParser, network_default: Path): help='Disable insertion of EDFAs after ROADMs and fibers ' 'as well as splitting of fibers by auto-design.') # Option for additional equipment files - parser.add_argument('-x', '--extra-equipment', nargs='+', type=Path, + parser.add_argument('--extra-equipment', nargs='+', type=Path, metavar=_help_fname_json, default=None, help='List of additional equipment files to complement the main equipment file.') # Option for additional config files - parser.add_argument('-xc', '--extra-config', nargs='+', type=Path, - metavar=_help_fname_json, default=[_examples_dir / "std_medium_gain_advanced_config.json", - _examples_dir / "Juniper-BoosterHG.json"], + parser.add_argument('--extra-config', nargs='+', type=Path, + metavar=_help_fname_json, help='List of additional config files as referenced in equipment files with ' - '"advanced_config_from_json" or "default_config_from_json"') + '"advanced_config_from_json" or "default_config_from_json".' + f'Existing configs:\n{_default_config_files}') def transmission_main_example(args=None): diff --git a/gnpy/tools/json_io.py b/gnpy/tools/json_io.py index 8af61b0e5..7026e0286 100644 --- a/gnpy/tools/json_io.py +++ b/gnpy/tools/json_io.py @@ -39,6 +39,10 @@ Model_openroadm_ila = namedtuple('Model_openroadm_ila', 'nf_coef') Model_hybrid = namedtuple('Model_hybrid', 'nf_ram gain_ram edfa_variety') Model_dual_stage = namedtuple('Model_dual_stage', 'preamp_variety booster_variety') +_examples_dir = Path(__file__).parent.parent / 'example-data' +DEFAULT_EXTRA_CONFIG = {"std_medium_gain_advanced_config.json": _examples_dir / "std_medium_gain_advanced_config.json", + "Juniper-BoosterHG.json": _examples_dir / "Juniper-BoosterHG.json"} +DEFAULT_EQPT_CONFIG = _examples_dir / "eqpt_config.json" class Model_openroadm_preamp: @@ -218,6 +222,7 @@ def from_json(cls, extra_configs, **kwargs): # default EDFA DGT and ripples are defined in parameters DEFAULT_EDFA_CONFIG. copy these values when # creating a new amplifier config = {k: v for k, v in DEFAULT_EDFA_CONFIG.items()} + config_filename = 'default' # default value to display in case of error type_variety = kwargs['type_variety'] type_def = kwargs.get('type_def', 'variable_gain') # default compatibility with older json eqpt files nf_def = None @@ -227,7 +232,8 @@ def from_json(cls, extra_configs, **kwargs): if type_def == 'fixed_gain': if 'default_config_from_json' in kwargs: # use user defined default instead of DEFAULT_EDFA_CONFIG - config = load_json(extra_configs[kwargs.pop('default_config_from_json')]) + config_filename = extra_configs[kwargs.pop('default_config_from_json')] + config = load_json(config_filename) try: nf0 = kwargs.pop('nf0') except KeyError as exc: # nf0 is expected for a fixed gain amp @@ -241,11 +247,13 @@ def from_json(cls, extra_configs, **kwargs): nf_def = Model_fg(nf0) elif type_def == 'advanced_model': # use the user file name define in library instead of default config - config = load_json(extra_configs[kwargs.pop('advanced_config_from_json')]) + config_filename = extra_configs[kwargs.pop('advanced_config_from_json')] + config = load_json(config_filename) elif type_def == 'variable_gain': if 'default_config_from_json' in kwargs: # use user defined default instead of DEFAULT_EDFA_CONFIG - config = load_json(extra_configs[kwargs.pop('default_config_from_json')]) + config_filename = extra_configs[kwargs.pop('default_config_from_json')] + config = load_json(config_filename) gain_min, gain_max = kwargs['gain_min'], kwargs['gain_flatmax'] try: # nf_min and nf_max are expected for a variable gain amp nf_min = kwargs.pop('nf_min') @@ -284,7 +292,7 @@ def from_json(cls, extra_configs, **kwargs): # raise an error if config does not contain f_min, f_max if 'f_min' not in config or 'f_max' not in config: - raise EquipmentConfigError('default Edfa config does not contain f_min and f_max values.' + raise EquipmentConfigError(f'Config file {config_filename} does not contain f_min and f_max values.' + ' Please correct file.') # use f_min, f_max from kwargs if 'f_min' in kwargs: @@ -386,7 +394,38 @@ def merge_equipment(equipment: dict, additional_filenames: List[Path], extra_con _logger.warning(msg) -def load_equipment(filename: Path, extra_configs: Dict[str, Path]) -> dict: +def load_equipments_and_configs(equipment_filename: Path, + extra_equipment_filenames: List[Path], + extra_config_filenames: List[Path]) -> dict: + """Loads equipment configurations and merge with additional equipment and configuration files. + + Args: + equipment_filename (Path): The path to the primary equipment configuration file. + extra_equipment_filenames (List[Path]): A list of paths to additional equipment configuration files to merge. + extra_config_filenames (List[Path]): A list of paths to additional configuration files to include. + + Returns: + dict: A dictionary containing the loaded equipment configurations. + + Notes: + If no equipment filename is provided, a default equipment configuration will be used. + Additional configurations from `extra_config_filenames` will override the default configurations. + If `extra_equipment_filenames` are provided, their contents will be merged into the loaded equipment. + """ + extra_configs = DEFAULT_EXTRA_CONFIG + if not equipment_filename: + equipment_filename = DEFAULT_EQPT_CONFIG + if extra_config_filenames: + extra_configs = {f.name: f for f in extra_config_filenames} + for k, v in DEFAULT_EXTRA_CONFIG.items(): + extra_configs[k] = v + equipment = load_equipment(equipment_filename, extra_configs) + if extra_equipment_filenames: + merge_equipment(equipment, extra_equipment_filenames, extra_configs) + return equipment + + +def load_equipment(filename: Path, extra_configs: Dict[str, Path] = DEFAULT_EXTRA_CONFIG) -> dict: """Load equipment, returns equipment dict """ json_data = load_json(filename) diff --git a/tests/test_invocation.py b/tests/test_invocation.py index e8383e7df..21a1ac21b 100644 --- a/tests/test_invocation.py +++ b/tests/test_invocation.py @@ -41,7 +41,8 @@ ['gnpy/example-data/multiband_example_network.json', 'Site_A', 'Site_D', '-e', 'gnpy/example-data/eqpt_config_multiband.json', '--spectrum', 'gnpy/example-data/multiband_spectrum.json', '--show-channels']), ('path_requests_run_extra_equipment', 'logs_path_requests_run_extra_equipment', path_requests_run, - ['gnpy/example-data/meshTopologyExampleV2.xls', 'gnpy/example-data/service_pluggable.json', '--extra-equipment', 'gnpy/example-data/extra_eqpt_config.json', 'tests/data/extra_eqpt_config.json']) + ['gnpy/example-data/meshTopologyExampleV2.xls', 'gnpy/example-data/service_pluggable.json', '--extra-equipment', 'gnpy/example-data/extra_eqpt_config.json', 'tests/data/extra_eqpt_config.json', + '--extra-config', 'tests/data/user_edfa_config.json']) )) def test_example_invocation(capfd, caplog, output, log, handler, args): """Make sure that our examples produce useful output"""