diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index 010e182f..bfdd4788 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -891,18 +891,23 @@ class Fiber(_Node): :return Dict[str, Any]: JSON representation of the fiber. """ + params = { + # have to specify each because namedtupple cannot be updated :( + 'length': round(self.params.length * 1e-3, 6), + 'loss_coef': round(self.params.loss_coef * 1e3, 6), + 'length_units': 'km', + 'att_in': self.params.att_in, + 'con_in': self.params.con_in, + 'con_out': self.params.con_out + } + # Export pmd_coef only if it is not a default from library. + if self.params.pmd_coef_defined: + params['pmd_coef'] = self.params.pmd_coef + return {'uid': self.uid, 'type': type(self).__name__, 'type_variety': self.type_variety, - 'params': { - # have to specify each because namedtupple cannot be updated :( - 'length': round(self.params.length * 1e-3, 6), - 'loss_coef': round(self.params.loss_coef * 1e3, 6), - 'length_units': 'km', - 'att_in': self.params.att_in, - 'con_in': self.params.con_in, - 'con_out': self.params.con_out - }, + 'params': params, 'metadata': { 'location': self.metadata['location']._asdict() } @@ -1025,7 +1030,7 @@ class Fiber(_Node): else: wavelength = c / frequency dispersion = self.params.dispersion + self.params.dispersion_slope * \ - (wavelength - c / self.params.f_dispersion_ref) + (wavelength - c / self.params.f_dispersion_ref) # noqa E127 beta2 = -((c / frequency) ** 2 * dispersion) / (2 * pi * c) return beta2 @@ -1048,7 +1053,7 @@ class Fiber(_Node): dispersion_slope = self.params.dispersion_slope beta2 = self.beta2(frequency) beta3 = (dispersion_slope - (4 * pi * frequency ** 3 / c ** 2) * beta2) / ( - 2 * pi * frequency ** 2 / c) ** 2 + 2 * pi * frequency ** 2 / c) ** 2 # noqa E127 return beta3 def gamma(self, frequency=None): diff --git a/gnpy/core/parameters.py b/gnpy/core/parameters.py index c96772e8..0269208b 100644 --- a/gnpy/core/parameters.py +++ b/gnpy/core/parameters.py @@ -343,6 +343,7 @@ class FiberParams(Parameters): # Polarization Mode Dispersion self._pmd_coef = kwargs['pmd_coef'] # s/sqrt(m) + self._pmd_coef_defined = kwargs.get('pmd_coef_defined', kwargs['pmd_coef'] is True) # Loss Coefficient if isinstance(kwargs['loss_coef'], dict): @@ -428,6 +429,10 @@ class FiberParams(Parameters): def pmd_coef(self): return self._pmd_coef + @property + def pmd_coef_defined(self): + return self._pmd_coef_defined + @property def ref_wavelength(self): return self._ref_wavelength diff --git a/gnpy/core/utils.py b/gnpy/core/utils.py index 4e7a7c33..463a532a 100644 --- a/gnpy/core/utils.py +++ b/gnpy/core/utils.py @@ -329,6 +329,35 @@ def merge_amplifier_restrictions(dict1, dict2): return copy_dict1 +def use_pmd_coef(dict1: dict, dict2: dict): + """If Fiber dict1 is missing the pmd_coef value then use the one of dict2. + In addition records in "pmd_coef_defined" key the pmd_coef if is was defined in dict1. + + :param dict1: A dictionnary that contains "pmd_coef" key. + :type dict1: dict + :param dict2: Another dictionnary that contains "pmd_coef" key. + :type dict2: dict + + >>> dict1 = {'a': 1, 'pmd_coef': 1.5e-15} + >>> dict2 = {'a': 2, 'pmd_coef': 2e-15} + >>> use_pmd_coef(dict1, dict2) + >>> dict1 + {'a': 1, 'pmd_coef': 1.5e-15, 'pmd_coef_defined': True} + + >>> dict1 = {'a': 1} + >>> use_pmd_coef(dict1, dict2) + >>> dict1 + {'a': 1, 'pmd_coef_defined': False, 'pmd_coef': 2e-15} + """ + if 'pmd_coef' in dict1 and not dict1['pmd_coef'] \ + or ('pmd_coef' not in dict1 and 'pmd_coef' in dict2): + dict1['pmd_coef_defined'] = False + dict1['pmd_coef'] = dict2['pmd_coef'] + elif 'pmd_coef' in dict1 and dict1['pmd_coef']: + dict1['pmd_coef_defined'] = True + # all other case do not need any change + + def silent_remove(this_list, elem): """Remove matching elements from a list without raising ValueError @@ -558,3 +587,23 @@ def transform_data(data: str) -> Union[List[int], None]: if isinstance(data, str): return [int(x) for x in data.split(' | ')] return None + + +def convert_pmd_lineic(pmd: Union[float, None], length: float, length_unit: str) -> Union[float, None]: + """Convert PMD value of the span in ps into pmd_lineic in s/sqrt(km) + + :param pmd: value in ps + :type pmd: Union[float, None] + :param length: value in length_unit + :type length: float + :param length_unit: 'km' or 'm' + :type length_unit: str + :return: lineic PMD s/sqrt(m) + :rtype: Union[float, None] + + >>> convert_pmd_lineic(10, 0.001, 'km') + 1e-11 + """ + if pmd: + return pmd * 1e-12 / sqrt(convert_length(length, length_unit)) + return None diff --git a/gnpy/example-data/edfa_example_network.json b/gnpy/example-data/edfa_example_network.json index 861be903..1a8ed91a 100644 --- a/gnpy/example-data/edfa_example_network.json +++ b/gnpy/example-data/edfa_example_network.json @@ -23,7 +23,8 @@ "length_units": "km", "att_in": 0, "con_in": 0.5, - "con_out": 0.5 + "con_out": 0.5, + "pmd_coef": 3.0e-15 }, "metadata": { "location": { diff --git a/gnpy/example-data/meshTopologyExampleV2.xls b/gnpy/example-data/meshTopologyExampleV2.xls index 0704900e..93ca00dc 100644 Binary files a/gnpy/example-data/meshTopologyExampleV2.xls and b/gnpy/example-data/meshTopologyExampleV2.xls differ diff --git a/gnpy/tools/convert.py b/gnpy/tools/convert.py index 0e6ed51e..d8df962f 100755 --- a/gnpy/tools/convert.py +++ b/gnpy/tools/convert.py @@ -33,7 +33,7 @@ from xlrd.sheet import Sheet from xlrd.biffh import XLRDError from networkx import DiGraph -from gnpy.core.utils import silent_remove, transform_data +from gnpy.core.utils import silent_remove, transform_data, convert_pmd_lineic from gnpy.core.exceptions import NetworkTopologyError from gnpy.core.elements import Edfa, Fused, Fiber @@ -170,7 +170,7 @@ class Link: 'east_lineic': 0.2, 'east_con_in': None, 'east_con_out': None, - 'east_pmd': 0.1, + 'east_pmd': None, 'east_cable': '' } @@ -636,6 +636,62 @@ def create_west_eqpt_element(node: Node, nodes_by_city: Dict[str, Node]) -> dict return eqpt +def create_east_fiber_element(fiber: Node, nodes_by_city: Dict[str, Node]) -> Dict: + """Create fibers json elements for the east direction. + + :param fiber: The Node object representing the equipment. + :type fiber: Node + :param nodes_by_city: A dictionary mapping city names to Node objects. + :type nodes_by_city: Dict[str, Node] + :return: A dictionary representing the west equipment element in JSON format. + :rtype: Dict + """ + fiber_dict = { + 'uid': f'fiber ({fiber.from_city} \u2192 {fiber.to_city})-{fiber.east_cable}', + 'metadata': {'location': midpoint(nodes_by_city[fiber.from_city], + nodes_by_city[fiber.to_city])}, + 'type': 'Fiber', + 'type_variety': fiber.east_fiber, + 'params': { + 'length': round(fiber.east_distance, 3), + 'length_units': fiber.distance_units, + 'loss_coef': fiber.east_lineic, + 'con_in': fiber.east_con_in, + 'con_out': fiber.east_con_out + } + } + if fiber.east_pmd: + fiber_dict['params']['pmd_coef'] = convert_pmd_lineic(fiber.east_pmd, fiber.east_distance, fiber.distance_units) + return fiber_dict + + +def create_west_fiber_element(fiber: Node, nodes_by_city: Dict[str, Node]) -> Dict: + """Create fibers json elements for the west direction. + + :param fiber: The Node object representing the equipment. + :type fiber: Node + :param nodes_by_city: A dictionary mapping city names to Node objects. + :type nodes_by_city: Dict[str, Node] + :return: A dictionary representing the west equipment element in JSON format. + :rtype: Dict + """ + fiber_dict = { + 'uid': f'fiber ({fiber.to_city} \u2192 {fiber.from_city})-{fiber.west_cable}', + 'metadata': {'location': midpoint(nodes_by_city[fiber.from_city], + nodes_by_city[fiber.to_city])}, + 'type': 'Fiber', + 'type_variety': fiber.west_fiber, + 'params': {'length': round(fiber.west_distance, 3), + 'length_units': fiber.distance_units, + 'loss_coef': fiber.west_lineic, + 'con_in': fiber.west_con_in, + 'con_out': fiber.west_con_out} + } + if fiber.west_pmd: + fiber_dict['params']['pmd_coef'] = convert_pmd_lineic(fiber.west_pmd, fiber.west_distance, fiber.distance_units) + return fiber_dict + + def xls_to_json_data(input_filename: Path, filter_region: List[str] = None) -> dict: """Read the Excel sheets and produce the JSON dict in GNPy format (legacy). @@ -698,29 +754,8 @@ def xls_to_json_data(input_filename: Path, filter_region: List[str] = None) -> d 'longitude': x.longitude}}, 'type': 'Fused'} for x in nodes_by_city.values() if x.node_type.lower() == 'fused'] - + [{'uid': f'fiber ({x.from_city} \u2192 {x.to_city})-{x.east_cable}', - 'metadata': {'location': midpoint(nodes_by_city[x.from_city], - nodes_by_city[x.to_city])}, - 'type': 'Fiber', - 'type_variety': x.east_fiber, - 'params': {'length': round(x.east_distance, 3), - 'length_units': x.distance_units, - 'loss_coef': x.east_lineic, - 'con_in': x.east_con_in, - 'con_out': x.east_con_out} - } - for x in links] - + [{'uid': f'fiber ({x.to_city} \u2192 {x.from_city})-{x.west_cable}', - 'metadata': {'location': midpoint(nodes_by_city[x.from_city], - nodes_by_city[x.to_city])}, - 'type': 'Fiber', - 'type_variety': x.west_fiber, - 'params': {'length': round(x.west_distance, 3), - 'length_units': x.distance_units, - 'loss_coef': x.west_lineic, - 'con_in': x.west_con_in, - 'con_out': x.west_con_out} - } for x in links] + + [create_east_fiber_element(x, nodes_by_city) for x in links] + + [create_west_fiber_element(x, nodes_by_city) for x in links] + [{'uid': f'west edfa in {x.city}', 'metadata': {'location': {'city': x.city, 'region': x.region, diff --git a/gnpy/tools/json_io.py b/gnpy/tools/json_io.py index f36e71cf..25d96439 100644 --- a/gnpy/tools/json_io.py +++ b/gnpy/tools/json_io.py @@ -23,7 +23,7 @@ from gnpy.core.equipment import trx_mode_params, find_type_variety from gnpy.core.exceptions import ConfigurationError, EquipmentConfigError, NetworkTopologyError, ServiceError from gnpy.core.science_utils import estimate_nf_model from gnpy.core.info import Carrier -from gnpy.core.utils import automatic_nch, automatic_fmax, merge_amplifier_restrictions, dbm2watt +from gnpy.core.utils import automatic_nch, automatic_fmax, merge_amplifier_restrictions, dbm2watt, use_pmd_coef from gnpy.core.parameters import DEFAULT_RAMAN_COEFFICIENT, EdfaParams, MultiBandParams, DEFAULT_EDFA_CONFIG from gnpy.topology.request import PathRequest, Disjunction, compute_spectrum_slot_vs_bandwidth, ResultElement from gnpy.topology.spectrum_assignment import mvalue_to_slots @@ -687,6 +687,9 @@ def network_from_json(json_data: dict, equipment: dict) -> DiGraph: if not extra_params: msg = f'ROADM {el_config["uid"]}: invalid equalization settings' raise ConfigurationError(msg) + # use temp pmd_coef if it exists else use the default one from library and keep this knowledge in + # pmd_coef_defined + use_pmd_coef(temp, extra_params) temp = merge_amplifier_restrictions(temp, extra_params) el_config['params'] = temp el_config['type_variety'] = variety diff --git a/tests/invocation/spectrum1_transmission_main_example b/tests/invocation/spectrum1_transmission_main_example index d38d3fe8..33ddb28d 100644 --- a/tests/invocation/spectrum1_transmission_main_example +++ b/tests/invocation/spectrum1_transmission_main_example @@ -101,7 +101,7 @@ Transceiver trx Lorient_KMA OSNR ASE (0.1nm, dB): 23.89 OSNR ASE (signal bw, dB): 19.81 CD (ps/nm): 2171.00 - PMD (ps): 0.46 + PMD (ps): 3.03 PDL (dB): 0.00 Latency (ms): 0.64 Actual pch out (dBm): 0.00 diff --git a/tests/invocation/spectrum2_transmission_main_example b/tests/invocation/spectrum2_transmission_main_example index 727764d3..d21bd8da 100644 --- a/tests/invocation/spectrum2_transmission_main_example +++ b/tests/invocation/spectrum2_transmission_main_example @@ -101,7 +101,7 @@ Transceiver trx Lorient_KMA OSNR ASE (0.1nm, dB): mode_1: 23.91, mode_2: 23.87 OSNR ASE (signal bw, dB): mode_1: 19.83, mode_2: 16.78 CD (ps/nm): 2171.00 - PMD (ps): 0.46 + PMD (ps): 3.03 PDL (dB): 0.00 Latency (ms): 0.64 Actual pch out (dBm): mode_1: 0.00, mode_2: 0.00 diff --git a/tests/invocation/transmission_main_example b/tests/invocation/transmission_main_example index a6dc6e2c..fc4c542e 100644 --- a/tests/invocation/transmission_main_example +++ b/tests/invocation/transmission_main_example @@ -52,7 +52,7 @@ Transceiver Site_B OSNR ASE (0.1nm, dB): 33.30 OSNR ASE (signal bw, dB): 29.21 CD (ps/nm): 1336.00 - PMD (ps): 0.36 + PMD (ps): 0.85 PDL (dB): 0.00 Latency (ms): 0.39 Actual pch out (dBm): 0.00 diff --git a/tests/test_logger.py b/tests/test_logger.py index 7e9f9064..3aff1f42 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -377,8 +377,8 @@ def wrong_element(): "expected_msg": "Config error in fiber (ILA2 → ILA1): " + "Fiber configurations json must include \'length_units\'. Configuration: " + "{\'length\': 100.0, \'loss_coef\': 0.2, \'att_in\': 0, \'con_in\': 0, \'con_out\': 0, " - + "\'type_variety\': \'SSMF\', \'dispersion\': 1.67e-05, \'effective_area\': 8.3e-11, " - + "\'pmd_coef\': 1.265e-15}" + + "\'pmd_coef_defined': False, \'pmd_coef\': 1.265e-15, " + + "\'type_variety\': \'SSMF\', \'dispersion\': 1.67e-05, \'effective_area\': 8.3e-11}" }) return data