From 48e3f96967e5fa5273fca68b8ab43ca02e657e66 Mon Sep 17 00:00:00 2001 From: EstherLerouzic Date: Mon, 24 Oct 2022 21:41:01 +0200 Subject: [PATCH] add equalization per constant ratio power/slot_width Constant power per slot_width uses the slot width instead of baud rate compared to PSD. This is the equalization used in OpenROADM add tests for constant power per slot width equalization Signed-off-by: EstherLerouzic Change-Id: Ie350e4c15cb6b54c15e418556fe33e72486cb134 --- docs/json.rst | 115 ++++++++++++++++++++++++++---------- gnpy/core/elements.py | 34 +++++++---- gnpy/core/info.py | 9 ++- gnpy/core/network.py | 19 +++--- gnpy/core/parameters.py | 4 +- gnpy/tools/json_io.py | 23 ++++---- gnpy/topology/request.py | 3 +- tests/test_amplifier.py | 3 +- tests/test_equalization.py | 78 +++++++++++++++--------- tests/test_propagation.py | 2 +- tests/test_science_utils.py | 6 +- 11 files changed, 199 insertions(+), 97 deletions(-) diff --git a/docs/json.rst b/docs/json.rst index a1b29e26..9b2dd59f 100644 --- a/docs/json.rst +++ b/docs/json.rst @@ -176,36 +176,36 @@ ROADM The user can only modify the value of existing parameters: -+-----------------------------+-----------+----------------------------------------------------+ -| field | type | description | -+=============================+===========+====================================================+ -| ``target_pch_out_db`` | (number) | Default :ref:`equalization strategy` | -| or | | for this ROADM type. | -| ``target_psd_out_mWperGHz`` | | | -| (mutually exclusive) | | Auto-design sets the ROADM egress channel | -| | | power. This reflects typical control loop | -| | | algorithms that adjust ROADM losses to | -| | | equalize channels (e.g., coming from | -| | | different ingress direction or add ports). | -| | | | -| | | These values are used as defaults when no | -| | | overrides are set per each ``Roadm`` | -| | | element in the network topology. | -+-----------------------------+-----------+----------------------------------------------------+ -| ``add_drop_osnr`` | (number) | OSNR contribution from the add/drop ports | -+-----------------------------+-----------+----------------------------------------------------+ -| ``pmd`` | (number) | Polarization mode dispersion (PMD). (s) | -+-----------------------------+-----------+----------------------------------------------------+ -| ``restrictions`` | (dict of | If non-empty, keys ``preamp_variety_list`` | -| | strings) | and ``booster_variety_list`` represent | -| | | list of ``type_variety`` amplifiers which | -| | | are allowed for auto-design within ROADM's | -| | | line degrees. | -| | | | -| | | If no booster should be placed on a degree, | -| | | insert a ``Fused`` node on the degree | -| | | output. | -+-----------------------------+-----------+----------------------------------------------------+ ++-------------------------------+-----------+----------------------------------------------------+ +| field | type | description | ++===============================+===========+====================================================+ +| ``target_pch_out_db`` | (number) | Default :ref:`equalization strategy` | +| or | | for this ROADM type. | +| ``target_psd_out_mWperGHz`` | | | +| or | | Auto-design sets the ROADM egress channel | +| ``target_out_mWperSlotWidth`` | | power. This reflects typical control loop | +| (mutually exclusive) | | algorithms that adjust ROADM losses to | +| | | equalize channels (e.g., coming from | +| | | different ingress direction or add ports). | +| | | | +| | | These values are used as defaults when no | +| | | overrides are set per each ``Roadm`` | +| | | element in the network topology. | ++-------------------------------+-----------+----------------------------------------------------+ +| ``add_drop_osnr`` | (number) | OSNR contribution from the add/drop ports | ++-------------------------------+-----------+----------------------------------------------------+ +| ``pmd`` | (number) | Polarization mode dispersion (PMD). (s) | ++-------------------------------+-----------+----------------------------------------------------+ +| ``restrictions`` | (dict of | If non-empty, keys ``preamp_variety_list`` | +| | strings) | and ``booster_variety_list`` represent | +| | | list of ``type_variety`` amplifiers which | +| | | are allowed for auto-design within ROADM's | +| | | line degrees. | +| | | | +| | | If no booster should be placed on a degree, | +| | | insert a ``Fused`` node on the degree | +| | | output. | ++-------------------------------+-----------+----------------------------------------------------+ Global parameters ----------------- @@ -564,6 +564,57 @@ There is no overlap of the occupation and both share the same boundary. Equalization choices ~~~~~~~~~~~~~~~~~~~~ -ROADMs typically equalize the optical power across multiple channels using one of the available equalization strategies — either targeting a specific output power, or a specific power spectral density (PSD). -Both of these strategies can be adjusted by a per-channel offset. +ROADMs typically equalize the optical power across multiple channels using one of the available equalization strategies — either targeting a specific output power, or a specific power spectral density (PSD), or a spectfic power spectral density using slot_width as spectrum width reference (PSW). +All of these strategies can be adjusted by a per-channel power offset. The equalization strategy can be defined globally per a ROADM model, or per each ROADM instance in the topology, and within a ROADM also on a per-degree basis. + +Let's consider some example for the equalization. Suppose that the types of signal to be propagated are the following: + +.. code-block:: json + + { + "baud_rate": 32e9, + "f_min":191.3e12, + "f_max":192.3e12, + "spacing": 50e9, + "label": 1 + }, + { + "baud_rate": 64e9, + "f_min":193.3e12, + "f_max":194.3e12, + "spacing": 75e9, + "label": 2 + } + + +with the PSD equalization in a ROADM: + +.. code-block:: json + + { + "uid": "roadm A", + "type": "Roadm", + "params": { + "target_psd_out_mWperGHz": 3.125e-4, + } + }, + + +This means that power out of the ROADM will be computed as 3.125e-4 * 32 = 0.01 mW ie -20 dBm for label 1 types of carriers +and 3.125e4 * 64 = 0.02 mW ie -16.99 dBm for label2 channels. So a ratio of ~ 3 dB between target powers for these carriers. + +With the PSW equalization: + +.. code-block:: json + + { + "uid": "roadm A", + "type": "Roadm", + "params": { + "target_out_mWperSlotWidth": 2.0e-4, + } + }, + +the power out of the ROADM will be computed as 2.0e-4 * 50 = 0.01 mW ie -20 dBm for label 1 types of carriers +and 2.0e4 * 75 = 0.015 mW ie -18.24 dBm for label2 channels. So a ratio of ~ 1.76 dB between target powers for these carriers. diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index 22609c28..7381a009 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -21,16 +21,18 @@ instance as a result. """ from numpy import abs, array, errstate, ones, interp, mean, pi, polyfit, polyval, sum, sqrt, log10, exp, asarray, full,\ - squeeze, zeros, append, flip, outer, minimum + squeeze, zeros, append, flip, outer, ndarray from scipy.constants import h, c from scipy.interpolate import interp1d from collections import namedtuple +from typing import Union + from gnpy.core.utils import lin2db, db2lin, arrange_frequencies, snr_sum, per_label_average, pretty_summary_print, \ - watt2dbm, psd2powerdbm, power_dbm_to_psd_mw_ghz + watt2dbm, psd2powerdbm from gnpy.core.parameters import RoadmParams, FusedParams, FiberParams, PumpParams, EdfaParams, EdfaOperational from gnpy.core.science_utils import NliSolver, RamanSolver -from gnpy.core.info import SpectralInformation +from gnpy.core.info import SpectralInformation, ReferenceCarrier from gnpy.core.exceptions import NetworkTopologyError, SpectrumError @@ -235,9 +237,10 @@ class Roadm(_Node): # target for equalization for the ROADM only one must be not None self.target_pch_out_dbm = self.params.target_pch_out_db self.target_psd_out_mWperGHz = self.params.target_psd_out_mWperGHz - # per degree equalization that overrides the ROADM equalization if not None + self.target_out_mWperSlotWidth = self.params.target_out_mWperSlotWidth self.per_degree_pch_out_dbm = self.params.per_degree_pch_out_db self.per_degree_pch_psd = self.params.per_degree_pch_psd + self.per_degree_pch_psw = self.params.per_degree_pch_psw @property def to_json(self): @@ -245,6 +248,8 @@ class Roadm(_Node): equalisation, value = 'target_pch_out_db', self.target_pch_out_dbm elif self.target_psd_out_mWperGHz is not None: equalisation, value = 'target_psd_out_mWperGHz', self.target_psd_out_mWperGHz + elif self.target_out_mWperSlotWidth is not None: + equalisation, value = 'target_out_mWperSlotWidth', self.target_out_mWperSlotWidth else: assert False, 'There must be one default equalization defined in ROADM' to_json = { @@ -263,6 +268,8 @@ class Roadm(_Node): to_json['params']['per_degree_pch_out_db'] = self.per_degree_pch_out_dbm if self.per_degree_pch_psd: to_json['params']['per_degree_psd_out_mWperGHz'] = self.per_degree_pch_psd + if self.per_degree_pch_psw: + to_json['params']['per_degree_psd_out_mWperSlotWidth'] = self.per_degree_pch_psw return to_json def __repr__(self): @@ -278,7 +285,8 @@ class Roadm(_Node): f' reference pch out (dBm): {self.ref_pch_out_dbm:.2f}', f' actual pch out (dBm): {total_pch}']) - def get_roadm_target_power(self, reference_baudrate=None, spectral_info=None): + def get_roadm_target_power(self, ref_carrier: ReferenceCarrier = None, + spectral_info: SpectralInformation = None) -> Union[int, float, ndarray]: """Computes the power in dBm for a reference carrier or for a spectral information. power is computed based on equalization target. if spectral_info baud_rate is baud_rate = [32e9, 42e9, 64e9, 42e9, 32e9], and @@ -294,22 +302,28 @@ class Roadm(_Node): return full(len(spectral_info.channel_number), self.target_pch_out_dbm) if self.target_psd_out_mWperGHz is not None: return psd2powerdbm(self.target_psd_out_mWperGHz, spectral_info.baud_rate) + if self.target_out_mWperSlotWidth is not None: + return psd2powerdbm(self.target_out_mWperSlotWidth, spectral_info.slot_width) else: if self.target_pch_out_dbm is not None: return self.target_pch_out_dbm if self.target_psd_out_mWperGHz is not None: - return psd2powerdbm(self.target_psd_out_mWperGHz, reference_baudrate) + return psd2powerdbm(self.target_psd_out_mWperGHz, ref_carrier.baud_rate) + if self.target_out_mWperSlotWidth is not None: + return psd2powerdbm(self.target_out_mWperSlotWidth, ref_carrier.slot_width) return None - def get_per_degree_ref_power(self, degree, reference_baudrate): + def get_per_degree_ref_power(self, degree, ref_carrier): """Get the target power in dBm out of ROADM degree for the reference bandwidth If no equalization is defined on this degree use the ROADM level one. """ if degree in self.per_degree_pch_out_dbm: return self.per_degree_pch_out_dbm[degree] elif degree in self.per_degree_pch_psd: - return psd2powerdbm(self.per_degree_pch_psd[degree], reference_baudrate) - return self.get_roadm_target_power(reference_baudrate=reference_baudrate) + return psd2powerdbm(self.per_degree_pch_psd[degree], ref_carrier.baud_rate) + elif degree in self.per_degree_pch_psw: + return psd2powerdbm(self.per_degree_pch_psw[degree], ref_carrier.slot_width) + return self.get_roadm_target_power(ref_carrier) def get_per_degree_power(self, degree, spectral_info): """Get the target power in dBm out of ROADM degree for the spectral information @@ -332,7 +346,7 @@ class Roadm(_Node): # TODO maybe add a minimum loss for the ROADM # find the target power for the reference carrier - ref_per_degree_pch = self.get_per_degree_ref_power(degree, spectral_info.pref.ref_carrier.baud_rate) + ref_per_degree_pch = self.get_per_degree_ref_power(degree, spectral_info.pref.ref_carrier) # find the target powers for each signal carrier per_degree_pch = self.get_per_degree_power(degree, spectral_info=spectral_info) diff --git a/gnpy/core/info.py b/gnpy/core/info.py index 5a4e3ad1..1f56801a 100644 --- a/gnpy/core/info.py +++ b/gnpy/core/info.py @@ -371,8 +371,15 @@ class ReferenceCarrier: experienced by p_span_i in Roadm element. Baud rate is required to find the target power in constant PSD: power = PSD_target * baud_rate. - For example, if target PSD is 3.125e4mW/GHz and reference carrier type a 32 GBaud channel then + For example, if target PSD is 3.125e4mW/GHz and reference carrier type a 32 GBaud channel then output power should be -20 dBm and for a 64 GBaud channel power target would need 3 dB more: -17 dBm. + + Slot width is required to find the target power in constant PSW (constant power per slot width equalization): + power = PSW_target * slot_width. + For example, if target PSW is 2e4mW/GHz and reference carrier type a 32 GBaud channel in a 50GHz slot width then + output power should be -20 dBm and for a 64 GBaud channel in a 75 GHz slot width, power target would be -18.24 dBm. + Other attributes (like slot_width or roll-off) may be added there for future equalization purpose. """ baud_rate: float + slot_width: float diff --git a/gnpy/core/network.py b/gnpy/core/network.py index 42f55e12..3b3b36b7 100644 --- a/gnpy/core/network.py +++ b/gnpy/core/network.py @@ -12,6 +12,7 @@ from operator import attrgetter from gnpy.core import ansi_escapes, elements from gnpy.core.exceptions import ConfigurationError, NetworkTopologyError from gnpy.core.utils import round2float, convert_length +from gnpy.core.info import ReferenceCarrier from collections import namedtuple @@ -237,7 +238,8 @@ def set_egress_amplifier(network, this_node, equipment, pref_ch_db, pref_total_d """ this node can be a transceiver or a ROADM (same function called in both cases) """ power_mode = equipment['Span']['default'].power_mode - reference_baudrate = equipment['SI']['default'].baud_rate + ref_carrier = ReferenceCarrier(baud_rate=equipment['SI']['default'].baud_rate, + slot_width=equipment['SI']['default'].spacing) next_oms = (n for n in network.successors(this_node) if not isinstance(n, elements.Transceiver)) for oms in next_oms: # go through all the OMS departing from the ROADM @@ -247,8 +249,7 @@ def set_egress_amplifier(network, this_node, equipment, pref_ch_db, pref_total_d this_node_out_power = 0.0 # default value if this_node is a transceiver if isinstance(this_node, elements.Roadm): # get target power out from ROADM for the reference carrier based on equalization settings - this_node_out_power = this_node.get_per_degree_ref_power(degree=node.uid, - reference_baudrate=reference_baudrate) + this_node_out_power = this_node.get_per_degree_ref_power(degree=node.uid, ref_carrier=ref_carrier) # use the target power on this degree prev_dp = this_node_out_power - pref_ch_db dp = prev_dp @@ -333,22 +334,24 @@ def set_egress_amplifier(network, this_node, equipment, pref_ch_db, pref_total_d def set_roadm_per_degree_targets(roadm, network): """Set target powers/PSD on all degrees - This is needed to populate per_degree_pch_out_dbm or per_degree_pch_psd dicts when they are - not initialized by users. + This is needed to populate per_degree_pch_out_dbm or per_degree_pch_psd or per_degree_pch_psw dicts when + they are not initialized by users. """ next_oms = (n for n in network.successors(roadm) if not isinstance(n, elements.Transceiver)) for node in next_oms: # go through all the OMS departing from the ROADM - if node.uid not in roadm.per_degree_pch_out_dbm and node.uid not in roadm.per_degree_pch_psd: + if node.uid not in roadm.per_degree_pch_out_dbm and node.uid not in roadm.per_degree_pch_psd and \ + node.uid not in roadm.per_degree_pch_psw: # if no target power is defined on this degree or no per degree target power is given use the global one if roadm.params.target_pch_out_db: roadm.per_degree_pch_out_dbm[node.uid] = roadm.params.target_pch_out_db elif roadm.params.target_psd_out_mWperGHz: roadm.per_degree_pch_psd[node.uid] = roadm.params.target_psd_out_mWperGHz + elif roadm.params.target_out_mWperSlotWidth: + roadm.per_degree_pch_psw[node.uid] = roadm.params.target_out_mWperSlotWidth else: - raise ConfigurationError(roadm.uid, - 'needs a target_pch_out_db or a target_psd_out_mWperGHz') + raise ConfigurationError(roadm.uid, 'needs an equalization target') def add_roadm_booster(network, roadm): diff --git a/gnpy/core/parameters.py b/gnpy/core/parameters.py index f3a69a1b..8439cfbf 100644 --- a/gnpy/core/parameters.py +++ b/gnpy/core/parameters.py @@ -90,13 +90,15 @@ class RoadmParams(Parameters): def __init__(self, **kwargs): self.target_pch_out_db = kwargs.get('target_pch_out_db') self.target_psd_out_mWperGHz = kwargs.get('target_psd_out_mWperGHz') - equalisation_type = ['target_pch_out_db', 'target_psd_out_mWperGHz'] + self.target_out_mWperSlotWidth = kwargs.get('target_out_mWperSlotWidth') + equalisation_type = ['target_pch_out_db', 'target_psd_out_mWperGHz', 'target_out_mWperSlotWidth'] temp = [kwargs.get(k) is not None for k in equalisation_type] if sum(temp) > 1: raise ParametersError('ROADM config contains more than one equalisation type.' + 'Please choose only one', kwargs) self.per_degree_pch_out_db = kwargs.get('per_degree_pch_out_db', {}) self.per_degree_pch_psd = kwargs.get('per_degree_psd_out_mWperGHz', {}) + self.per_degree_pch_psw = kwargs.get('per_degree_psd_out_mWperSlotWidth', {}) try: self.add_drop_osnr = kwargs['add_drop_osnr'] self.pmd = kwargs['pmd'] diff --git a/gnpy/tools/json_io.py b/gnpy/tools/json_io.py index 38eb478c..ab1c4211 100644 --- a/gnpy/tools/json_io.py +++ b/gnpy/tools/json_io.py @@ -105,19 +105,19 @@ class Roadm(_JsonThing): def __init__(self, **kwargs): # If equalization is not defined in equipment, then raise an error. - # else use the one defined in equipment. Only one type of equalization - # must be defined: power (target_pch_out_db) or PSD (target_psd_out_mWperGHz) - equalisation_type = ['target_pch_out_db', 'target_psd_out_mWperGHz'] - temp = [k in kwargs for k in equalisation_type] - if sum(temp) > 1: + # Only one type of equalization must be defined. + allowed_equalisations = ['target_pch_out_db', 'target_psd_out_mWperGHz', 'target_out_mWperSlotWidth'] + requested_eq_mask = [eq in kwargs for eq in allowed_equalisations] + if sum(requested_eq_mask) > 1: raise EquipmentConfigError('Only one equalization type should be set in ROADM, found: ' - + ', '.join(temp)) - for key in equalisation_type: + + ', '.join(eq for eq in allowed_equalisations if eq in kwargs)) + if not any(requested_eq_mask): + raise EquipmentConfigError('No equalization type set in ROADM') + + for key in allowed_equalisations: if key in kwargs: setattr(self, key, kwargs[key]) break - if not any(temp): - raise EquipmentConfigError('At least one default equalization type should be set in ROADM') self.update_attr(self.default_values, kwargs, 'Roadm') @@ -486,7 +486,7 @@ def network_from_json(json_data, equipment): # if more than one equalization was defined in element config, then raise an error extra_params = merge_equalization(temp, extra_params) if not extra_params: - raise ConfigurationError(f'ROADM {el_config["uid"]} has incorrect configuration, check the equalization settings') + raise ConfigurationError(f'ROADM {el_config["uid"]}: invalid equalization settings') temp = merge_amplifier_restrictions(temp, extra_params) el_config['params'] = temp el_config['type_variety'] = variety @@ -700,9 +700,10 @@ def merge_equalization(params, extra_params): """params contains ROADM element config and extra_params default values from equipment library. If equalization is not defined in ROADM element use the one defined in equipment library. Only one type of equalization must be defined: power (target_pch_out_db) or PSD (target_psd_out_mWperGHz) + or PSW (target_out_mWperSlotWidth) params and extra_params are dict """ - equalization_types = ['target_pch_out_db', 'target_psd_out_mWperGHz'] + equalization_types = ['target_pch_out_db', 'target_psd_out_mWperGHz', 'target_out_mWperSlotWidth'] roadm_equalizations = find_equalisation(params, equalization_types) if sum(roadm_equalizations.values()) > 1: # if ROADM config contains more than one equalization type then this is an error diff --git a/gnpy/topology/request.py b/gnpy/topology/request.py index 1277290e..3bdd5c4c 100644 --- a/gnpy/topology/request.py +++ b/gnpy/topology/request.py @@ -345,7 +345,8 @@ def ref_carrier(equipment): req_power records the power in W that the user has defined for a given request (which might be different from the one used for the design). """ - return ReferenceCarrier(baud_rate=equipment['SI']['default'].baud_rate) + return ReferenceCarrier(baud_rate=equipment['SI']['default'].baud_rate, + slot_width=equipment['SI']['default'].spacing) def propagate(path, req, equipment): diff --git a/tests/test_amplifier.py b/tests/test_amplifier.py index bf2c162c..4ffa7f93 100644 --- a/tests/test_amplifier.py +++ b/tests/test_amplifier.py @@ -74,7 +74,8 @@ def si(nch_and_spacing, bw): f_min = 191.3e12 f_max = automatic_fmax(f_min, spacing, nb_channel) return create_input_spectral_information(f_min=f_min, f_max=f_max, roll_off=0.15, baud_rate=bw, power=1e-3, - spacing=spacing, tx_osnr=40.0, ref_carrier=ReferenceCarrier(baud_rate=32e9)) + spacing=spacing, tx_osnr=40.0, + ref_carrier=ReferenceCarrier(baud_rate=32e9, slot_width=50e9)) @pytest.mark.parametrize("gain, nf_expected", [(10, 15), (15, 10), (25, 5.8)]) diff --git a/tests/test_equalization.py b/tests/test_equalization.py index 2465ea81..d5350b5d 100644 --- a/tests/test_equalization.py +++ b/tests/test_equalization.py @@ -33,11 +33,16 @@ NETWORK_FILENAME = TEST_DIR / 'data/testTopology_expected.json' [('east edfa in Lannion_CAS to Morlaix', 'target_pch_out_db', -20, -20, [-20, -20, -20, -20, -20]), ('east edfa in Lannion_CAS to Morlaix', 'target_psd_out_mWperGHz', 5e-4, -17.9588, [-17.9588, -16.7778, -14.9485, -16.7778, -17.9588]), + ('east edfa in Lannion_CAS to Morlaix', 'target_out_mWperSlotWidth', 3e-4, -18.2390, + [-19.4885, -18.2390, -16.4781, -18.2390, -19.4885]), ('east edfa in Lannion_CAS to Corlay', 'target_pch_out_db', -20, -16, [-16, -16, -16, -16, -16]), ('east edfa in Lannion_CAS to Corlay', 'target_psd_out_mWperGHz', 5e-4, -16, [-16, -16, -16, -16, -16]), + ('east edfa in Lannion_CAS to Corlay', 'target_out_mWperSlotWidth', 5e-4, -16, [-16, -16, -16, -16, -16]), ('east edfa in Lannion_CAS to Stbrieuc', 'target_pch_out_db', -20, -17.16699, [-17.16698771, -15.98599459, -14.15668776, -15.98599459, -17.16698771]), ('east edfa in Lannion_CAS to Stbrieuc', 'target_psd_out_mWperGHz', 5e-4, -17.16699, + [-17.16698771, -15.98599459, -14.15668776, -15.98599459, -17.16698771]), + ('east edfa in Lannion_CAS to Stbrieuc', 'target_out_mWperSlotWidth', 5e-4, -17.16699, [-17.16698771, -15.98599459, -14.15668776, -15.98599459, -17.16698771])]) @pytest.mark.parametrize('delta_pdb_per_channel', [[0, 0, 0, 0, 0], [1, 3, 0, -5, 0]]) def test_equalization_combination_degree(delta_pdb_per_channel, degree, equalization_type, target, @@ -45,6 +50,7 @@ def test_equalization_combination_degree(delta_pdb_per_channel, degree, equaliza """Check that ROADM correctly computes power of thr reference channel based on different combination of equalization for ROADM and per degree """ + roadm_config = { "uid": "roadm Lannion_CAS", "params": { @@ -69,7 +75,7 @@ def test_equalization_combination_degree(delta_pdb_per_channel, degree, equaliza slot_width = array([37.5e9, 50e9, 75e9, 50e9, 37.5e9]) baud_rate = array([32e9, 42e9, 64e9, 42e9, 32e9]) signal = dbm2watt(array([-20.0, -18.0, -22.0, -25.0, -16.0])) - ref_carrier = ReferenceCarrier(baud_rate=32e9) + ref_carrier = ReferenceCarrier(baud_rate=32e9, slot_width=50e9) pref = Pref(p_span0=0, p_spani=0, ref_carrier=ref_carrier) si = create_arbitrary_spectral_information(frequency=frequency, slot_width=slot_width, signal=signal, baud_rate=baud_rate, roll_off=0.15, @@ -95,7 +101,8 @@ def test_equalization_combination_degree(delta_pdb_per_channel, degree, equaliza assert_allclose(expected_si, roadm.get_per_degree_power(degree, spectral_info=si), rtol=1e-3) -def test_wrong_element_config(): +@pytest.mark.parametrize('equalization_type', ["target_psd_out_mWperGHz", "target_out_mWperSlotWidth"]) +def test_wrong_element_config(equalization_type): """Check that 2 equalization correcty raise a config error """ roadm_config = { @@ -103,7 +110,7 @@ def test_wrong_element_config(): "params": { "per_degree_pch_out_db": {}, "target_pch_out_db": -20, - "target_psd_out_mWperGHz": 3.125e-4, + equalization_type: 3.125e-4, "add_drop_osnr": 38, "pmd": 0, "pdl": 0, @@ -152,6 +159,7 @@ def test_merge_equalization(): roadm = [n for n in network.nodes()][0] assert roadm.target_pch_out_dbm is None assert roadm.target_psd_out_mWperGHz == 3.125e-4 + assert roadm.target_out_mWperSlotWidth is None json_data = { "elements": [{ "uid": "roadm Brest_KLA", @@ -163,6 +171,7 @@ def test_merge_equalization(): roadm = [n for n in network.nodes()][0] assert roadm.target_pch_out_dbm == -18 assert roadm.target_psd_out_mWperGHz is None + assert roadm.target_out_mWperSlotWidth is None json_data = { "elements": [{ "uid": "roadm Brest_KLA", @@ -174,6 +183,19 @@ def test_merge_equalization(): roadm = [n for n in network.nodes()][0] assert roadm.target_pch_out_dbm is None assert roadm.target_psd_out_mWperGHz == 5e-4 + assert roadm.target_out_mWperSlotWidth is None + json_data = { + "elements": [{ + "uid": "roadm Brest_KLA", + "type": "Roadm", + "params": {"target_out_mWperSlotWidth": 3e-4}}], + "connections": [] + } + network = network_from_json(json_data, equipment) + roadm = [n for n in network.nodes()][0] + assert roadm.target_pch_out_dbm is None + assert roadm.target_psd_out_mWperGHz is None + assert roadm.target_out_mWperSlotWidth == 3e-4 @pytest.mark.parametrize('target_out, delta_pdb_per_channel, correction', @@ -191,7 +213,7 @@ def test_low_input_power(target_out, delta_pdb_per_channel, correction): baud_rate = array([32e9, 42e9, 64e9, 42e9, 32e9]) signal = dbm2watt(array([-20.0, -18.0, -22.0, -25.0, -16.0])) target = target_out + array(delta_pdb_per_channel) - ref_carrier = ReferenceCarrier(baud_rate=32e9) + ref_carrier = ReferenceCarrier(baud_rate=32e9, slot_width=50e9) pref = Pref(p_span0=0, p_spani=-20, ref_carrier=ref_carrier) si = create_arbitrary_spectral_information(frequency=frequency, slot_width=slot_width, signal=signal, baud_rate=baud_rate, roll_off=0.15, @@ -243,7 +265,7 @@ def test_2low_input_power(target_out, delta_pdb_per_channel, correction): baud_rate = array([32e9, 42e9, 64e9, 42e9, 32e9]) signal = dbm2watt(array([-20.0, -18.0, -22.0, -25.0, -16.0])) target = psd2powerdbm(target_out, baud_rate) + array(delta_pdb_per_channel) - ref_carrier = ReferenceCarrier(baud_rate=32e9) + ref_carrier = ReferenceCarrier(baud_rate=32e9, slot_width=50e9) pref = Pref(p_span0=0, p_spani=-20, ref_carrier=ref_carrier) si = create_arbitrary_spectral_information(frequency=frequency, slot_width=slot_width, signal=signal, baud_rate=baud_rate, roll_off=0.15, @@ -381,30 +403,23 @@ def test_initial_spectrum_not_identical(): assert_raises(AssertionError, assert_array_equal, infos_expected.number_of_channels, infos_actual.number_of_channels) -@pytest.mark.parametrize('with_initial_spectrum', [None, 0, +2, -0.5]) -def test_target_psd_out_mwperghz(with_initial_spectrum): - """ checks that if target_psd_out_mWperGHz is defined, it is used as equalization, and it gives same result if - computed target is the same +@pytest.mark.parametrize('equalization, target_value', [ + ('target_out_mWperSlotWidth', power_dbm_to_psd_mw_ghz(-20, 50e9)), + ('target_psd_out_mWperGHz', power_dbm_to_psd_mw_ghz(-20, 32e9))]) +@pytest.mark.parametrize('power_dbm', [0, 2, -0.5]) +def test_target_psd_or_psw(power_dbm, equalization, target_value): + """ checks that if target_out_mWperSlotWidth or target_psd_out_mWperGHz is defined, it is used as equalization + and it gives same result if computed target is the same """ equipment = load_equipment(EQPT_FILENAME) network = net_setup(equipment) req = create_voyager_req(equipment, 'trx Brest_KLA', 'trx Vannes_KBE', False, ['trx Vannes_KBE'], ['STRICT'], - 'mode 1', 50e9, with_initial_spectrum) - if with_initial_spectrum: - temp = [{ - "f_min": 191.35e12, # align f_min , f_max on Voyager f_min, f_max and not SI ! - "f_max": 196.05e12, - "baud_rate": req.baud_rate, - "slot_width": 50e9, - "roll_off": 0.15, - "tx_osnr": 40 - }] - req.initial_spectrum = _spectrum_from_json(temp) + 'mode 1', 50e9, power_dbm) path = compute_constrained_path(network, req) infos_expected = propagate(path, req, equipment) # change default equalization to power spectral density delattr(equipment['Roadm']['default'], 'target_pch_out_db') - setattr(equipment['Roadm']['default'], 'target_psd_out_mWperGHz', power_dbm_to_psd_mw_ghz(-20, 32e9)) + setattr(equipment['Roadm']['default'], equalization, target_value) # create a second instance with this roadm settings, network2 = net_setup(equipment) path2 = compute_constrained_path(network2, req) @@ -470,11 +485,12 @@ def test_target_psd_out_mwperghz_deltap(deltap): assert expected_gain == pytest.approx(actual_gain, rel=1e-3) +@pytest.mark.parametrize('equalization', ['target_psd_out_mWperGHz', 'target_out_mWperSlotWidth']) @pytest.mark.parametrize('case', ['SI', 'nodes']) @pytest.mark.parametrize('deltap', [0, +2, -0.5]) @pytest.mark.parametrize('target', [-20, -21, -18]) @pytest.mark.parametrize('mode, slot_width', (['mode 1', 50e9], ['mode 2', 75e9])) -def test_equalization(case, deltap, target, mode, slot_width): +def test_equalization(case, deltap, target, mode, slot_width, equalization): """check that power target on roadm is correct for these cases; check on booster - SI : target_pch_out_db / target_psd_out_mWperGHz - node : target_pch_out_db / target_psd_out_mWperGHz @@ -493,17 +509,16 @@ def test_equalization(case, deltap, target, mode, slot_width): # boosters = ['east edfa in Brest_KLA to Quimper', 'east edfa in Lorient_KMA to Loudeac', # 'east edfa in Lannion_CAS to Stbrieuc'] target_psd = power_dbm_to_psd_mw_ghz(target, 32e9) - ref = ReferenceCarrier(baud_rate=32e9) + ref = ReferenceCarrier(baud_rate=32e9, slot_width=50e9) if case == 'SI': delattr(equipment['Roadm']['default'], 'target_pch_out_db') - setattr(equipment['Roadm']['default'], 'target_psd_out_mWperGHz', - target_psd) + setattr(equipment['Roadm']['default'], equalization, target_psd) network = net_setup(equipment) elif case == 'nodes': json_data = load_json(NETWORK_FILENAME) for el in json_data['elements']: if el['uid'] in roadms: - el['params'] = {'target_psd_out_mWperGHz': target_psd} + el['params'] = {equalization: target_psd} network = network_from_json(json_data, equipment) spectrum = equipment['SI']['default'] p_db = spectrum.power_dbm @@ -514,6 +529,9 @@ def test_equalization(case, deltap, target, mode, slot_width): for roadm in pw_roadms: assert roadm.target_psd_out_mWperGHz is None assert roadm.target_pch_out_dbm == target + for roadm in [r for r in network.nodes() if r.uid in roadms and isinstance(r, Roadm)]: + assert roadm.target_pch_out_dbm is None + assert getattr(roadm, equalization) == target_psd path = compute_constrained_path(network, req) si = create_input_spectral_information( f_min=req.f_min, f_max=req.f_max, roll_off=req.roll_off, baud_rate=req.baud_rate, power=req.power, @@ -522,8 +540,12 @@ def test_equalization(case, deltap, target, mode, slot_width): if isinstance(el, Roadm): si = el(si, degree=path[i + 1].uid) if case in ['SI', 'nodes', 'degrees']: - assert_allclose(power_dbm_to_psd_mw_ghz(watt2dbm(si.signal + si.ase + si.nli), si.baud_rate), - target_psd, rtol=1e-3) + if equalization == 'target_psd_out_mWperGHz': + assert_allclose(power_dbm_to_psd_mw_ghz(watt2dbm(si.signal + si.ase + si.nli), si.baud_rate), + target_psd, rtol=1e-3) + if equalization == 'target_out_mWperSlotWidth': + assert_allclose(power_dbm_to_psd_mw_ghz(watt2dbm(si.signal + si.ase + si.nli), si.slot_width), + target_psd, rtol=1e-3) else: si = el(si) print(el.uid) diff --git a/tests/test_propagation.py b/tests/test_propagation.py index 566e78b7..519d688b 100644 --- a/tests/test_propagation.py +++ b/tests/test_propagation.py @@ -47,7 +47,7 @@ def propagation(input_power, con_in, con_out, dest): spacing = 50e9 # THz si = create_input_spectral_information(f_min=191.3e12, f_max=191.3e12 + 79 * spacing, roll_off=0.15, baud_rate=32e9, power=p, spacing=spacing, tx_osnr=None, - ref_carrier=ReferenceCarrier(baud_rate=32e9)) + ref_carrier=ReferenceCarrier(baud_rate=32e9, slot_width=50e9)) source = next(transceivers[uid] for uid in transceivers if uid == 'trx A') sink = next(transceivers[uid] for uid in transceivers if uid == dest) path = dijkstra_path(network, source, sink) diff --git a/tests/test_science_utils.py b/tests/test_science_utils.py index 6b46d3f8..fda4edc5 100644 --- a/tests/test_science_utils.py +++ b/tests/test_science_utils.py @@ -29,7 +29,7 @@ def test_fiber(): # fix grid spectral information generation spectral_info_input = create_input_spectral_information(f_min=191.3e12, f_max=196.1e12, roll_off=0.15, baud_rate=32e9, power=1e-3, spacing=50e9, tx_osnr=40.0, - ref_carrier=ReferenceCarrier(baud_rate=32e9)) + ref_carrier=ReferenceCarrier(baud_rate=32e9, slot_width=50e9)) # propagation spectral_info_out = fiber(spectral_info_input) @@ -69,7 +69,7 @@ def test_raman_fiber(): # spectral information generation spectral_info_input = create_input_spectral_information(f_min=191.3e12, f_max=196.1e12, roll_off=0.15, baud_rate=32e9, power=1e-3, spacing=50e9, tx_osnr=40.0, - ref_carrier=ReferenceCarrier(baud_rate=32e9)) + ref_carrier=ReferenceCarrier(baud_rate=32e9, slot_width=50e9)) SimParams.set_params(load_json(TEST_DIR / 'data' / 'sim_params.json')) fiber = RamanFiber(**load_json(TEST_DIR / 'data' / 'test_science_utils_fiber_config.json')) @@ -107,7 +107,7 @@ def test_fiber_lumped_losses_srs(set_sim_params): # spectral information generation spectral_info_input = create_input_spectral_information(f_min=191.3e12, f_max=196.1e12, roll_off=0.15, baud_rate=32e9, power=1e-3, spacing=50e9, tx_osnr=40.0, - ref_carrier=ReferenceCarrier(baud_rate=32e9)) + ref_carrier=ReferenceCarrier(baud_rate=32e9, slot_width=50e9)) SimParams.set_params(load_json(TEST_DIR / 'data' / 'sim_params.json')) fiber = Fiber(**load_json(TEST_DIR / 'data' / 'test_lumped_losses_raman_fiber_config.json'))