diff --git a/gnpy/core/info.py b/gnpy/core/info.py index 919678c7..ca3b9c6a 100644 --- a/gnpy/core/info.py +++ b/gnpy/core/info.py @@ -42,10 +42,11 @@ class Channel(namedtuple('Channel', """ -class Pref(namedtuple('Pref', 'p_span0, p_spani')): +class Pref(namedtuple('Pref', 'p_span0, p_spani, ref_carrier')): """noiseless reference power in dBm: p_span0: inital target carrier power for a reference channel defined by user p_spani: carrier power after element i for a reference channel defined by user + ref_carrier records the baud rate of the reference channel """ @@ -236,7 +237,7 @@ class SpectralInformation(object): delta_pdb_per_channel=append(self.delta_pdb_per_channel, other.delta_pdb_per_channel), tx_osnr=append(self.tx_osnr, other.tx_osnr), - ref_power=Pref(self.pref.p_span0, self.pref.p_spani)) + ref_power=Pref(self.pref.p_span0, self.pref.p_spani, self.pref.ref_carrier)) except SpectrumError: raise SpectrumError('Spectra cannot be summed: channels overlapping.') @@ -295,7 +296,7 @@ def create_arbitrary_spectral_information(frequency: Union[ndarray, Iterable, in raise -def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing, tx_osnr): +def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing, tx_osnr, ref_carrier=None): """ Creates a fixed slot width spectral information with flat power. all arguments are scalar values""" number_of_channels = automatic_nch(f_min, f_max, spacing) @@ -305,21 +306,24 @@ def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, delta_pdb_per_channel = zeros(number_of_channels) return create_arbitrary_spectral_information(frequency, slot_width=spacing, signal=power, baud_rate=baud_rate, roll_off=roll_off, delta_pdb_per_channel=delta_pdb_per_channel, - tx_osnr=tx_osnr, ref_power=Pref(p_span0=p_span0, p_spani=p_spani)) + tx_osnr=tx_osnr, + ref_power=Pref(p_span0=p_span0, p_spani=p_spani, + ref_carrier=ref_carrier)) -def carriers_to_spectral_information(initial_spectrum: dict[Union[int, float], Carrier], +def carriers_to_spectral_information(initial_spectrum: dict[Union[int, float], Carrier], power: float, ref_carrier: ReferenceCarrier) -> SpectralInformation: """Initial spectrum is a dict with key = carrier frequency, and value a Carrier object. :param initial_spectrum: indexed by frequency in Hz, with power offset (delta_pdb), baudrate, slot width, tx_osnr and roll off. - :param ref_carrier: reference carrier (baudrate and power) used for the reference channel + :param power: power of the request + :param ref_carrier: reference carrier (baudrate) used for the reference channel """ frequency = list(initial_spectrum.keys()) - signal = [ref_carrier.req_power * db2lin(c.delta_pdb) for c in initial_spectrum.values()] + signal = [power * db2lin(c.delta_pdb) for c in initial_spectrum.values()] roll_off = [c.roll_off for c in initial_spectrum.values()] baud_rate = [c.baud_rate for c in initial_spectrum.values()] - delta_pdb_per_channel = array([c.delta_pdb for c in initial_spectrum.values()]) + delta_pdb_per_channel = [c.delta_pdb for c in initial_spectrum.values()] slot_width = [c.slot_width for c in initial_spectrum.values()] tx_osnr = [c.tx_osnr for c in initial_spectrum.values()] p_span0 = watt2dbm(ref_carrier.req_power) @@ -327,7 +331,8 @@ def carriers_to_spectral_information(initial_spectrum: dict[Union[int, float], C return create_arbitrary_spectral_information(frequency=frequency, signal=signal, baud_rate=baud_rate, slot_width=slot_width, roll_off=roll_off, delta_pdb_per_channel=delta_pdb_per_channel, tx_osnr=tx_osnr, - ref_power=Pref(p_span0=p_span0, p_spani=p_spani)) + ref_power=Pref(p_span0=p_span0, p_spani=p_spani, + ref_carrier=ref_carrier)) @dataclass @@ -345,13 +350,15 @@ class Carrier: @dataclass class ReferenceCarrier: - """Reference channel is used during autodesign to determine target power - based on power spectral density values during propagation in ROADMs for equalization purpose. - It is also required to correctly compute the loss experienced by p_span_i in Roadm element. + """Reference channel type is used to determine target power out of ROADM for the reference channel when + constant power spectral density (PSD) equalization is set. Reference channel is the type that has been defined + in SI block and used for the initial design of the network. + Computing the power out of ROADM for the reference channel is required to correctly compute the loss + experienced by p_span_i in Roadm element. - In typical scenarios, users would pick a 32 GBaud channel at 0dBm, which will - neatly lead to the same power spectral density for a 64 GBaud channel at 3 dBm. + 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 + output power should be -20 dBm and for a 64 GBaud channel power target would need 3 dB more: -17 dBm. Other attributes (like slot_width or roll-off) may be added there for future equalization purpose. """ baud_rate: float - req_power: float diff --git a/gnpy/topology/request.py b/gnpy/topology/request.py index b791a8c7..1277290e 100644 --- a/gnpy/topology/request.py +++ b/gnpy/topology/request.py @@ -340,12 +340,12 @@ def compute_constrained_path(network, req): return total_path -def ref_carrier(req_power, equipment): +def ref_carrier(equipment): """Create a reference carier based SI information with the specified request's power: 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, req_power=req_power) + return ReferenceCarrier(baud_rate=equipment['SI']['default'].baud_rate) def propagate(path, req, equipment): @@ -353,11 +353,11 @@ def propagate(path, req, equipment): """ if req.initial_spectrum is not None: si = carriers_to_spectral_information(initial_spectrum=req.initial_spectrum, - ref_carrier=ref_carrier(req.power, equipment)) + power=req.power, ref_carrier=ref_carrier(equipment)) else: 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, spacing=req.spacing, tx_osnr=req.tx_osnr) + power=req.power, spacing=req.spacing, tx_osnr=req.tx_osnr, ref_carrier=ref_carrier(equipment)) for i, el in enumerate(path): if isinstance(el, Roadm): si = el(si, degree=path[i+1].uid) @@ -396,12 +396,11 @@ def propagate_and_optimize_mode(path, req, equipment): # this case is not yet handled: spectrum can not be defined for the path-request-run function # and this function is only called in this case. so coming here should not be considered yet. msg = f'Request: {req.request_id} contains a unexpected initial_spectrum.' - LOGGER.critical(msg) raise ServiceError(msg) spc_info = create_input_spectral_information(f_min=req.f_min, f_max=req.f_max, roll_off=equipment['SI']['default'].roll_off, baud_rate=this_br, power=req.power, spacing=req.spacing, - tx_osnr=req.tx_osnr) + tx_osnr=req.tx_osnr, ref_carrier=ref_carrier(equipment)) for i, el in enumerate(path): if isinstance(el, Roadm): spc_info = el(spc_info, degree=path[i+1].uid) diff --git a/tests/test_info.py b/tests/test_info.py index 7d603584..12de8224 100644 --- a/tests/test_info.py +++ b/tests/test_info.py @@ -13,7 +13,7 @@ def test_create_arbitrary_spectral_information(): baud_rate=32e9, signal=[1, 1, 1], delta_pdb_per_channel=[1, 1, 1], tx_osnr=40.0, - ref_power=Pref(1, 1)) + ref_power=Pref(1, 1, None)) assert_array_equal(si.baud_rate, array([32e9, 32e9, 32e9])) assert_array_equal(si.slot_width, array([37.5e9, 37.5e9, 37.5e9])) assert_array_equal(si.signal, ones(3)) @@ -35,7 +35,8 @@ def test_create_arbitrary_spectral_information(): slot_width=array([50e9, 50e9, 50e9]), baud_rate=32e9, signal=array([1, 2, 3]), tx_osnr=40.0, - ref_power=Pref(1, 1)) + ref_power=Pref(1, 1, None)) + assert_array_equal(si.signal, array([3, 2, 1])) with pytest.raises(SpectrumError, match='Spectrum baud rate, including the roll off, ' @@ -43,17 +44,16 @@ def test_create_arbitrary_spectral_information(): create_arbitrary_spectral_information(frequency=[193.25e12, 193.3e12, 193.35e12], signal=1, baud_rate=[64e9, 32e9, 64e9], slot_width=50e9, tx_osnr=40.0, - ref_power=Pref(1, 1)) + ref_power=Pref(1, 1, None)) with pytest.raises(SpectrumError, match='Spectrum required slot widths larger than the frequency spectral ' r'distances between channels: \[\(1, 2\), \(3, 4\)\].'): create_arbitrary_spectral_information(frequency=[193.26e12, 193.3e12, 193.35e12, 193.39e12], signal=1, - tx_osnr=40.0, baud_rate=32e9, slot_width=50e9, ref_power=Pref(1, 1)) + tx_osnr=40.0, baud_rate=32e9, slot_width=50e9, ref_power=Pref(1, 1, None)) with pytest.raises(SpectrumError, match='Spectrum required slot widths larger than the frequency spectral ' r'distances between channels: \[\(1, 2\), \(2, 3\)\].'): create_arbitrary_spectral_information(frequency=[193.25e12, 193.3e12, 193.35e12], signal=1, baud_rate=49e9, - tx_osnr=40.0, roll_off=0.1, ref_power=Pref(1, 1)) - + tx_osnr=40.0, roll_off=0.1, ref_power=Pref(1, 1, None)) with pytest.raises(SpectrumError, match='Dimension mismatch in input fields.'): create_arbitrary_spectral_information(frequency=[193.25e12, 193.3e12, 193.35e12], signal=[1, 2], baud_rate=49e9, - tx_osnr=40.0, ref_power=Pref(1, 1)) + tx_osnr=40.0, ref_power=Pref(1, 1, None)) diff --git a/tests/test_science_utils.py b/tests/test_science_utils.py index 9d8b9bb7..5dddca19 100644 --- a/tests/test_science_utils.py +++ b/tests/test_science_utils.py @@ -45,7 +45,7 @@ def test_fiber(): baud_rate = array([32e9, 42e9, 64e9, 42e9, 32e9]) signal = 1e-3 + array([0, -1e-4, 3e-4, -2e-4, +2e-4]) delta_pdb_per_channel = [0, 0, 0, 0, 0] - pref = Pref(p_span0=0, p_spani=0) + pref = Pref(p_span0=0, p_spani=0, ref_carrier=None) spectral_info_input = create_arbitrary_spectral_information(frequency=frequency, slot_width=slot_width, signal=signal, baud_rate=baud_rate, roll_off=0.15, delta_pdb_per_channel=delta_pdb_per_channel,