Merge pull request #199 from Orange-OpenSource/sys_margins

Sys margins
This commit is contained in:
James
2019-01-28 10:39:19 -05:00
committed by GitHub
11 changed files with 139 additions and 92 deletions

View File

@@ -98,7 +98,8 @@
],
"Roadms":[{
"gain_mode_default_loss": 20,
"power_mode_pout_target": -20
"power_mode_pout_target": -20,
"add_drop_osnr": 38
}],
"SI":[{
"f_min": 191.3e12,
@@ -108,11 +109,8 @@
"power_dbm": 0,
"power_range_db": [0,0,0.5],
"roll_off": 0.15,
"OSNR": 11,
"bit_rate":100e9,
"tx_osnr": 45,
"min_spacing": 37.5e9,
"cost":1
"tx_osnr": 40,
"sys_margins": 0
}],
"Transceiver":[
{
@@ -129,7 +127,7 @@
"OSNR": 11,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 45,
"tx_osnr": 40,
"min_spacing": 37.5e9,
"cost":1
},
@@ -139,7 +137,7 @@
"OSNR": 15,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 45,
"tx_osnr": 40,
"min_spacing": 75e9,
"cost":1
}
@@ -158,7 +156,7 @@
"OSNR": 12,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 45,
"tx_osnr": 40,
"min_spacing": 37.5e9,
"cost":1
},
@@ -168,7 +166,7 @@
"OSNR": 18,
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 45,
"tx_osnr": 40,
"min_spacing": 62.5e9,
"cost":1
},
@@ -178,7 +176,7 @@
"OSNR": 21,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 45,
"tx_osnr": 40,
"min_spacing": 75e9,
"cost":1
},
@@ -188,7 +186,7 @@
"OSNR": 16,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 45,
"tx_osnr": 40,
"min_spacing": 75e9,
"cost":1
}

View File

