fix: PMD was not correctly read from excel or exported from json

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I1069b07dfb62bf94d4f591908c034df4e49ce22a
This commit is contained in:
EstherLerouzic
2025-02-28 19:28:44 +01:00
parent bce42331c4
commit b0ac41e2d5
11 changed files with 141 additions and 43 deletions

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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": {

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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