From d96e92387f7dbeb799fa64ac2191b64a32a81bca Mon Sep 17 00:00:00 2001 From: EstherLerouzic Date: Wed, 18 Sep 2024 15:52:49 +0200 Subject: [PATCH] Add frequency dependency on ROADM impairments Signed-off-by: EstherLerouzic Change-Id: Icb88bf9c42c09deb0064e3299b78b080462fef79 --- gnpy/core/elements.py | 48 +++++++++++++++++++++++++++----- gnpy/core/parameters.py | 36 +++++++++++------------- gnpy/topology/request.py | 6 ++-- tests/data/eqpt_config.json | 13 +++++++++ tests/test_roadm_restrictions.py | 38 ++++++++++++++++--------- 5 files changed, 100 insertions(+), 41 deletions(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index 5acdd1f0e..f0e7b9676 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -418,7 +418,7 @@ def propagate(self, spectral_info, degree, from_degree): # record input powers to compute the actual loss at the end of the process input_power_dbm = watt2dbm(spectral_info.signal + spectral_info.nli + spectral_info.ase) # apply min ROADM loss if it exists - roadm_maxloss_db = self.get_roadm_path(from_degree, degree).impairment.maxloss + roadm_maxloss_db = self.get_impairment('roadm-maxloss', spectral_info.frequency, from_degree, degree) spectral_info.apply_attenuation_db(roadm_maxloss_db) # records the total power after applying minimum loss net_input_power_dbm = watt2dbm(spectral_info.signal + spectral_info.nli + spectral_info.ase) @@ -433,7 +433,7 @@ def propagate(self, spectral_info, degree, from_degree): # the power out of the ROADM for the ref channel is the min value between target power and input power. ref_pch_in_dbm = self.ref_pch_in_dbm[from_degree] # Calculate the output power for the reference channel (only for visualization) - self.ref_pch_out_dbm = min(ref_pch_in_dbm - roadm_maxloss_db, ref_per_degree_pch) + self.ref_pch_out_dbm = min(ref_pch_in_dbm - max(roadm_maxloss_db), ref_per_degree_pch) # Definition of effective_loss: # Optical power of carriers are equalized by the ROADM, so that the experienced loss is not the same for @@ -465,11 +465,11 @@ def propagate(self, spectral_info, degree, from_degree): spectral_info.apply_attenuation_db(delta_power) # Update the PMD information - pmd_impairment = self.get_roadm_path(from_degree=from_degree, to_degree=degree).impairment.pmd + pmd_impairment = self.get_impairment('roadm-pmd', spectral_info.frequency, from_degree, degree) spectral_info.pmd = sqrt(spectral_info.pmd ** 2 + pmd_impairment ** 2) # Update the PMD information - pdl_impairment = self.get_roadm_path(from_degree=from_degree, to_degree=degree).impairment.pdl + pdl_impairment = self.get_impairment('roadm-pdl', spectral_info.frequency, from_degree, degree) spectral_info.pdl = sqrt(spectral_info.pdl ** 2 + pdl_impairment ** 2) # Update the per channel power with the result of propagation @@ -487,13 +487,19 @@ def set_roadm_paths(self, from_degree, to_degree, path_type, impairment_id=None) """ # initialize impairment with params.pmd, params.cd # if more detailed parameters are available for the Roadm, the use them instead - roadm_global_impairment = {'roadm-pmd': self.params.pmd, - 'roadm-pdl': self.params.pdl} + roadm_global_impairment = { + 'impairment': [{ + 'roadm-pmd': self.params.pmd, + 'roadm-pdl': self.params.pdl, + 'frequency-range': { + 'lower-frequency': None, + 'upper-frequency': None + }}]} if path_type in ['add', 'drop']: # without detailed imparments, we assume that add OSNR contribution is the same as drop contribution # add_drop_osnr_db = - 10log10(1/add_osnr + 1/drop_osnr) with add_osnr = drop_osnr # = add_osnr_db + 10log10(2) - roadm_global_impairment['roadm-osnr'] = self.params.add_drop_osnr + lin2db(2) + roadm_global_impairment['impairment'][0]['roadm-osnr'] = self.params.add_drop_osnr + lin2db(2) impairment = RoadmImpairment(roadm_global_impairment) if impairment_id is None: @@ -534,6 +540,34 @@ def get_path_type_per_id(self, impairment_id): return self.roadm_path_impairments[impairment_id].path_type return None + def get_impairment(self, impairment: str, frequency_array: array, from_degree: str, degree: str) \ + -> array: + """ + Retrieves the specified impairment values for the given frequency array. + + Parameters: + impairment (str): The type of impairment to retrieve (roadm-pmd, roamd-maxloss...). + frequency_array (array): The frequencies at which to check for impairments. + from_degree (str): The ingress degree for the roadm internal path. + degree (str): The egress degree for the roadm internal path. + + Returns: + array: An array of impairment values for the specified frequencies. + """ + result = [] + impairment_per_band = self.get_roadm_path(from_degree, degree).impairment.impairments + for frequency in frequency_array: + for item in impairment_per_band: + f_min = item['frequency-range']['lower-frequency'] + f_max = item['frequency-range']['upper-frequency'] + if (f_min is None or f_min <= frequency <= f_max): + item[impairment] = item.get(impairment, RoadmImpairment.default_values[impairment]) + if item[impairment] is not None: + result.append(item[impairment]) + break # Stop searching after the first match for this frequency + if result: + return array(result) + def __call__(self, spectral_info, degree, from_degree): self.propagate(spectral_info, degree=degree, from_degree=from_degree) return spectral_info diff --git a/gnpy/core/parameters.py b/gnpy/core/parameters.py index 7d0fb6084..3116ef2dd 100644 --- a/gnpy/core/parameters.py +++ b/gnpy/core/parameters.py @@ -137,7 +137,7 @@ def get_roadm_path_impairments(self, path_impairments_list): for path_impairment in path_impairments_list: index = path_impairment['roadm-path-impairments-id'] path_type = next(key for key in path_impairment if key in authorized_path_types.keys()) - impairment_dict = dict({'path-type': authorized_path_types[path_type]}, **path_impairment[path_type][0]) + impairment_dict = {'path-type': authorized_path_types[path_type], 'impairment': path_impairment[path_type]} roadm_path_impairments[index] = RoadmImpairment(impairment_dict) return roadm_path_impairments @@ -158,26 +158,24 @@ def __init__(self, from_degree, to_degree, path_type, impairment_id=None, impair class RoadmImpairment: """Generic definition of impairments for express, add and drop""" + default_values = { + 'roadm-pmd': None, + 'roadm-cd': None, + 'roadm-pdl': None, + 'roadm-inband-crosstalk': None, + 'roadm-maxloss': 0, + 'roadm-osnr': None, + 'roadm-pmax': None, + 'roadm-noise-figure': None, + 'minloss': None, + 'typloss': None, + 'pmin': None, + 'ptyp': None + } + def __init__(self, params): - """Records roadm internal paths and types""" self.path_type = params.get('path-type') - self.pmd = params.get('roadm-pmd') - self.cd = params.get('roadm-cd') - self.pdl = params.get('roadm-pdl') - self.inband_crosstalk = params.get('roadm-inband-crosstalk') - self.maxloss = params.get('roadm-maxloss', 0) - if params.get('frequency-range') is not None: - self.fmin = params.get('frequency-range')['lower-frequency'] - self.fmax = params.get('frequency-range')['upper-frequency'] - else: - self.fmin, self.fmax = None, None - self.osnr = params.get('roadm-osnr', None) - self.pmax = params.get('roadm-pmax', None) - self.nf = params.get('roadm-noise-figure', None) - self.minloss = params.get('minloss', None) - self.typloss = params.get('typloss', None) - self.pmin = params.get('pmin', None) - self.ptyp = params.get('ptyp', None) + self.impairments = params['impairment'] class FusedParams(Parameters): diff --git a/gnpy/topology/request.py b/gnpy/topology/request.py index 9478fa049..641e118e9 100644 --- a/gnpy/topology/request.py +++ b/gnpy/topology/request.py @@ -367,7 +367,8 @@ def propagate(path, req, equipment): for i, el in enumerate(path): if isinstance(el, Roadm): si = el(si, degree=path[i + 1].uid, from_degree=path[i - 1].uid) - roadm_osnr.append(el.get_roadm_path(from_degree=path[i - 1].uid, to_degree=path[i + 1].uid).impairment.osnr) + roadm_osnr.append(el.get_impairment('roadm-osnr', si.frequency, + from_degree=path[i - 1].uid, degree=path[i + 1].uid)) else: si = el(si) path[0].update_snr(si.tx_osnr) @@ -413,7 +414,8 @@ def propagate_and_optimize_mode(path, req, equipment): for i, el in enumerate(path): if isinstance(el, Roadm): spc_info = el(spc_info, degree=path[i + 1].uid, from_degree=path[i - 1].uid) - roadm_osnr.append(el.get_roadm_path(from_degree=path[i - 1].uid, to_degree=path[i + 1].uid).impairment.osnr) + roadm_osnr.append(el.get_impairment('roadm-osnr', spc_info.frequency, + from_degree=path[i - 1].uid, degree=path[i + 1].uid)) else: spc_info = el(spc_info) for this_mode in modes_to_explore: diff --git a/tests/data/eqpt_config.json b/tests/data/eqpt_config.json index a0f3e8dc3..73c99cd7a 100644 --- a/tests/data/eqpt_config.json +++ b/tests/data/eqpt_config.json @@ -128,6 +128,19 @@ "roadm-pmax": 2.5, "roadm-osnr": 41, "roadm-noise-figure": 23 + }, { + "frequency-range": { + "lower-frequency": 186.3e12, + "upper-frequency": 190.1e12 + }, + "roadm-pmd": 0, + "roadm-cd": 0, + "roadm-pdl": 0.5, + "roadm-inband-crosstalk": 0, + "roadm-maxloss": 5, + "roadm-pmax": 0, + "roadm-osnr": 35, + "roadm-noise-figure": 6 }] }, { "roadm-path-impairments-id": 2, diff --git a/tests/test_roadm_restrictions.py b/tests/test_roadm_restrictions.py index 439f92492..5d17f030c 100644 --- a/tests/test_roadm_restrictions.py +++ b/tests/test_roadm_restrictions.py @@ -596,11 +596,19 @@ def test_roadm_per_degree_impairments(type_variety, from_degree, to_degree, impa { "roadm-path-impairments-id": 1, "roadm-add-path": [{ + "frequency-range": { + "lower-frequency": 191.3e12, + "upper-frequency": 196.1e12 + }, "roadm-osnr": 41, }] }, { "roadm-path-impairments-id": 3, "roadm-add-path": [{ + "frequency-range": { + "lower-frequency": 191.3e12, + "upper-frequency": 196.1e12 + }, "roadm-inband-crosstalk": 0, "roadm-osnr": 20, "roadm-noise-figure": 23 @@ -657,14 +665,15 @@ def test_wrong_roadm_per_degree_impairments(from_degree, to_degree, impairment_i build_network(network, equipment, 0.0, 20.0) -@pytest.mark.parametrize('path_type, type_variety, expected_pmd, expected_pdl, expected_osnr', [ - ('express', 'default', 5.0e-12, 0.5, None), # roadm instance parameters pre-empts library - ('express', 'example_test', 5.0e-12, 0.5, None), - ('express', 'example_detailed_impairments', 0, 0, None), # detailed parameters pre-empts global instance ones - ('add', 'default', 5.0e-12, 0.5, None), - ('add', 'example_test', 5.0e-12, 0.5, None), - ('add', 'example_detailed_impairments', 0, 0, 41)]) -def test_impairment_initialization(path_type, type_variety, expected_pmd, expected_pdl, expected_osnr): +@pytest.mark.parametrize('path_type, type_variety, expected_pmd, expected_pdl, expected_osnr, freq', [ + ('express', 'default', 5.0e-12, 0.5, None, [191.3e12]), # roadm instance parameters pre-empts library + ('express', 'example_test', 5.0e-12, 0.5, None, [191.3e12]), + ('express', 'example_detailed_impairments', 0, 0, None, [191.3e12]), # detailed parameters pre-empts global ones + ('add', 'default', 5.0e-12, 0.5, None, [191.3e12]), + ('add', 'example_test', 5.0e-12, 0.5, None, [191.3e12]), + ('add', 'example_detailed_impairments', 0, 0, 41, [191.3e12]), + ('add', 'example_detailed_impairments', [0, 0], [0.5, 0], [35, 41], [188.5e12, 191.3e12])]) +def test_impairment_initialization(path_type, type_variety, expected_pmd, expected_pdl, expected_osnr, freq): """Check that impairments are correctly initialized, with this order: - use equipment roadm impairments if no impairment are set in the ROADM instance - use roadm global impairment if roadm global impairment are set @@ -687,13 +696,16 @@ def test_impairment_initialization(path_type, type_variety, expected_pmd, expect roadm = Roadm(**roadm_config) roadm.set_roadm_paths(from_degree='tata', to_degree='toto', path_type=path_type) assert roadm.get_roadm_path(from_degree='tata', to_degree='toto').path_type == path_type - assert roadm.get_roadm_path(from_degree='tata', to_degree='toto').impairment.pmd == expected_pmd - assert roadm.get_roadm_path(from_degree='tata', to_degree='toto').impairment.pdl == expected_pdl + assert_allclose(roadm.get_impairment('roadm-pmd', freq, from_degree='tata', degree='toto'), + expected_pmd, rtol=1e-12) + assert_allclose(roadm.get_impairment('roadm-pdl', freq, from_degree='tata', degree='toto'), + expected_pdl, rtol=1e-12) if path_type == 'add': # we assume for simplicity that add contribution is the same as drop contribution # add_drop_osnr_db = 10log10(1/add_osnr + 1/drop_osnr) if type_variety in ['default', 'example_test']: - assert roadm.get_roadm_path(from_degree='tata', - to_degree='toto').impairment.osnr == roadm.params.add_drop_osnr + lin2db(2) + assert_allclose(roadm.get_impairment('roadm-osnr', freq, from_degree='tata', degree='toto'), + roadm.params.add_drop_osnr + lin2db(2), rtol=1e-12) else: - assert roadm.get_roadm_path(from_degree='tata', to_degree='toto').impairment.osnr == expected_osnr + assert_allclose(roadm.get_impairment('roadm-osnr', freq, from_degree='tata', degree='toto'), + expected_osnr, rtol=1e-12)