Skip to content

Commit

Permalink
Feat: add detailed ROADM impairments per roadm-path
Browse files Browse the repository at this point in the history
Signed-off-by: EstherLerouzic <[email protected]>
Change-Id: I09c55dcff53ffb264609654cde0f1d8b9dc7fe9b
  • Loading branch information
EstherLerouzic committed Jun 2, 2024
1 parent fb4195c commit f950a6a
Show file tree
Hide file tree
Showing 12 changed files with 483 additions and 12 deletions.
80 changes: 76 additions & 4 deletions gnpy/core/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@

from gnpy.core.utils import lin2db, db2lin, arrange_frequencies, snr_sum, per_label_average, pretty_summary_print, \
watt2dbm, psd2powerdbm
from gnpy.core.parameters import RoadmParams, FusedParams, FiberParams, PumpParams, EdfaParams, EdfaOperational
from gnpy.core.parameters import RoadmParams, FusedParams, FiberParams, PumpParams, EdfaParams, EdfaOperational, \
RoadmPath, RoadmImpairment
from gnpy.core.science_utils import NliSolver, RamanSolver
from gnpy.core.info import SpectralInformation, ReferenceCarrier
from gnpy.core.info import SpectralInformation
from gnpy.core.exceptions import NetworkTopologyError, SpectrumError, ParametersError


Expand Down Expand Up @@ -259,6 +260,17 @@ def __init__(self, *args, params=None, **kwargs):
self.per_degree_pch_psw = self.params.per_degree_pch_psw
self.ref_pch_in_dbm = {}
self.ref_carrier = None
# Define the nature of from-to internal connection: express-path, drop-path, add-path
# roadm_paths contains a list of RoadmPath object for each path crossing the ROADM
self.roadm_paths = []
# roadm_path_impairments contains a dictionnary of impairments profiles corresponding to type_variety
# first listed add, drop an express constitute the default
self.roadm_path_impairments = self.params.roadm_path_impairments
# per degree definitions, in case some degrees have particular deviations with respect to default.
self.per_degree_impairments = {f'{i["from_degree"]}-{i["to_degree"]}': {"from_degree": i["from_degree"],
"to_degree": i["to_degree"],
"impairment_id": i["impairment_id"]}
for i in self.params.per_degree_impairments}

@property
def to_json(self):
Expand Down Expand Up @@ -289,6 +301,9 @@ def to_json(self):
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
if self.per_degree_impairments:
to_json['per_degree_impairments'] = list(self.per_degree_impairments.values())

return to_json

def __repr__(self):
Expand Down Expand Up @@ -404,11 +419,68 @@ def propagate(self, spectral_info, degree, from_degree):
delta_power = watt2dbm(input_power) - new_target

spectral_info.apply_attenuation_db(delta_power)
spectral_info.pmd = sqrt(spectral_info.pmd ** 2 + self.params.pmd ** 2)
spectral_info.pdl = sqrt(spectral_info.pdl ** 2 + self.params.pdl ** 2)
spectral_info.pmd = sqrt(spectral_info.pmd ** 2
+ self.get_roadm_path(from_degree=from_degree, to_degree=degree).impairment.pmd ** 2)
spectral_info.pdl = sqrt(spectral_info.pdl ** 2
+ self.get_roadm_path(from_degree=from_degree, to_degree=degree).impairment.pdl ** 2)
self.pch_out_dbm = watt2dbm(spectral_info.signal + spectral_info.nli + spectral_info.ase)
self.propagated_labels = spectral_info.label

def set_roadm_paths(self, from_degree, to_degree, path_type, impairment_id=None):
"""set internal path type: express, drop or add with corresponding impairment
If no impairment id is defined, then use the first profile that matches the path_type in the
profile dictionnary.
"""
# initialize impairment with params.pmd, params.cd
# if more detailed parameters are available for the Roadm, the use them instead
roadm_global_impairment = {'roadm-pmd': self.params.pmd,
'roadm-pdl': self.params.pdl}
if path_type in ['add', 'drop']:
# without detailed imparments, we assume that add OSNR contribution is the same as drop contribution
# add_drop_osnr_db = - 10log10(1/add_osnr + 1/drop_osnr) with add_osnr = drop_osnr
# = add_osnr_db + 10log10(2)
roadm_global_impairment['roadm-osnr'] = self.params.add_drop_osnr + lin2db(2)
impairment = RoadmImpairment(roadm_global_impairment)

if impairment_id is None:
# get the first item in the type variety that matches the path_type
for path_impairment_id, path_impairment in self.roadm_path_impairments.items():
if path_impairment.path_type == path_type:
impairment = path_impairment
impairment_id = path_impairment_id
break
# at this point, path_type is not part of roadm_path_impairment, impairment and impairment_id are None
else:
if impairment_id in self.roadm_path_impairments:
impairment = self.roadm_path_impairments[impairment_id]
else:
msg = f'ROADM {self.uid}: impairment profile id {impairment_id} is not defined in library'
raise NetworkTopologyError(msg)
# print(from_degree, to_degree, path_type)
self.roadm_paths.append(RoadmPath(from_degree=from_degree, to_degree=to_degree, path_type=path_type,
impairment_id=impairment_id, impairment=impairment))