@@ -121,7 +121,7 @@
"type": "Roadm"
},
{
"uid": "ingress fused spans in Corlay",
"uid": "west fused spans in Corlay",
"metadata": {
"location": {
"city": "Corlay",
@@ -133,7 +133,7 @@
"type": "Fused"
},
{
"uid": "ingress fused spans in Loudeac",
"uid": "west fused spans in Loudeac",
"metadata": {
"location": {
"city": "Loudeac",
@@ -145,7 +145,7 @@
"type": "Fused"
},
{
"uid": "ingress fused spans in Morlaix",
"uid": "west fused spans in Morlaix",
"metadata": {
"location": {
"city": "Morlaix",
@@ -157,7 +157,7 @@
"type": "Fused"
},
{
"uid": "egress fused spans in Corlay",
"uid": "east fused spans in Corlay",
"metadata": {
"location": {
"city": "Corlay",
@@ -169,7 +169,7 @@
"type": "Fused"
},
{
"uid": "egress fused spans in Loudeac",
"uid": "east fused spans in Loudeac",
"metadata": {
"location": {
"city": "Loudeac",
@@ -181,7 +181,7 @@
"type": "Fused"
},
{
"uid": "egress fused spans in Morlaix",
"uid": "east fused spans in Morlaix",
"metadata": {
"location": {
"city": "Morlaix",
@@ -652,34 +652,34 @@
},
{
"from_node": "fiber (Lannion_CAS → Corlay)-F061",
"to_node": "ingress fused spans in Corlay"
"to_node": "west fused spans in Corlay"
},
{
"from_node": "ingress fused spans in Corlay",
"from_node": "west fused spans in Corlay",
"to_node": "fiber (Corlay → Loudeac)-F010"
},
{
"from_node": "fiber (Loudeac → Corlay)-F010",
"to_node": "egress fused spans in Corlay"
"to_node": "east fused spans in Corlay"
},
{
"from_node": "egress fused spans in Corlay",
"from_node": "east fused spans in Corlay",
"to_node": "fiber (Corlay → Lannion_CAS)-F061"
},
{
"from_node": "fiber (Corlay → Loudeac)-F010",
"to_node": "ingress fused spans in Loudeac"
"to_node": "west fused spans in Loudeac"
},
{
"from_node": "ingress fused spans in Loudeac",
"from_node": "west fused spans in Loudeac",
"to_node": "fiber (Loudeac → Lorient_KMA)-F054"
},
{
"from_node": "fiber (Lorient_KMA → Loudeac)-F054",
"to_node": "egress fused spans in Loudeac"
"to_node": "east fused spans in Loudeac"
},
{
"from_node": "egress fused spans in Loudeac",
"from_node": "east fused spans in Loudeac",
"to_node": "fiber (Loudeac → Corlay)-F010"
},
{
@@ -748,18 +748,18 @@
},
{
"from_node": "fiber (Lannion_CAS → Morlaix)-F059",
"to_node": "ingress fused spans in Morlaix"
"to_node": "west fused spans in Morlaix"
},
{
"from_node": "ingress fused spans in Morlaix",
"from_node": "west fused spans in Morlaix",
"to_node": "fiber (Morlaix → Brest_KLA)-F060"
},
{
"from_node": "fiber (Brest_KLA → Morlaix)-F060",
"to_node": "egress fused spans in Morlaix"
"to_node": "east fused spans in Morlaix"
},
{
"from_node": "egress fused spans in Morlaix",
"from_node": "east fused spans in Morlaix",
"to_node": "fiber (Morlaix → Lannion_CAS)-F059"
},
{

View File

@@ -74,14 +74,20 @@ def requests_from_json(json_data,equipment):
# params['power'] is updated
if req['path-constraints']['te-bandwidth']['output-power']:
params['power'] = req['path-constraints']['te-bandwidth']['output-power']
# same process for nb-channel
fmin = params['frequency']['min']
fmax = params['frequency']['max']
f_min = params['f_min']
f_max_from_si = params['f_max']
if req['path-constraints']['te-bandwidth']['max-nb-of-channel'] is not None :
params['nb_channel'] = req['path-constraints']['te-bandwidth']['max-nb-of-channel']
nch = req['path-constraints']['te-bandwidth']['max-nb-of-channel']
params['nb_channel'] = nch
spacing = params['spacing']
params['f_max'] = f_min + nch*spacing
else :
params['nb_channel'] = automatic_nch(fmin,fmax,params['spacing'])
consitency_check(params)
params['nb_channel'] = automatic_nch(f_min,f_max_from_si,params['spacing'])
consistency_check(params, f_max_from_si)
try :
params['path_bandwidth'] = req['path-constraints']['te-bandwidth']['path_bandwidth']
except KeyError:
@@ -89,11 +95,11 @@ def requests_from_json(json_data,equipment):
requests_list.append(Path_request(**params))
return requests_list
def consitency_check(params):
fmin = params['frequency']['min']
fmax = params['frequency']['max']
max_recommanded_nb_channels = automatic_nch(fmin,fmax,
params['spacing'])
def consistency_check(params, f_max_from_si):
f_min = params['f_min']
f_max = params['f_max']
max_recommanded_nb_channels = automatic_nch(f_min,f_max,
params['spacing'])
if params['baud_rate'] is not None:
#implicitely means that a mode is defined with min_spacing
if params['min_spacing']>params['spacing'] :
@@ -103,10 +109,10 @@ def consitency_check(params):
print(msg)
logger.critical(msg)
exit()
if params['nb_channel']>max_recommanded_nb_channels:
if f_max>f_max_from_si:
msg = dedent(f'''
Requested channel number {params["nb_channel"]}, baud rate {params["baud_rate"]} GHz and requested spacing {params["spacing"]*1e-9}GHz
is not consistent with frequency range {fmin*1e-12} THz, {fmax*1e-12} THz, min recommanded spacing {params["min_spacing"]*1e-9}GHz.
is not consistent with frequency range {f_min*1e-12} THz, {f_max*1e-12} THz, min recommanded spacing {params["min_spacing"]*1e-9}GHz.
max recommanded nb of channels is {max_recommanded_nb_channels}
Computation stopped.''')
logger.critical(msg)
@@ -206,7 +212,7 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
logger.warning(msg)
total_path = []
else:
total_path,mode = propagate_and_optimize_mode(total_path,pathreq,equipment, show=False)
total_path,mode = propagate_and_optimize_mode(total_path,pathreq,equipment)
# if no baudrate satisfies spacing, no mode is returned and an empty path is returned
# a warning is shown in the propagate_and_optimize_mode
if mode is not None :

View File

@@ -25,7 +25,7 @@ from collections import namedtuple
from gnpy.core.node import Node
from gnpy.core.units import UNITS
from gnpy.core.utils import lin2db, db2lin, itufs
from gnpy.core.utils import lin2db, db2lin, itufs, snr_sum
class Transceiver(Node):
def __init__(self, *args, **kwargs):
@@ -35,20 +35,47 @@ class Transceiver(Node):
self.osnr_nli = None
self.snr = None
self.passive = False
self.baud_rate = None
def _calc_snr(self, spectral_info):
with errstate(divide='ignore'):
self.osnr_ase = [lin2db(divide(c.power.signal, c.power.ase))
self.baud_rate = [c.baud_rate for c in spectral_info.carriers]
ratio_01nm = [lin2db(12.5e9/b_rate) for b_rate in self.baud_rate]
#set raw values to record original calculation, before update_snr()
self.raw_osnr_ase = [lin2db(divide(c.power.signal, c.power.ase))
for c in spectral_info.carriers]
ratio_01nm = [lin2db(12.5e9/c.baud_rate)
for c in spectral_info.carriers]
self.osnr_ase_01nm = [ase - ratio for ase, ratio
in zip(self.osnr_ase, ratio_01nm)]
self.osnr_nli = [lin2db(divide(c.power.signal, c.power.nli))
self.raw_osnr_ase_01nm = [ase - ratio for ase, ratio
in zip(self.raw_osnr_ase, ratio_01nm)]
self.raw_osnr_nli = [lin2db(divide(c.power.signal, c.power.nli))
for c in spectral_info.carriers]
self.snr = [lin2db(divide(c.power.signal, c.power.nli+c.power.ase))
self.raw_snr = [lin2db(divide(c.power.signal, c.power.nli+c.power.ase))
for c in spectral_info.carriers]
self.osnr_ase = self.raw_osnr_ase
self.osnr_ase_01nm = self.raw_osnr_ase_01nm
self.osnr_nli = self.raw_osnr_nli
self.snr = self.raw_snr
def update_snr(self, *args):
"""
snr_added in 0.1nm
compute SNR penalties such as transponder Tx_osnr or Roadm add_drop_osnr
only applied in request.py / propagate on the last Trasceiver node of the path
all penalties are added in a single call because to avoid uncontrolled cumul
"""
#use raw_values so that the added snr penalties are not cumulated
snr_added = 0
for s in args:
snr_added += db2lin(-s)
snr_added = -lin2db(snr_added)
self.osnr_ase = list(map(lambda x,y:snr_sum(x,y,snr_added),
self.raw_osnr_ase, self.baud_rate))
self.snr = list(map(lambda x,y:snr_sum(x,y,snr_added),
self.raw_snr, self.baud_rate))
self.osnr_ase_01nm = list(map(lambda x:snr_sum(x,12.5e9,snr_added),
self.raw_osnr_ase_01nm))
@property
def to_json(self):
return {'uid' : self.uid,

View File

@@ -25,9 +25,9 @@ Fiber = namedtuple('Fiber', 'type_variety dispersion gamma')
Spans = namedtuple('Spans', 'power_mode delta_power_range_db max_length length_units \
max_loss padding EOL con_in con_out')
Transceiver = namedtuple('Transceiver', 'type_variety frequency mode')
Roadms = namedtuple('Roadms', 'gain_mode_default_loss power_mode_pout_target')
Roadms = namedtuple('Roadms', 'gain_mode_default_loss power_mode_pout_target add_drop_osnr')
SI = namedtuple('SI', 'f_min f_max baud_rate spacing roll_off \
power_dbm power_range_db OSNR bit_rate tx_osnr min_spacing cost')
power_dbm power_range_db tx_osnr sys_margins')
AmpBase = namedtuple(
'AmpBase',
'type_variety type_def gain_flatmax gain_min p_max'
@@ -162,6 +162,8 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
try:
trxs = equipment['Transceiver']
#if called from path_requests_run.py, trx_mode is filled with None when not specified by user
#if called from transmission_main.py, trx_mode is ''
if trx_mode is not None:
mode_params = next(mode for trx in trxs \
if trx == trx_type_variety \
@@ -184,7 +186,8 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
"min_spacing":None,
"cost":None}
trx_params = {**mode_params}
trx_params['frequency'] = equipment['Transceiver'][trx_type_variety].frequency
trx_params['f_min'] = equipment['Transceiver'][trx_type_variety].frequency['min']
trx_params['f_max'] = equipment['Transceiver'][trx_type_variety].frequency['max']
# TODO: novel automatic feature maybe unwanted if spacing is specified
# trx_params['spacing'] = automatic_spacing(trx_params['baud_rate'])
@@ -198,21 +201,18 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
else:
# default transponder charcteristics
# mainly used with transmission_main_example.py
trx_params['frequency'] = {'min': default_si_data.f_min, 'max': default_si_data.f_max}
trx_params['f_min'] = default_si_data.f_min
trx_params['f_max'] = default_si_data.f_max
trx_params['baud_rate'] = default_si_data.baud_rate
trx_params['spacing'] = default_si_data.spacing
trx_params['OSNR'] = default_si_data.OSNR
trx_params['bit_rate'] = default_si_data.bit_rate
trx_params['cost'] = default_si_data.cost
trx_params['OSNR'] = None
trx_params['bit_rate'] = None
trx_params['cost'] = None
trx_params['roll_off'] = default_si_data.roll_off
trx_params['nb_channel'] = automatic_nch(trx_params['frequency']['min'],
trx_params['frequency']['max'],
trx_params['spacing'])
trx_params['tx_osnr'] = default_si_data.tx_osnr
trx_params['min_spacing'] = default_si_data.min_spacing
nch = automatic_nch(trx_params['frequency']['min'],
trx_params['frequency']['max'],
trx_params['spacing'])
trx_params['min_spacing'] = None
nch = automatic_nch(trx_params['f_min'], trx_params['f_max'], trx_params['spacing'])
trx_params['nb_channel'] = nch
print(f'There are {nch} channels propagating')
trx_params['power'] = db2lin(default_si_data.power_dbm)*1e-3
@@ -229,10 +229,20 @@ def automatic_spacing(baud_rate):
def automatic_nch(f_min, f_max, spacing):
return int((f_max - f_min)//spacing)
def automatic_fmax(f_min, spacing, nch):
return f_min + spacing * nch
def load_equipment(filename):
json_data = load_json(filename)
return equipment_from_json(json_data, filename)
def update_trx_osnr(equipment):
"""add sys_margins to all Transceivers OSNR values"""
for trx in equipment['Transceiver'].values():
for m in trx.mode:
m['OSNR'] = m['OSNR'] + equipment['SI']['default'].sys_margins
return equipment
def equipment_from_json(json_data, filename):
"""build global dictionnary eqpt_library that stores all eqpt characteristics:
edfa type type_variety, fiber type_variety
@@ -257,4 +267,5 @@ def equipment_from_json(json_data, filename):
equipment[key][subkey] = Amp.from_default_json(config, **entry)
else:
equipment[key][subkey] = typ(**entry)
equipment = update_trx_osnr(equipment)
return equipment

View File

@@ -14,6 +14,7 @@ from numpy import array
from gnpy.core.utils import lin2db, db2lin
from json import loads
from gnpy.core.utils import load_json
from gnpy.core.equipment import automatic_nch, automatic_spacing
class ConvenienceAccess:
@@ -56,18 +57,17 @@ def merge_input_spectral_information(*si):
#TODO
pass
def create_input_spectral_information(f_min, roll_off, baud_rate, power, spacing, nb_channel, tx_osnr):
def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing):
# pref in dB : convert power lin into power in dB
pref = lin2db(power * 1e3)
ase_power = (power / db2lin(tx_osnr)) * (baud_rate / 12.5e9)
si = SpectralInformation(pref=Pref(pref, pref))
nb_channel = automatic_nch(f_min, f_max, spacing)
si = si.update(carriers=[
Channel(f, (f_min+spacing*f),
baud_rate, roll_off, Power(power, 0, ase_power)) for f in range(1,nb_channel+1)
baud_rate, roll_off, Power(power, 0, 0)) for f in range(1,nb_channel+1)
])
return si
if __name__ == '__main__':
pref = lin2db(power * 1e3)
si = SpectralInformation(

View File

@@ -34,7 +34,7 @@ logger = getLogger(__name__)
RequestParams = namedtuple('RequestParams','request_id source destination trx_type'+
' trx_mode nodes_list loose_list spacing power nb_channel frequency format baud_rate OSNR bit_rate roll_off tx_osnr min_spacing cost path_bandwidth')
' 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')
DisjunctionParams = namedtuple('DisjunctionParams','disjunction_id relaxable link_diverse node_diverse disjunctions_req')
class Path_request:
@@ -51,7 +51,8 @@ class Path_request:
self.spacing = params.spacing
self.power = params.power
self.nb_channel = params.nb_channel
self.frequency = params.frequency
self.f_min = params.f_min
self.f_max = params.f_max
self.format = params.format
self.OSNR = params.OSNR
self.bit_rate = params.bit_rate
@@ -388,23 +389,23 @@ def propagate(path, req, equipment, show=False):
#update roadm loss in case of power sweep (power mode only)
set_roadm_loss(path, equipment, lin2db(req.power*1e3))
si = create_input_spectral_information(
req.frequency['min'], req.roll_off, req.baud_rate,
req.power, req.spacing, req.nb_channel, req.tx_osnr)
req.f_min, req.f_max, req.roll_off, req.baud_rate,
req.power, req.spacing)
for el in path:
si = el(si)
if show :
print(el)
path[-1].update_snr(req.tx_osnr, equipment['Roadms']['default'].add_drop_osnr)
return path
def propagate_and_optimize_mode(path, req, equipment, show=False):
def propagate_and_optimize_mode(path, req, equipment):
#update roadm loss in case of power sweep (power mode only)
set_roadm_loss(path, equipment, lin2db(req.power*1e3))
# if mode is unknown : loops on the modes starting from the highest baudrate fiting in the
# spacing. TODO add a min_spacing attribute in transceivers. for now just using baudrate*1.1
# step 1: create an ordered list of modes based on baudrate
baudrate_to_explore = list(set([m['baud_rate'] for m in equipment['Transceiver'][req.tsp].mode
if float(m['min_spacing'])<= req.spacing]))
# TODO be carefull on limits cases if min_spacing very close to req spacing eg 50.001 50.000
# TODO be carefull on limits cases if spacing very close to req spacing eg 50.001 50.000
baudrate_to_explore = sorted(baudrate_to_explore, reverse=True)
if baudrate_to_explore :
# at least 1 baudrate can be tested wrt spacing
@@ -418,15 +419,14 @@ def propagate_and_optimize_mode(path, req, equipment, show=False):
found_a_feasible_mode = False
# TODO : the case of roll of is not included: for now use SI one
# TODO : if the loop in mode optimization does not have a feasible path, then bugs
si = create_input_spectral_information(
req.f_min, req.f_max, equipment['SI']['default'].roll_off,
b, req.power, req.spacing)
for el in path:
si = el(si)
for m in modes_to_explore :
si = create_input_spectral_information(
req.frequency['min'], equipment['SI']['default'].roll_off,
b, req.power, req.spacing, req.nb_channel, m['tx_osnr'])
for el in path:
si = el(si)
if show :
print(el)
if path[-1].snr is not None:
path[-1].update_snr(m['tx_osnr'], equipment['Roadms']['default'].add_drop_osnr)
if round(min(path[-1].snr+lin2db(b/(12.5e9))),2) > m['OSNR'] :
found_a_feasible_mode = True
return path, m
@@ -869,7 +869,8 @@ def compare_reqs(req1,req2,disjlist) :
req1.spacing == req2.spacing and \
req1.power == req2.power and \
req1.nb_channel == req2.nb_channel and \
req1.frequency == req2.frequency and \
req1.f_min == req2.f_min and \
req1.f_max == req2.f_max and \
req1.format == req2.format and \
req1.OSNR == req2.OSNR and \
req1.roll_off == req2.roll_off and \

View File

@@ -120,6 +120,10 @@ def freq2wavelength(value):
"""
return c() / value
def snr_sum(snr, bw, snr_added, bw_added=12.5e9):
snr_added = snr_added - lin2db(bw/bw_added)
snr = -lin2db(db2lin(-snr)+db2lin(-snr_added))
return snr
def deltawl2deltaf(delta_wl, wavelength):
""" deltawl2deltaf(delta_wl, wavelength):

View File

@@ -66,7 +66,8 @@
],
"Roadms":[{
"gain_mode_default_loss": 20,
"power_mode_pout_target": -20
"power_mode_pout_target": -20,
"add_drop_osnr": 100
}],
"SI":[{
"f_min": 191.3e12,
@@ -76,11 +77,8 @@
"power_dbm": 0,
"power_range_db": [0,0.5,0.5],
"roll_off": 0.15,
"OSNR": 15,
"bit_rate":100e9,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost":1
"sys_margins": 0
}],
"Transceiver":[
{

View File

@@ -9,7 +9,7 @@ from json import load
from gnpy.core.elements import Transceiver, Fiber, Edfa
from gnpy.core.utils import lin2db, db2lin
from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power, Pref
from gnpy.core.equipment import load_equipment
from gnpy.core.equipment import load_equipment, automatic_fmax
from gnpy.core.network import build_network, load_network, set_roadm_loss
from pathlib import Path
import pytest
@@ -66,7 +66,9 @@ def setup_trx():
def si(nch_and_spacing, bw):
"""parametrize a channel comb with nb_channel, spacing and signal bw"""
nb_channel, spacing = nch_and_spacing
return create_input_spectral_information(191.3e12, 0.15, bw, 1e-3, spacing, nb_channel, 100)
f_min = 191.3e12
f_max = automatic_fmax(f_min, spacing, nb_channel)
return create_input_spectral_information(f_min, f_max, 0.15, bw, 1e-3, spacing)
@pytest.mark.parametrize("gain, nf_expected", [(10, 15), (15, 10), (25, 5.8)])
def test_variable_gain_nf(gain, nf_expected, setup_edfa_variable_gain, si):

View File

@@ -74,7 +74,7 @@ def test_automaticmodefeature(net,eqpt,serv,expected_mode):
path_res_list.append(pathreq.format)
total_path = propagate(total_path,pathreq,equipment, show=False)
else:
total_path,mode = propagate_and_optimize_mode(total_path,pathreq,equipment, show=False)
total_path,mode = propagate_and_optimize_mode(total_path,pathreq,equipment)
# if no baudrate satisfies spacing, no mode is returned and an empty path is returned
# a warning is shown in the propagate_and_optimize_mode
if mode is not None :