Calculate CD and PMD penalty

The penalties are calculated and presented separately from the GSNR.

They are also taken into account when optimizing trx mode and verifying
path feasibility in path_requests_run processing.

Penalties are specified in the eqpt_config file as part of trx modes.
This patch includes specifications for OpenROADM trx modes.

Penalties are defined by a list of
impairment_value/penalty_value pairs, for example:

"penalties": [
    {
        "chromatic_dispersion": 4e3,
        "penalty_value": 0
    },
    {
        "chromatic_dispersion": 18e3,
        "penalty_value": 0.5
    },
    {
        "pmd": 10,
        "penalty_value": 0
    },
    {
        "pmd": 30,
        "penalty_value": 0.5
    }
]

- Between given pairs, penalty is linearly interpolated.
- Below min and above max up_to_boundary, transmission is considered
  not feasible.

This is in line with how penalties are specified in OpenROADM and
compatible with specifications from most other organizations and
vendors.

The implementation makes it easy to add other penalties (PDL, etc.) in
the future.

The input format is flexible such that it can easily be extended to
accept combined penalty entries (e.g. CD and PMD) in the future.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I3745eba48ca60c0e4c904839a99b59104eae9216
This commit is contained in:
Jonas Mårtensson
2021-11-11 13:02:46 +01:00
committed by Jan Kundrát
parent 82b148eb87
commit 587932290d
8 changed files with 315 additions and 22 deletions

View File

@@ -83,6 +83,8 @@ class Transceiver(_Node):
self.baud_rate = None
self.chromatic_dispersion = None
self.pmd = None
self.penalties = {}
self.total_penalty = 0
def _calc_cd(self, spectral_info):
""" Updates the Transceiver property with the CD of the received channels. CD in ps/nm.
@@ -94,6 +96,18 @@ class Transceiver(_Node):
"""
self.pmd = [carrier.pmd*1e12 for carrier in spectral_info.carriers]
def _calc_penalty(self, impairment_value, boundary_list):
return interp(impairment_value, boundary_list['up_to_boundary'], boundary_list['penalty_value'],
left=float('inf'), right=float('inf'))
def calc_penalties(self, penalties):
"""Updates the Transceiver property with penalties (CD, PMD, etc.) of the received channels in dB.
Penalties are linearly interpolated between given points and set to 'inf' outside interval.
"""
self.penalties = {impairment: self._calc_penalty(getattr(self, impairment), boundary_list)
for impairment, boundary_list in penalties.items()}
self.total_penalty = sum(list(self.penalties.values()), axis=0)
def _calc_snr(self, spectral_info):
with errstate(divide='ignore'):
self.baud_rate = [c.baud_rate for c in spectral_info.carriers]
@@ -154,7 +168,8 @@ class Transceiver(_Node):
f'osnr_nli={self.osnr_nli!r}, '
f'snr={self.snr!r}, '
f'chromatic_dispersion={self.chromatic_dispersion!r}, '
f'pmd={self.pmd!r})')
f'pmd={self.pmd!r}, '
f'penalties={self.penalties!r})')
def __str__(self):
if self.snr is None or self.osnr_ase is None:
@@ -167,7 +182,7 @@ class Transceiver(_Node):
cd = mean(self.chromatic_dispersion)
pmd = mean(self.pmd)
return '\n'.join([f'{type(self).__name__} {self.uid}',
result = '\n'.join([f'{type(self).__name__} {self.uid}',
f' GSNR (0.1nm, dB): {snr_01nm:.2f}',
f' GSNR (signal bw, dB): {snr:.2f}',
@@ -176,6 +191,15 @@ class Transceiver(_Node):
f' CD (ps/nm): {cd:.2f}',
f' PMD (ps): {pmd:.2f}'])
cd_penalty = self.penalties.get('chromatic_dispersion')
if cd_penalty is not None:
result += f'\n CD penalty (dB): {mean(cd_penalty):.2f}'
pmd_penalty = self.penalties.get('pmd')
if pmd_penalty is not None:
result += f'\n PMD penalty (dB): {mean(pmd_penalty):.2f}'
return result
def __call__(self, spectral_info):
self._calc_snr(spectral_info)
self._calc_cd(spectral_info)

View File

@@ -35,6 +35,7 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
mode_params = {"format": "undetermined",
"baud_rate": None,
"OSNR": None,
"penalties": None,
"bit_rate": None,
"roll_off": None,
"tx_osnr": None,
@@ -59,6 +60,7 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
trx_params['baud_rate'] = default_si_data.baud_rate
trx_params['spacing'] = default_si_data.spacing
trx_params['OSNR'] = None
trx_params['penalties'] = {}
trx_params['bit_rate'] = None
trx_params['cost'] = None
trx_params['roll_off'] = default_si_data.roll_off

View File

@@ -117,6 +117,24 @@
"bit_rate": 100e9,
"roll_off": null,
"tx_osnr": 33,
"penalties": [
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 18e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 30,
"penalty_value": 0.5
}
],
"min_spacing": 50e9,
"cost": 1
},
@@ -127,6 +145,28 @@
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 35,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 40e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 30,
"penalty_value": 0.5
}
],
"min_spacing": 50e9,
"cost": 1
},
@@ -137,6 +177,28 @@
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 24e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 25,
"penalty_value": 0.5
}
],
"min_spacing": 87.5e9,
"cost": 1
},
@@ -147,6 +209,28 @@
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 18e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 25,
"penalty_value": 0.5
}
],
"min_spacing": 87.5e9,
"cost": 1
},
@@ -157,6 +241,28 @@
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 12e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 20,
"penalty_value": 0.5
}
],
"min_spacing": 87.5e9,
"cost": 1
}