def get_roadm_path(self, from_degree, to_degree):
"""Get internal path type impairment"""
for roadm_path in self.roadm_paths:
if roadm_path.from_degree == from_degree and roadm_path.to_degree == to_degree:
return roadm_path
msg = f'Could not find from_degree-to_degree {from_degree}-{to_degree} path in ROADM {self.uid}'
raise NetworkTopologyError(msg)

def get_per_degree_impairment_id(self, from_degree, to_degree):
"""returns the id of the impairment if the degrees are in the per_degree tab"""
if f'{from_degree}-{to_degree}' in self.per_degree_impairments.keys():
return self.per_degree_impairments[f'{from_degree}-{to_degree}']["impairment_id"]
return None

def get_path_type_per_id(self, impairment_id):
"""returns the path_type of the impairment if the is is defined"""
if impairment_id in self.roadm_path_impairments.keys():
return self.roadm_path_impairments[impairment_id].path_type
return None

def __call__(self, spectral_info, degree, from_degree):
self.propagate(spectral_info, degree=degree, from_degree=from_degree)
return spectral_info
Expand Down
57 changes: 57 additions & 0 deletions gnpy/core/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,62 @@ def set_fiber_input_power(network, fiber, equipment, pref_ch_db):
fiber.ref_pch_in_dbm = pref_ch_db - loss


def set_roadm_internal_paths(roadm, network):
"""Set ROADM path types (express, add, drop)
Uses implicit guess if no information is set in ROADM
"""
next_oms = [n.uid for n in network.successors(roadm) if not isinstance(n, elements.Transceiver)]
previous_oms = [n.uid for n in network.predecessors(roadm) if not isinstance(n, elements.Transceiver)]
drop_port = [n.uid for n in network.successors(roadm) if isinstance(n, elements.Transceiver)]
add_port = [n.uid for n in network.predecessors(roadm) if isinstance(n, elements.Transceiver)]

default_express = 'express'
default_add = 'add'
default_drop = 'drop'
# take user defined element impairment id if it exists
correct_from_degrees = []
correct_add = []
correct_to_degrees = []
correct_drop = []
for from_degree in previous_oms:
correct_from_degrees.append(from_degree)
for to_degree in next_oms:
correct_to_degrees.append(to_degree)
impairment_id = roadm.get_per_degree_impairment_id(from_degree, to_degree)
roadm.set_roadm_paths(from_degree=from_degree, to_degree=to_degree, path_type=default_express,
impairment_id=impairment_id)
for drop in drop_port:
correct_drop.append(drop)
impairment_id = roadm.get_per_degree_impairment_id(from_degree, drop)
path_type = roadm.get_path_type_per_id(impairment_id)
# a degree connected to a transceiver MUST be add or drop
# but a degree connected to something else could be an express, add or drop
# (for example case of external shelves)
if path_type and path_type != 'drop':
msg = f'Roadm {roadm.uid} path_type is defined as {path_type} but it should be drop'
raise NetworkTopologyError(msg)
roadm.set_roadm_paths(from_degree=from_degree, to_degree=drop, path_type=default_drop,
impairment_id=impairment_id)
for to_degree in next_oms:
for add in add_port:
correct_add.append(add)
impairment_id = roadm.get_per_degree_impairment_id(add, to_degree)
path_type = roadm.get_path_type_per_id(impairment_id)
if path_type and path_type != 'add':
msg = f'Roadm {roadm.uid} path_type is defined as {path_type} but it should be add'
raise NetworkTopologyError(msg)
roadm.set_roadm_paths(from_degree=add, to_degree=to_degree, path_type=default_add,
impairment_id=impairment_id)
# sanity check: raise an error if per_degree from or to degrees are not in the correct list
# raise an error if user defined path_type is not consistent with inferred path_type:
for item in roadm.per_degree_impairments.values():
if item['from_degree'] not in correct_from_degrees + correct_add or \
item['to_degree'] not in correct_to_degrees + correct_drop:
msg = f'Roadm {roadm.uid} has wrong from-to degree uid {item["from_degree"]} - {item["to_degree"]}'
raise NetworkTopologyError(msg)


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)
Expand Down Expand Up @@ -777,6 +833,7 @@ def build_network(network, equipment, pref_ch_db, pref_total_db, set_connector_l
set_egress_amplifier(network, roadm, equipment, pref_ch_db, pref_total_db, verbose)
for roadm in roadms:
set_roadm_input_powers(network, roadm, equipment, pref_ch_db)
set_roadm_internal_paths(roadm, network)
for fiber in [f for f in network.nodes() if isinstance(f, (elements.Fiber, elements.RamanFiber))]:
set_fiber_input_power(network, fiber, equipment, pref_ch_db)

Expand Down
60 changes: 60 additions & 0 deletions gnpy/core/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,68 @@ def __init__(self, **kwargs):
self.pmd = kwargs['pmd']
self.pdl = kwargs['pdl']
self.restrictions = kwargs['restrictions']
self.roadm_path_impairments = self.get_roadm_path_impairments(kwargs['roadm-path-impairments'])
except KeyError as e:
raise ParametersError(f'ROADM configurations must include {e}. Configuration: {kwargs}')
self.per_degree_impairments = kwargs.get('per_degree_impairments', [])

def get_roadm_path_impairments(self, path_impairments_list):
"""Get the ROADM list of profiles for impairments definition
transform the ietf model into gnpy internal model: add a path-type in the attributes
"""
if not path_impairments_list:
return {}
authorized_path_types = {
'roadm-express-path': 'express',
'roadm-add-path': 'add',
'roadm-drop-path': 'drop',
}
roadm_path_impairments = {}
for path_impairment in path_impairments_list:
index = path_impairment['roadm-path-impairments-id']
path_type = next(key for key in path_impairment if key in authorized_path_types.keys())
impairment_dict = dict({'path-type': authorized_path_types[path_type]}, **path_impairment[path_type])
roadm_path_impairments[index] = RoadmImpairment(impairment_dict)
return roadm_path_impairments


class RoadmPath:
def __init__(self, from_degree, to_degree, path_type, impairment_id=None, impairment=None):
"""Records roadm internal paths, types and impairment
path_type must be in "express", "add", "drop"
impairment_id must be one of the id detailed in equipement
"""
self.from_degree = from_degree
self.to_degree = to_degree
self.path_type = path_type
self.impairment_id = impairment_id
self.impairment = impairment


class RoadmImpairment:
"""Generic definition of impairments for express, add and drop"""
def __init__(self, params):
"""Records roadm internal paths and types"""
self.path_type = params.get('path-type')
self.pmd = params.get('roadm-pmd')
self.cd = params.get('roadm-cd')
self.pdl = params.get('roadm-pdl')
self.inband_crosstalk = params.get('roadm-inband-crosstalk')
self.maxloss = params.get('roadm-maxloss', 0)
if params.get('frequency-range') is not None:
self.fmin = params.get('frequency-range')['lower-frequency']
self.fmax = params.get('frequency-range')['upper-frequency']
else:
self.fmin, self.fmax = None, None
self.osnr = params.get('roadm-osnr', None)
self.pmax = params.get('roadm-pmax', None)
self.nf = params.get('roadm-noise-figure', None)
self.minloss = params.get('minloss', None)
self.typloss = params.get('typloss', None)
self.pmin = params.get('pmin', None)
self.ptyp = params.get('ptyp', None)


class FusedParams(Parameters):
Expand Down
65 changes: 64 additions & 1 deletion gnpy/example-data/eqpt_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,70 @@
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
},
"roadm-path-impairments": []
}, {
"type_variety": "detailed_impairments",
"target_pch_out_db": -20,
"add_drop_osnr": 38,
"pmd": 0,
"pdl": 0,
"restrictions": {
"preamp_variety_list":[],
"booster_variety_list":[]
},
"roadm-path-impairments": [
{
"roadm-path-impairments-id": 0,
"roadm-express-path": {
"frequency-range": {
"lower-frequency": 191.3e12,
"upper-frequency": 196.1e12
},
"roadm-pmd": 0,
"roadm-cd": 0,
"roadm-pdl": 0,
"roadm-inband-crosstalk": 0,
"roadm-maxloss": 16.5
}
}, {
"roadm-path-impairments-id": 1,
"roadm-add-path": {
"frequency-range": {
"lower-frequency": 191.3e12,
"upper-frequency": 196.1e12
},
"roadm-pmd": 0,
"roadm-cd": 0,
"roadm-pdl": 0,
"roadm-inband-crosstalk": 0,
"roadm-maxloss": 11.5,
"roadm-pmax": 2.5,
"roadm-osnr": 41,
"roadm-noise-figure": 23
}
}, {
"roadm-path-impairments-id": 2,
"roadm-drop-path": {
"frequency-range": {
"lower-frequency": 191.3e12,
"upper-frequency": 196.1e12
},
"roadm-pmd": 0,
"roadm-cd": 0,
"roadm-pdl": 0,
"roadm-inband-crosstalk": 0,
"roadm-maxloss": 11.5,
"roadm-minloss": 7.5,
"roadm-typloss": 10,
"roadm-pmin": -13.5,
"roadm-pmax": -9.5,
"roadm-ptyp": -12,
"roadm-osnr": 41,
"roadm-noise-figure": 15
}
}
]
}
],
"SI": [{
Expand Down
3 changes: 2 additions & 1 deletion gnpy/tools/json_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ class Roadm(_JsonThing):
'restrictions': {
'preamp_variety_list': [],
'booster_variety_list': []
}
},
'roadm-path-impairments': []
}

def __init__(self, **kwargs):
Expand Down
Loading

0 comments on commit f950a6a

Please sign in to comment.