Computes reference input power in ROADM during design

input power is computed at design time: so let's record it and
use it instead of p_span_i for ROADM reference channel loss computation.
Note that this loss parameter is only used for visualisation purpose.
No impact on propagation.

Since this loss is computed for the reference channel used for
design, we need to record input power based on input degrees,
and indicate this information within the call function.

Note that this will be also usefull later on to implement ROADM
parameters

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I64d510fc20df72f07158f400964d592d76dc0ce4
This commit is contained in:
EstherLerouzic
2022-10-19 15:01:01 +02:00
parent 7c60b000b5
commit 07fd89351b
6 changed files with 85 additions and 16 deletions

View File

@@ -258,6 +258,7 @@ class Roadm(_Node):
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
self.ref_pch_in_dbm = {}
@property
def to_json(self):
@@ -354,7 +355,7 @@ class Roadm(_Node):
return psd2powerdbm(self.per_degree_pch_psw[degree], spectral_info.slot_width)
return self.get_roadm_target_power(spectral_info=spectral_info)
def propagate(self, spectral_info, degree):
def propagate(self, spectral_info, degree, from_degree):
"""Equalization targets are read from topology file if defined and completed with default
definition of the library.
If the input power is lower than the target one, use the input power instead because
@@ -370,15 +371,15 @@ class Roadm(_Node):
per_degree_pch = self.get_per_degree_power(degree, spectral_info=spectral_info)
# 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
# Depending on propagation upstream from this ROADM, the input power 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, ref_per_degree_pch)
self.ref_pch_out_dbm = min(self.ref_pch_in_dbm[from_degree], 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
# different carriers. effective_loss records the loss for the reference carrier.
self.ref_effective_loss = spectral_info.pref.p_spani - self.ref_pch_out_dbm
self.ref_effective_loss = self.ref_pch_in_dbm[from_degree] - self.ref_pch_out_dbm
input_power = spectral_info.signal + spectral_info.nli + spectral_info.ase
target_power_per_channel = per_degree_pch + spectral_info.delta_pdb_per_channel
# Computation of the per channel target power according to equalization policy
@@ -415,8 +416,8 @@ class Roadm(_Node):
"""
spectral_info.pref = spectral_info.pref._replace(p_spani=self.ref_pch_out_dbm)
def __call__(self, spectral_info, degree):
self.propagate(spectral_info, degree=degree)
def __call__(self, spectral_info, degree, from_degree):
self.propagate(spectral_info, degree=degree, from_degree=from_degree)
self.update_pref(spectral_info)
return spectral_info

View File

@@ -14,7 +14,7 @@ from logging import getLogger
from gnpy.core import elements
from gnpy.core.exceptions import ConfigurationError, NetworkTopologyError
from gnpy.core.utils import round2float, convert_length
from gnpy.core.utils import round2float, convert_length, psd2powerdbm
from gnpy.core.info import ReferenceCarrier
from gnpy.tools.json_io import Amp
@@ -352,9 +352,72 @@ def set_roadm_per_degree_targets(roadm, network):
raise ConfigurationError(roadm.uid, 'needs an equalization target')
def set_roadm_input_powers(network, roadm, equipment, pref_ch_db):
"""Set reference powers at ROADM input for a reference channel and based on the adjacent OMS.
This supposes that there is no dependency on path. For example, the succession:
node power out of element
roadm A (target power -10dBm) -10dBm
fiber A (16 dB loss) -26dBm
roadm B (target power -12dBm) -26dBm
fiber B (10 dB loss) -36dBm
roadm C (target power -14dBm) -36dBm
is not consistent because target powers in roadm B and roadm C can not be met.
input power for the reference channel will be set -26 dBm in roadm B and -22dBm in roadm C,
because at design time we can not know about path.
The function raises a warning if target powers can not be met with the design.
User should be aware that design was not successfull and that power reduction was applied.
Note that this value is only used for visualisation purpose (to compute ROADM loss in elements).
"""
ref_carrier = ReferenceCarrier(baud_rate=equipment['SI']['default'].baud_rate,
slot_width=equipment['SI']['default'].spacing)
previous_elements = [n for n in network.predecessors(roadm)]
roadm.ref_pch_in_dbm = {}
for element in previous_elements:
node = element
loss = 0.0
while isinstance(node, (elements.Fiber, elements.Fused, elements.RamanFiber)):
# go through all predecessors until a power target is found either in an amplifier, a ROADM or a transceiver
# then deduce power at ROADM input from this degree based on this target and crossed losses
loss += node.loss
previous_node = node
node = next(network.predecessors(node))
if isinstance(node, elements.Edfa):
roadm.ref_pch_in_dbm[element.uid] = pref_ch_db + node._delta_p - node.out_voa - loss
elif isinstance(node, elements.Roadm):
roadm.ref_pch_in_dbm[element.uid] = \
node.get_per_degree_ref_power(degree=previous_node.uid, ref_carrier=ref_carrier) - loss
elif isinstance(node, elements.Transceiver):
roadm.ref_pch_in_dbm[element.uid] = pref_ch_db - loss
# check if target power can be met
temp = []
if roadm.per_degree_pch_out_dbm:
temp.append(max([p for p in roadm.per_degree_pch_out_dbm.values()]))
if roadm.per_degree_pch_psd:
temp.append(max([psd2powerdbm(p, ref_carrier.baud_rate) for p in roadm.per_degree_pch_psd.values()]))
if roadm.per_degree_pch_psw:
temp.append(max([psd2powerdbm(p, ref_carrier.slot_width) for p in roadm.per_degree_pch_psw.values()]))
if roadm.params.target_pch_out_db:
temp.append(roadm.params.target_pch_out_db)
if roadm.params.target_psd_out_mWperGHz:
temp.append(psd2powerdbm(roadm.params.target_psd_out_mWperGHz, ref_carrier.baud_rate))
if roadm.params.target_out_mWperSlotWidth:
temp.append(psd2powerdbm(roadm.params.target_out_mWperSlotWidth, ref_carrier.slot_width))
if not temp:
raise ConfigurationError(f'Could not find target power/PSD/PSW in ROADM "{roadm.uid}"')
target_to_be_supported = max(temp)
for from_degree, in_power in roadm.ref_pch_in_dbm.items():
if in_power < target_to_be_supported:
logger.warning(
f'WARNING: maximum target power {target_to_be_supported}dBm '
+ f'in ROADM "{roadm.uid}" can not be met for at least one crossing path. Min input power '
+ f'from "{from_degree}" direction is {round(in_power, 2)}dBm. Please correct input topology.'
)
def add_roadm_booster(network, roadm):
next_nodes = [n for n in network.successors(roadm)
if not (isinstance(n, elements.Transceiver) or isinstance(n, elements.Fused) or isinstance(n, elements.Edfa))]
if not (isinstance(n, elements.Transceiver) or isinstance(n, elements.Fused)
or isinstance(n, elements.Edfa))]
# no amplification for fused spans or TRX
for next_node in next_nodes:
network.remove_edge(roadm, next_node)
@@ -564,6 +627,8 @@ def build_network(network, equipment, pref_ch_db, pref_total_db, no_insert_edfas
for roadm in roadms:
set_roadm_per_degree_targets(roadm, network)
set_egress_amplifier(network, roadm, equipment, pref_ch_db, pref_total_db)
for roadm in roadms:
set_roadm_input_powers(network, roadm, equipment, pref_ch_db)
trx = [t for t in network.nodes() if isinstance(t, elements.Transceiver)]
for t in trx:

View File

@@ -350,7 +350,7 @@ def propagate(path, req, equipment):
ref_carrier=ref_carrier(equipment))
for i, el in enumerate(path):
if isinstance(el, Roadm):
si = el(si, degree=path[i+1].uid)
si = el(si, degree=path[i + 1].uid, from_degree=path[i - 1].uid)
else:
si = el(si)
path[0].update_snr(si.tx_osnr)
@@ -395,7 +395,7 @@ def propagate_and_optimize_mode(path, req, equipment):
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)
spc_info = el(spc_info, degree=path[i + 1].uid, from_degree=path[i - 1].uid)
else:
spc_info = el(spc_info)
for this_mode in modes_to_explore:

View File

@@ -80,7 +80,7 @@ Edfa west edfa in Lorient_KMA to Loudeac
actual pch out (dBm): 1.21
output VOA (dB): 0.00
Roadm roadm Lorient_KMA
effective loss (dB): 21.17
effective loss (dB): 21.18
reference pch out (dBm): -20.00
actual pch out (dBm): -20.00
Transceiver trx Lorient_KMA

View File

@@ -73,6 +73,7 @@ def test_equalization_combination_degree(delta_pdb_per_channel, degree, equaliza
}
}
roadm = Roadm(**roadm_config)
roadm.ref_pch_in_dbm['tata'] = 0
frequency = 191e12 + array([0, 50e9, 150e9, 225e9, 275e9])
slot_width = array([37.5e9, 50e9, 75e9, 50e9, 37.5e9])
baud_rate = array([32e9, 42e9, 64e9, 42e9, 32e9])
@@ -98,7 +99,7 @@ def test_equalization_combination_degree(delta_pdb_per_channel, degree, equaliza
'metadata': {'location': {'latitude': 0, 'longitude': 0, 'city': None, 'region': None}}
}
assert roadm.to_json == to_json_before_propagation
si = roadm(si, degree)
si = roadm(si, degree=degree, from_degree='tata')
assert roadm.ref_pch_out_dbm == pytest.approx(expected_pch_out_dbm, rel=1e-4)
assert_allclose(expected_si, roadm.get_per_degree_power(degree, spectral_info=si), rtol=1e-3)
@@ -244,7 +245,8 @@ def test_low_input_power(target_out, delta_pdb_per_channel, correction):
}
}
roadm = Roadm(**roadm_config)
si = roadm(si, 'toto')
roadm.ref_pch_in_dbm['tata'] = 0
si = roadm(si, degree='toto', from_degree='tata')
assert_allclose(watt2dbm(si.signal), target - correction, rtol=1e-5)
# in other words check that if target is below input power, target is applied else power is unchanged
assert_allclose((watt2dbm(signal) >= target) * target + (watt2dbm(signal) < target) * watt2dbm(signal),
@@ -296,7 +298,8 @@ def test_2low_input_power(target_out, delta_pdb_per_channel, correction):
}
}
roadm = Roadm(**roadm_config)
si = roadm(si, 'toto')
roadm.ref_pch_in_dbm['tata'] = 0
si = roadm(si, degree='toto', from_degree='tata')
assert_allclose(watt2dbm(si.signal), target - correction, rtol=1e-5)
@@ -537,7 +540,7 @@ def test_equalization(case, deltap, target, mode, slot_width, equalization):
spacing=req.spacing, tx_osnr=req.tx_osnr, ref_carrier=ref)
for i, el in enumerate(path):
if isinstance(el, Roadm):
si = el(si, degree=path[i + 1].uid)
si = el(si, degree=path[i + 1].uid, from_degree=path[i - 1].uid)
if case in ['SI', 'nodes', 'degrees']:
if equalization == 'target_psd_out_mWperGHz':
assert_allclose(power_dbm_to_psd_mw_ghz(watt2dbm(si.signal + si.ase + si.nli), si.baud_rate),

View File

@@ -262,7 +262,7 @@ def test_roadm_target_power(prev_node_type, effective_pch_out_db, power_dbm):
for i, el in enumerate(path):
if isinstance(el, Roadm):
power_in_roadm = si.signal + si.ase + si.nli
si = el(si, degree=path[i + 1].uid)
si = el(si, degree=path[i + 1].uid, from_degree=path[i - 1].uid)
power_out_roadm = si.signal + si.ase + si.nli
if el.uid == 'roadm node B':
# if previous was an EDFA, power level at ROADM input is enough for the ROADM to apply its