View File

@@ -127,6 +127,24 @@
"bit_rate": 100e9,
"roll_off": null,
"tx_osnr": 33,
"penalties": [
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 18e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 30,
"penalty_value": 0.5
}
],
"min_spacing": 50e9,
"cost": 1
},
@@ -137,6 +155,28 @@
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 48e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 30,
"penalty_value": 0.5
}
],
"min_spacing": 50e9,
"cost": 1
},
@@ -147,6 +187,28 @@
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 24e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 30,
"penalty_value": 0.5
}
],
"min_spacing": 50e9,
"cost": 1
},
@@ -157,6 +219,28 @@
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 24e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 25,
"penalty_value": 0.5
}
],
"min_spacing": 87.5e9,
"cost": 1
},
@@ -167,6 +251,28 @@
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 18e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 25,
"penalty_value": 0.5
}
],
"min_spacing": 87.5e9,
"cost": 1
},
@@ -177,6 +283,28 @@
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 12e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 20,
"penalty_value": 0.5
}
],
"min_spacing": 87.5e9,
"cost": 1
}

View File

@@ -113,6 +113,26 @@ class Transceiver(_JsonThing):
def __init__(self, **kwargs):
self.update_attr(self.default_values, kwargs, 'Transceiver')
for mode_params in self.mode:
penalties = mode_params.get('penalties')
mode_params['penalties'] = {}
if not penalties:
continue
for impairment in ('chromatic_dispersion', 'pmd', 'pdl'):
imp_penalties = [p for p in penalties if impairment in p]
if not imp_penalties:
continue
if all(p[impairment] > 0 for p in imp_penalties):
# make sure the list of penalty values include a proper lower boundary
# (we assume 0 penalty for 0 impairment)
imp_penalties.insert(0, {impairment: 0, 'penalty_value': 0})
# make sure the list of penalty values are sorted by impairment value
imp_penalties.sort(key=lambda i: i[impairment])
# rearrange as dict of lists instead of list of dicts
mode_params['penalties'][impairment] = {
'up_to_boundary': [p[impairment] for p in imp_penalties],
'penalty_value': [p['penalty_value'] for p in imp_penalties]
}
class Fiber(_JsonThing):

View File

