diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index 112d17da7..7846f276e 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -26,7 +26,7 @@ from scipy.interpolate import interp1d from collections import namedtuple -from gnpy.core.utils import lin2db, db2lin, arrange_frequencies, snr_sum +from gnpy.core.utils import lin2db, db2lin, arrange_frequencies, snr_sum, watt2dbm 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 @@ -216,21 +216,21 @@ def __init__(self, *args, params=None, **kwargs): if not params: params = {} super().__init__(*args, params=RoadmParams(**params), **kwargs) - self.pch_out_db = self.params.target_pch_out_db + self.ref_pch_out_dbm = self.params.target_pch_out_db self.loss = 0 # auto-design interest self.effective_loss = None self.passive = True self.restrictions = self.params.restrictions - self.per_degree_pch_out_db = self.params.per_degree_pch_out_db + self.per_degree_pch_out_dbm = self.params.per_degree_pch_out_db @property def to_json(self): return {'uid': self.uid, 'type': type(self).__name__, 'params': { - 'target_pch_out_db': self.pch_out_db, + 'target_pch_out_db': self.ref_pch_out_dbm, 'restrictions': self.restrictions, - 'per_degree_pch_out_db': self.per_degree_pch_out_db + 'per_degree_pch_out_db': self.per_degree_pch_out_dbm }, 'metadata': { 'location': self.metadata['location']._asdict() @@ -246,7 +246,7 @@ def __str__(self): return '\n'.join([f'{type(self).__name__} {self.uid}', f' effective loss (dB): {self.effective_loss:.2f}', - f' pch out (dBm): {self.pch_out_db:.2f}']) + f' pch out (dBm): {self.ref_pch_out_dbm:.2f}']) def propagate(self, spectral_info, degree): # pin_target and loss are read from eqpt_config.json['Roadm'] @@ -258,10 +258,17 @@ def propagate(self, spectral_info, degree): # if the input power is lower than the target one, use the input power instead because # a ROADM doesn't amplify, it can only attenuate # TODO maybe add a minimum loss for the ROADM - per_degree_pch = self.per_degree_pch_out_db[degree] \ - if degree in self.per_degree_pch_out_db else self.pch_out_db - self.pch_out_db = min(spectral_info.pref.p_spani, per_degree_pch) - self.effective_loss = spectral_info.pref.p_spani - self.pch_out_db + per_degree_pch = self.per_degree_pch_out_dbm.get(degree, self.ref_pch_out_dbm) + # Definition of ref_pch_out_dbm for the reference channel: + # Depending on propagation upstream from this ROADM, the input power (p_spani) might be smaller than + # the target power out configured for this ROADM degree's egress. Since ROADM does not amplify, + # the power out of the ROADM for the ref channel is the min value between target power and input power. + # (TODO add a minimum loss for the ROADM crossing) + self.ref_pch_out_dbm = min(spectral_info.pref.p_spani, 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 + # different carriers. effective_loss records the loss for a reference carrier. + self.effective_loss = spectral_info.pref.p_spani - self.ref_pch_out_dbm input_power = spectral_info.signal + spectral_info.nli + spectral_info.ase min_power = min(lin2db(input_power * 1e3)) per_degree_pch = per_degree_pch if per_degree_pch < min_power else min_power @@ -271,7 +278,8 @@ def propagate(self, spectral_info, degree): spectral_info.pdl = sqrt(spectral_info.pdl ** 2 + self.params.pdl ** 2) def update_pref(self, spectral_info): - spectral_info.pref = spectral_info.pref._replace(p_span0=spectral_info.pref.p_span0, p_spani=self.pch_out_db) + spectral_info.pref = spectral_info.pref._replace(p_span0=spectral_info.pref.p_span0, + p_spani=self.ref_pch_out_dbm) def __call__(self, spectral_info, degree): self.propagate(spectral_info, degree=degree) @@ -639,7 +647,7 @@ def interpol_params(self, spectral_info): self.nch = spectral_info.number_of_channels pin = spectral_info.signal + spectral_info.ase + spectral_info.nli - self.pin_db = lin2db(sum(pin * 1e3)) + self.pin_db = watt2dbm(sum(pin)) # The following should be changed when we have the new spectral information including slot widths. # For now, with homogeneous spectrum, we can calculate it as the difference between neighbouring channels. self.slot_width = self.channel_freq[1] - self.channel_freq[0] @@ -652,15 +660,14 @@ def interpol_params(self, spectral_info): self.effective_gain = self.target_pch_out_db - pref.p_spani """check power saturation and correct effective gain & power accordingly:""" + # Compute the saturation accounting for actual power at the input of the amp self.effective_gain = min( self.effective_gain, - self.params.p_max - (pref.p_spani + pref.neq_ch) + self.params.p_max - self.pin_db ) - #print(self.uid, self.effective_gain, self.operational.gain_target) self.effective_pch_out_db = round(pref.p_spani + self.effective_gain, 2) """check power saturation and correct target_gain accordingly:""" - #print(self.uid, self.effective_gain, self.pin_db, pref.p_spani) self.nf = self._calc_nf() self.gprofile = self._gain_profile(pin) diff --git a/gnpy/core/network.py b/gnpy/core/network.py index 582c65cc6..ca119b59f 100644 --- a/gnpy/core/network.py +++ b/gnpy/core/network.py @@ -238,7 +238,7 @@ def set_egress_amplifier(network, this_node, equipment, pref_ch_db, pref_total_d """ power_mode = equipment['Span']['default'].power_mode next_oms = (n for n in network.successors(this_node) if not isinstance(n, elements.Transceiver)) - this_node_degree = {k: v for k, v in this_node.per_degree_pch_out_db.items()} if hasattr(this_node, 'per_degree_pch_out_db') else {} + this_node_degree = getattr(this_node, 'per_degree_pch_out_dbm', {}) for oms in next_oms: # go through all the OMS departing from the ROADM prev_node = this_node @@ -334,7 +334,7 @@ def set_egress_amplifier(network, this_node, equipment, pref_ch_db, pref_total_d # print(f'{node.uid}') if isinstance(this_node, elements.Roadm): - this_node.per_degree_pch_out_db = {k: v for k, v in this_node_degree.items()} + this_node.per_degree_pch_out_dbm = {k: v for k, v in this_node_degree.items()} def add_roadm_booster(network, roadm): diff --git a/gnpy/core/utils.py b/gnpy/core/utils.py index a9a943498..26a784a2e 100644 --- a/gnpy/core/utils.py +++ b/gnpy/core/utils.py @@ -106,6 +106,69 @@ def db2lin(value): return 10**(value / 10) +def watt2dbm(value): + """Convert Watt units to dBm + + >>> round(watt2dbm(0.001), 1) + 0.0 + >>> round(watt2dbm(0.02), 1) + 13.0 + """ + return lin2db(value * 1e3) + + +def dbm2watt(value): + """Convert dBm units to Watt + + >>> round(dbm2watt(0), 4) + 0.001 + >>> round(dbm2watt(-3), 4) + 0.0005 + >>> round(dbm2watt(13), 4) + 0.02 + """ + return db2lin(value) * 1e-3 + + +def psd2powerdbm(psd_mwperghz, baudrate_baud): + """computes power in dBm based on baudrate in bauds and psd in mW/GHz + + >>> round(psd2powerdbm(0.031176, 64e9),3) + 3.0 + >>> round(psd2powerdbm(0.062352, 32e9),3) + 3.0 + >>> round(psd2powerdbm(0.015625, 64e9),3) + 0.0 + """ + return lin2db(baudrate_baud * psd_mwperghz * 1e-9) + + +def power_dbm_to_psd_mw_ghz(power_dbm, baudrate_baud): + """computes power spectral density in mW/GHz based on baudrate in bauds and power in dBm + + >>> power_dbm_to_psd_mw_ghz(0, 64e9) + 0.015625 + >>> round(power_dbm_to_psd_mw_ghz(3, 64e9), 6) + 0.031176 + >>> round(power_dbm_to_psd_mw_ghz(3, 32e9), 6) + 0.062352 + """ + return db2lin(power_dbm) / (baudrate_baud * 1e-9) + + +def psd_mw_per_ghz(power_watt, baudrate_baud): + """computes power spectral density in mW/GHz based on baudrate in bauds and power in W + + >>> psd_mw_per_ghz(2e-3, 32e9) + 0.0625 + >>> psd_mw_per_ghz(1e-3, 64e9) + 0.015625 + >>> psd_mw_per_ghz(0.5e-3, 32e9) + 0.015625 + """ + return power_watt * 1e3 / (baudrate_baud * 1e-9) + + def round2float(number, step): """Round a floating point number so that its "resolution" is not bigger than 'step' diff --git a/tests/data/testTopology_response.json b/tests/data/testTopology_response.json index dc4a4a12f..6681ec3c5 100644 --- a/tests/data/testTopology_response.json +++ b/tests/data/testTopology_response.json @@ -1258,7 +1258,7 @@ }, { "metric-type": "SNR-0.1nm", - "accumulative-value": 28.77 + "accumulative-value": 28.78 }, { "metric-type": "OSNR-bandwidth", diff --git a/tests/data/testTopology_response_expected.csv b/tests/data/testTopology_response_expected.csv index 7aed0436a..3853fae2b 100644 --- a/tests/data/testTopology_response_expected.csv +++ b/tests/data/testTopology_response_expected.csv @@ -3,5 +3,5 @@ response-id,source,destination,path_bandwidth,Pass?,nb of tsp pairs,total cost,t 1,trx Brest_KLA,trx Vannes_KBE,10.0,True,1,1,Voyager,mode 1,22.65,22.11,18.03,32.0,1.0,trx Brest_KLA | roadm Brest_KLA | east edfa in Brest_KLA to Morlaix | fiber (Brest_KLA → Morlaix)-F060 | east fused spans in Morlaix | fiber (Morlaix → Lannion_CAS)-F059 | west edfa in Lannion_CAS to Morlaix | roadm Lannion_CAS | east edfa in Lannion_CAS to Corlay | fiber (Lannion_CAS → Corlay)-F061 | west fused spans in Corlay | fiber (Corlay → Loudeac)-F010 | west fused spans in Loudeac | fiber (Loudeac → Lorient_KMA)-F054 | west edfa in Lorient_KMA to Loudeac | roadm Lorient_KMA | east edfa in Lorient_KMA to Vannes_KBE | fiber (Lorient_KMA → Vannes_KBE)-F055 | west edfa in Vannes_KBE to Lorient_KMA | roadm Vannes_KBE | trx Vannes_KBE,"-276, 4",,, 3,trx Lannion_CAS,trx Rennes_STA,60.0,True,1,1,vendorA_trx-type1,mode 1,28.29,25.85,21.77,32.0,1.0,trx Lannion_CAS | roadm Lannion_CAS | east edfa in Lannion_CAS to Stbrieuc | fiber (Lannion_CAS → Stbrieuc)-F056 | east edfa in Stbrieuc to Rennes_STA | fiber (Stbrieuc → Rennes_STA)-F057 | west edfa in Rennes_STA to Stbrieuc | roadm Rennes_STA | trx Rennes_STA,"-284, 4",,, 4,trx Rennes_STA,trx Lannion_CAS,150.0,True,1,1,vendorA_trx-type1,mode 2,22.27,22.15,15.05,64.0,0.0,trx Rennes_STA | roadm Rennes_STA | east edfa in Rennes_STA to Ploermel | fiber (Rennes_STA → Ploermel)- | east edfa in Ploermel to Vannes_KBE | fiber (Ploermel → Vannes_KBE)- | west edfa in Vannes_KBE to Ploermel | roadm Vannes_KBE | east edfa in Vannes_KBE to Lorient_KMA | fiber (Vannes_KBE → Lorient_KMA)-F055 | west edfa in Lorient_KMA to Vannes_KBE | roadm Lorient_KMA | east edfa in Lorient_KMA to Loudeac | fiber (Lorient_KMA → Loudeac)-F054 | east fused spans in Loudeac | fiber (Loudeac → Corlay)-F010 | east fused spans in Corlay | fiber (Corlay → Lannion_CAS)-F061 | west edfa in Lannion_CAS to Corlay | roadm Lannion_CAS | trx Lannion_CAS,"-266, 6",,, -5,trx Rennes_STA,trx Lannion_CAS,20.0,True,1,1,vendorA_trx-type1,mode 2,30.79,28.77,21.68,64.0,3.0,trx Rennes_STA | roadm Rennes_STA | east edfa in Rennes_STA to Stbrieuc | fiber (Rennes_STA → Stbrieuc)-F057 | west edfa in Stbrieuc to Rennes_STA | fiber (Stbrieuc → Lannion_CAS)-F056 | west edfa in Lannion_CAS to Stbrieuc | roadm Lannion_CAS | trx Lannion_CAS,"-274, 6",,, +5,trx Rennes_STA,trx Lannion_CAS,20.0,True,1,1,vendorA_trx-type1,mode 2,30.79,28.78,21.68,64.0,3.0,trx Rennes_STA | roadm Rennes_STA | east edfa in Rennes_STA to Stbrieuc | fiber (Rennes_STA → Stbrieuc)-F057 | west edfa in Stbrieuc to Rennes_STA | fiber (Stbrieuc → Lannion_CAS)-F056 | west edfa in Lannion_CAS to Stbrieuc | roadm Lannion_CAS | trx Lannion_CAS,"-274, 6",,, 6,,,,NO_PATH,,,,,,,,,,,,,, diff --git a/tests/invocation/path_requests_run b/tests/invocation/path_requests_run index 78348ce98..e54ce1db6 100644 --- a/tests/invocation/path_requests_run +++ b/tests/invocation/path_requests_run @@ -146,7 +146,7 @@ req id demand GSNR@bandwidth A-Z (Z-A) GSNR@0 0 trx Lorient_KMA to trx Vannes_KBE : 24.83 28.92 14 mode 1 100.0 1 (-284,4) 1 trx Brest_KLA to trx Vannes_KBE : 17.75 21.83 14 mode 1 200.0 2 (-272,8) 3 trx Lannion_CAS to trx Rennes_STA : 22.21 26.29 13 mode 1 60.0 1 (-284,4) -4 trx Rennes_STA to trx Lannion_CAS : 16.07 23.29 17 mode 2 150.0 1 (-258,6) +4 trx Rennes_STA to trx Lannion_CAS : 16.06 23.29 17 mode 2 150.0 1 (-258,6) 5 trx Rennes_STA to trx Lannion_CAS : 20.31 27.54 17 mode 2 20.0 1 (-274,6) 7 | 6 trx Lannion_CAS to trx Lorient_KMA : 19.52 23.61 14 mode 1 700.0 7 (-224,28) 7b trx Lannion_CAS to trx Lorient_KMA : 19.61 23.69 14 mode 1 400.0 4 (-172,24) diff --git a/tests/invocation/transmission_saturated b/tests/invocation/transmission_saturated index d071219fd..39780e680 100644 --- a/tests/invocation/transmission_saturated +++ b/tests/invocation/transmission_saturated @@ -297,19 +297,19 @@ Fiber fiber (Loudeac → Lorient_KMA)-F054 pch out (dBm): -26.82 Edfa west edfa in Lorient_KMA to Loudeac type_variety: test - effective gain(dB): 28.00 + effective gain(dB): 27.99 (before att_in and before output VOA) noise figure (dB): 5.76 (including att_in) pad att_in (dB): 0.00 Power In (dBm): -6.99 - Power Out (dBm): 21.04 + Power Out (dBm): 21.03 Delta_P (dB): -1.82 target pch (dBm): 1.18 - effective pch (dBm): 1.18 + effective pch (dBm): 1.17 output VOA (dB): 0.00 Roadm roadm Lorient_KMA - effective loss (dB): 21.18 + effective loss (dB): 21.17 pch out (dBm): -20.00 Transceiver trx Lorient_KMA GSNR (0.1nm, dB): 23.94 diff --git a/tests/test_roadm_restrictions.py b/tests/test_roadm_restrictions.py index 75772d01d..15f089701 100644 --- a/tests/test_roadm_restrictions.py +++ b/tests/test_roadm_restrictions.py @@ -271,14 +271,14 @@ def test_roadm_target_power(prev_node_type, effective_pch_out_db, power_dbm): if prev_node_type == 'edfa': # edfa prev_node sets input power to roadm to a high enough value: # check that target power is correctly set in the ROADM - assert_allclose(el.pch_out_db, effective_pch_out_db, rtol=1e-3) + assert_allclose(el.ref_pch_out_dbm, effective_pch_out_db, rtol=1e-3) # Check that egress power of roadm is equal to target power assert_allclose(power_out_roadm, db2lin(effective_pch_out_db - 30), rtol=1e-3) elif prev_node_type == 'fused': # fused prev_node does reamplfy power after fiber propagation, so input power # to roadm is low. # check that target power correctly reports power_dbm from previous propagation - assert_allclose(el.pch_out_db, effective_pch_out_db + power_dbm, rtol=1e-3) + assert_allclose(el.ref_pch_out_dbm, effective_pch_out_db + power_dbm, rtol=1e-3) # Check that egress power of roadm is equalized to the min carrier input power. assert_allclose(power_out_roadm, min_power_in_roadm, rtol=1e-3) else: