Skip to content

Commit

Permalink
Remove Pref, and move ref_carrier definition
Browse files Browse the repository at this point in the history
Finally, ref_carrier is not meant to change after design since
it is the carrier used for design. So let's move its definition
to networks function. Only ROADM need the ref_carrier baud rate
so let's define a dedicated variable in ROADM to hold it.

Signed-off-by: EstherLerouzic <[email protected]>
Change-Id: Ida7e42dd534a04c8df8792b44980f3fd2165ecb6
  • Loading branch information
EstherLerouzic committed Nov 2, 2023
1 parent da508c9 commit 2ccad8f
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 119 deletions.
19 changes: 10 additions & 9 deletions gnpy/core/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ def __init__(self, *args, params=None, **kwargs):
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 = {}
self.ref_carrier = None

@property
def to_json(self):
Expand Down Expand Up @@ -299,8 +300,8 @@ def __str__(self):
f' reference pch out (dBm): {self.ref_pch_out_dbm:.2f}',
f' actual pch out (dBm): {total_pch}'])

def get_roadm_target_power(self, ref_carrier: ReferenceCarrier = None,
spectral_info: SpectralInformation = None) -> Union[float, ndarray]:

def get_roadm_target_power(self, spectral_info: SpectralInformation = None) -> Union[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
Expand All @@ -322,22 +323,22 @@ def get_roadm_target_power(self, ref_carrier: ReferenceCarrier = None,
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, ref_carrier.baud_rate)
return psd2powerdbm(self.target_psd_out_mWperGHz, self.ref_carrier.baud_rate)
if self.target_out_mWperSlotWidth is not None:
return psd2powerdbm(self.target_out_mWperSlotWidth, ref_carrier.slot_width)
return psd2powerdbm(self.target_out_mWperSlotWidth, self.ref_carrier.slot_width)
return None

def get_per_degree_ref_power(self, degree, ref_carrier):
def get_per_degree_ref_power(self, degree):
"""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], ref_carrier.baud_rate)
return psd2powerdbm(self.per_degree_pch_psd[degree], self.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)
return psd2powerdbm(self.per_degree_pch_psw[degree], self.ref_carrier.slot_width)
return self.get_roadm_target_power()

def get_per_degree_power(self, degree, spectral_info):
"""Get the target power in dBm out of ROADM degree for the spectral information
Expand All @@ -362,7 +363,7 @@ def propagate(self, spectral_info, degree, from_degree):
# 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)
ref_per_degree_pch = self.get_per_degree_ref_power(degree)
# find the target powers for each signal carrier
per_degree_pch = self.get_per_degree_power(degree, spectral_info=spectral_info)

Expand Down
43 changes: 7 additions & 36 deletions gnpy/core/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,14 @@ class Channel(
"""


class Pref(namedtuple('Pref', 'ref_carrier')):
"""reference channel used during design:
ref_carrier records the baud rate of the reference channel
"""


class SpectralInformation(object):
"""Class containing the parameters of the entire WDM comb.
delta_pdb_per_channel: (per frequency) per channel delta power in dbm for the actual mix of channels"""

def __init__(self, frequency: array, baud_rate: array, slot_width: array, signal: array, nli: array, ase: array,
roll_off: array, chromatic_dispersion: array, pmd: array, pdl: array, latency: array,
delta_pdb_per_channel: array, tx_osnr: array, ref_power: Pref, label: array):
delta_pdb_per_channel: array, tx_osnr: array, label: array):
indices = argsort(frequency)
self._frequency = frequency[indices]
self._df = outer(ones(frequency.shape), frequency) - outer(frequency, ones(frequency.shape))
Expand Down Expand Up @@ -87,18 +80,8 @@ def __init__(self, frequency: array, baud_rate: array, slot_width: array, signal
self._latency = latency[indices]
self._delta_pdb_per_channel = delta_pdb_per_channel[indices]
self._tx_osnr = tx_osnr[indices]
self._pref = ref_power
self._label = label[indices]

@property
def pref(self):
"""Instance of gnpy.info.Pref"""
return self._pref

@pref.setter
def pref(self, pref: Pref):
self._pref = pref

@property
def frequency(self):
return self._frequency
Expand Down Expand Up @@ -235,8 +218,6 @@ def apply_gain_db(self, gain_db):

def __add__(self, other: SpectralInformation):
try:
# Note that pref.p_spanx from "self" and "other" must be identical for a given simulation (correspond to the
# the simulation setup):
return SpectralInformation(frequency=append(self.frequency, other.frequency),
slot_width=append(self.slot_width, other.slot_width),
signal=append(self.signal, other.signal), nli=append(self.nli, other.nli),
Expand All @@ -251,21 +232,18 @@ def __add__(self, other: SpectralInformation):
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.ref_carrier),
label=append(self.label, other.label))
except SpectrumError:
raise SpectrumError('Spectra cannot be summed: channels overlapping.')


def _replace(self, carriers, pref):
def _replace(self, carriers):
self.chromatic_dispersion = array([c.chromatic_dispersion for c in carriers])
self.pmd = array([c.pmd for c in carriers])
self.pdl = array([c.pdl for c in carriers])
self.latency = array([c.latency for c in carriers])
self.signal = array([c.power.signal for c in carriers])
self.nli = array([c.power.nli for c in carriers])
self.ase = array([c.power.ase for c in carriers])
self.pref = pref
return self


Expand All @@ -280,7 +258,6 @@ def create_arbitrary_spectral_information(frequency: Union[ndarray, Iterable, fl
pmd: Union[float, ndarray, Iterable] = 0.,
pdl: Union[float, ndarray, Iterable] = 0.,
latency: Union[float, ndarray, Iterable] = 0.,
ref_power: Pref = None,
label: Union[str, ndarray, Iterable] = None):
"""This is just a wrapper around the SpectralInformation.__init__() that simplifies the creation of
a non-uniform spectral information with NLI and ASE powers set to zero."""
Expand All @@ -307,17 +284,15 @@ def create_arbitrary_spectral_information(frequency: Union[ndarray, Iterable, fl
chromatic_dispersion=chromatic_dispersion,
pmd=pmd, pdl=pdl, latency=latency,
delta_pdb_per_channel=delta_pdb_per_channel,
tx_osnr=tx_osnr,
ref_power=ref_power, label=label)
tx_osnr=tx_osnr, label=label)
except ValueError as e:
if 'could not broadcast' in str(e):
raise SpectrumError('Dimension mismatch in input fields.')
else:
raise


def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing, tx_osnr, delta_pdb=0,
ref_carrier=None):
def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing, tx_osnr, delta_pdb=0):
"""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)
Expand All @@ -326,18 +301,15 @@ def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power,
label = [f'{baud_rate * 1e-9 :.2f}G' for i in range(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(ref_carrier=ref_carrier),
label=label)
tx_osnr=tx_osnr, label=label)


def carriers_to_spectral_information(initial_spectrum: dict[float, Carrier], power: float,
ref_carrier: ReferenceCarrier) -> SpectralInformation:
def carriers_to_spectral_information(initial_spectrum: dict[float, Carrier],
power: float) -> 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 power: power of the request
:param ref_carrier: reference carrier (baudrate) used for the reference channel
"""
frequency = list(initial_spectrum.keys())
signal = [power * db2lin(c.delta_pdb) for c in initial_spectrum.values()]
Expand All @@ -351,7 +323,6 @@ def carriers_to_spectral_information(initial_spectrum: dict[float, Carrier], pow
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(ref_carrier=ref_carrier),
label=label)


Expand Down
28 changes: 15 additions & 13 deletions gnpy/core/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,6 @@ def set_amplifier_voa(amp, power_target, power_mode):
def set_egress_amplifier(network, this_node, equipment, pref_ch_db, pref_total_db, verbose):
"""this node can be a transceiver or a ROADM (same function called in both cases)"""
power_mode = equipment['Span']['default'].power_mode
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
Expand All @@ -287,7 +285,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, ref_carrier=ref_carrier)
this_node_out_power = this_node.get_per_degree_ref_power(degree=node.uid)
# use the target power on this degree
prev_dp = this_node_out_power - pref_ch_db
dp = prev_dp
Expand Down Expand Up @@ -391,6 +389,13 @@ def set_egress_amplifier(network, this_node, equipment, pref_ch_db, pref_total_d
node = next_node


def set_roadm_ref_carrier(roadm, equipment):
"""ref_carrier records carrier information used for design and usefull for equalization
"""
roadm.ref_carrier = ReferenceCarrier(baud_rate=equipment['SI']['default'].baud_rate,
slot_width=equipment['SI']['default'].spacing)


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 or per_degree_pch_psw dicts when
Expand Down Expand Up @@ -429,8 +434,6 @@ def set_roadm_input_powers(network, roadm, equipment, pref_ch_db):
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:
Expand All @@ -446,23 +449,23 @@ def set_roadm_input_powers(network, roadm, equipment, pref_ch_db):
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
node.get_per_degree_ref_power(degree=previous_node.uid) - 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()]))
temp.append(max([psd2powerdbm(p, roadm.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()]))
temp.append(max([psd2powerdbm(p, roadm.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))
temp.append(psd2powerdbm(roadm.params.target_psd_out_mWperGHz, roadm.ref_carrier.baud_rate))
if roadm.params.target_out_mWperSlotWidth:
temp.append(psd2powerdbm(roadm.params.target_out_mWperSlotWidth, ref_carrier.slot_width))
temp.append(psd2powerdbm(roadm.params.target_out_mWperSlotWidth, roadm.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)
Expand All @@ -480,8 +483,6 @@ def set_fiber_input_power(network, fiber, equipment, pref_ch_db):
Supposes that target power out of ROADMs and amplifiers are consistent.
This is only for visualisation purpose
"""
ref_carrier = ReferenceCarrier(baud_rate=equipment['SI']['default'].baud_rate,
slot_width=equipment['SI']['default'].spacing)
loss = 0.0
node = next(network.predecessors(fiber))
while isinstance(node, elements.Fused):
Expand All @@ -495,7 +496,7 @@ def set_fiber_input_power(network, fiber, equipment, pref_ch_db):
fiber.ref_pch_in_dbm = node.ref_pch_in_dbm - loss - node.loss
elif isinstance(node, elements.Roadm):
fiber.ref_pch_in_dbm = \
node.get_per_degree_ref_power(degree=previous_node.uid, ref_carrier=ref_carrier) - loss
node.get_per_degree_ref_power(degree=previous_node.uid) - loss
elif isinstance(node, elements.Edfa):
fiber.ref_pch_in_dbm = pref_ch_db + node._delta_p - node.out_voa - loss
elif isinstance(node, elements.Transceiver):
Expand Down Expand Up @@ -730,6 +731,7 @@ def build_network(network, equipment, pref_ch_db, pref_total_db, set_connector_l
add_missing_fiber_attributes(network, equipment)
# set roadm equalization targets first
for roadm in roadms:
set_roadm_ref_carrier(roadm, equipment)
set_roadm_per_degree_targets(roadm, network)
# then set amplifiers gain, delta_p and out_voa on each OMS
for roadm in roadms + transceivers:
Expand Down
20 changes: 4 additions & 16 deletions gnpy/topology/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from numpy import mean, argmin
from gnpy.core.elements import Transceiver, Roadm
from gnpy.core.utils import lin2db
from gnpy.core.info import create_input_spectral_information, carriers_to_spectral_information, ReferenceCarrier
from gnpy.core.info import create_input_spectral_information, carriers_to_spectral_information
from gnpy.core.network import design_network
from gnpy.core.exceptions import ServiceError, DisjunctionError
import gnpy.core.ansi_escapes as ansi_escapes
Expand Down Expand Up @@ -336,25 +336,14 @@ def compute_constrained_path(network, req):
return total_path


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,
slot_width=equipment['SI']['default'].spacing)


def propagate(path, req, equipment):
"""propagates signals in each element according to initial spectrum set by user"""
if req.initial_spectrum is not None:
si = carriers_to_spectral_information(initial_spectrum=req.initial_spectrum,
power=req.power, ref_carrier=ref_carrier(equipment))
si = carriers_to_spectral_information(initial_spectrum=req.initial_spectrum, power=req.power)
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, delta_pdb=req.offset_db,
ref_carrier=ref_carrier(equipment))
power=req.power, spacing=req.spacing, tx_osnr=req.tx_osnr, delta_pdb=req.offset_db)
for i, el in enumerate(path):
if isinstance(el, Roadm):
si = el(si, degree=path[i + 1].uid, from_degree=path[i - 1].uid)
Expand Down Expand Up @@ -398,8 +387,7 @@ def propagate_and_optimize_mode(path, req, equipment):
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,
delta_pdb=this_offset,
tx_osnr=req.tx_osnr, ref_carrier=ref_carrier(equipment))
delta_pdb=this_offset, tx_osnr=req.tx_osnr)
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)
Expand Down
Loading

0 comments on commit 2ccad8f

Please sign in to comment.