@@ -20,7 +20,7 @@ from logging import getLogger
from networkx import (dijkstra_path, NetworkXNoPath,
all_simple_paths, shortest_simple_paths)
from networkx.utils import pairwise
from numpy import mean
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
@@ -32,12 +32,12 @@ from math import ceil
LOGGER = getLogger(__name__)
RequestParams = namedtuple('RequestParams', 'request_id source destination bidir trx_type' +
' trx_mode nodes_list loose_list spacing power nb_channel f_min' +
' f_max format baud_rate OSNR bit_rate roll_off tx_osnr' +
' min_spacing cost path_bandwidth effective_freq_slot')
DisjunctionParams = namedtuple('DisjunctionParams', 'disjunction_id relaxable link' +
'_diverse node_diverse disjunctions_req')
RequestParams = namedtuple('RequestParams', 'request_id source destination bidir trx_type'
' trx_mode nodes_list loose_list spacing power nb_channel f_min'
' f_max format baud_rate OSNR penalties bit_rate'
' roll_off tx_osnr min_spacing cost path_bandwidth effective_freq_slot')
DisjunctionParams = namedtuple('DisjunctionParams', 'disjunction_id relaxable link_diverse'
' node_diverse disjunctions_req')
class PathRequest:
@@ -62,6 +62,7 @@ class PathRequest:
self.f_max = params.f_max
self.format = params.format
self.OSNR = params.OSNR
self.penalties = params.penalties
self.bit_rate = params.bit_rate
self.roll_off = params.roll_off
self.tx_osnr = params.tx_osnr
@@ -348,10 +349,12 @@ def propagate(path, req, equipment):
else:
si = el(si)
path[0].update_snr(req.tx_osnr)
path[0].calc_penalties(req.penalties)
if any(isinstance(el, Roadm) for el in path):
path[-1].update_snr(req.tx_osnr, equipment['Roadm']['default'].add_drop_osnr)
else:
path[-1].update_snr(req.tx_osnr)
path[-1].calc_penalties(req.penalties)
return si
@@ -386,11 +389,13 @@ def propagate_and_optimize_mode(path, req, equipment):
for this_mode in modes_to_explore:
if path[-1].snr is not None:
path[0].update_snr(this_mode['tx_osnr'])
path[0].calc_penalties(this_mode['penalties'])
if any(isinstance(el, Roadm) for el in path):
path[-1].update_snr(this_mode['tx_osnr'], equipment['Roadm']['default'].add_drop_osnr)
else:
path[-1].update_snr(this_mode['tx_osnr'])
if round(min(path[-1].snr + lin2db(this_br / (12.5e9))), 2) \
path[-1].calc_penalties(this_mode['penalties'])
if round(min(path[-1].snr_01nm - path[-1].total_penalty), 2) \
> this_mode['OSNR'] + equipment['SI']['default'].sys_margins:
return path, this_mode
else:
@@ -1107,12 +1112,16 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
# means that at this point the mode was entered/forced by user and thus a
# baud_rate was defined
propagate(total_path, pathreq, equipment)
temp_snr01nm = round(mean(total_path[-1].snr+lin2db(pathreq.baud_rate/(12.5e9))), 2)
if temp_snr01nm < pathreq.OSNR + equipment['SI']['default'].sys_margins:
snr01nm_with_penalty = total_path[-1].snr_01nm - total_path[-1].total_penalty
min_ind = argmin(snr01nm_with_penalty)
if round(snr01nm_with_penalty[min_ind], 2) < pathreq.OSNR + equipment['SI']['default'].sys_margins:
msg = f'\tWarning! Request {pathreq.request_id} computed path from' +\
f' {pathreq.source} to {pathreq.destination} does not pass with' +\
f' {pathreq.tsp_mode}\n\tcomputedSNR in 0.1nm = {temp_snr01nm} ' +\
f'- required osnr {pathreq.OSNR} + {equipment["SI"]["default"].sys_margins} margin'
f' {pathreq.source} to {pathreq.destination} does not pass with {pathreq.tsp_mode}' +\
f'\n\tcomputed SNR in 0.1nm = {round(total_path[-1].snr_01nm[min_ind], 2)}' +\
f'\n\tCD penalty = {round(total_path[-1].penalties["chromatic_dispersion"][min_ind], 2)}' +\
f'\n\tPMD penalty = {round(total_path[-1].penalties["pmd"][min_ind], 2)}' +\
f'\n\trequired osnr = {pathreq.OSNR}' +\
f'\n\tsystem margin = {equipment["SI"]["default"].sys_margins}'
print(msg)
LOGGER.warning(msg)
pathreq.blocking_reason = 'MODE_NOT_FEASIBLE'
@@ -1152,14 +1161,16 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
print(f'\tPath (roadsm) {[r.uid for r in rev_p if isinstance(r,Roadm)]}\n')
propagate(rev_p, pathreq, equipment)
propagated_reversed_path = rev_p
temp_snr01nm = round(mean(propagated_reversed_path[-1].snr +\
lin2db(pathreq.baud_rate/(12.5e9))), 2)
if temp_snr01nm < pathreq.OSNR + equipment['SI']['default'].sys_margins:
snr01nm_with_penalty = rev_p[-1].snr_01nm - rev_p[-1].total_penalty
min_ind = argmin(snr01nm_with_penalty)
if round(snr01nm_with_penalty[min_ind], 2) < pathreq.OSNR + equipment['SI']['default'].sys_margins:
msg = f'\tWarning! Request {pathreq.request_id} computed path from' +\
f' {pathreq.source} to {pathreq.destination} does not pass with' +\
f' {pathreq.tsp_mode}\n' +\
f'\tcomputedSNR in 0.1nm = {temp_snr01nm} -' \
f' required osnr {pathreq.OSNR} + {equipment["SI"]["default"].sys_margins} margin'
f' {pathreq.source} to {pathreq.destination} does not pass with {pathreq.tsp_mode}' +\
f'\n\tcomputed SNR in 0.1nm = {round(rev_p[-1].snr_01nm[min_ind], 2)}' +\
f'\n\tCD penalty = {round(rev_p[-1].penalties["chromatic_dispersion"][min_ind], 2)}' +\
f'\n\tPMD penalty = {round(rev_p[-1].penalties["pmd"][min_ind], 2)}' +\
f'\n\trequired osnr = {pathreq.OSNR}' +\
f'\n\tsystem margin = {equipment["SI"]["default"].sys_margins}'
print(msg)
LOGGER.warning(msg)
# TODO selection of mode should also be on reversed direction !!

View File

@@ -358,6 +358,7 @@ def test_excel_ila_constraints(source, destination, route_list, hoptype, expecte
'cost': None,
'roll_off': 0,
'tx_osnr': 0,
'penalties': None,
'min_spacing': None,
'nb_channel': 0,
'power': 0,

View File

@@ -291,6 +291,7 @@ def request_set():
'cost': 1,
'roll_off': 0.15,
'tx_osnr': 38,
'penalties': {},
'min_spacing': 37.5e9,
'nb_channel': None,
'power': 0,