Skip to content

Commit

Permalink
Restrictions on auto-adding amplifiers into ROADMs
Browse files Browse the repository at this point in the history
This feature is intended to support designs such as OpenROADM where the
line degree integrates a specific preamp/booster pair. In that case, it
does not make sense for our autodesign to "pick an amplifier". The
restrictions can be activated by:

- Listing them in `eqpt_config.json`, so that they are effective for all
ROADM instances.
- On a per-ROADM basis within the Excel sheet or the JSON definitions.

Restrictions apply to an entire ROADM as a whole, not to the individual
degrees.

If a per-degree exception is needed, the amplifier of this degree can be
defined in the equipment sheet or in the network definition.

If no booster amplifier should be placed on a degree, use the `Fused`
node in place of an amplifier.

Signed-off-by: Esther Le Rouzic <esther.lerouzic@orange.com>
Co-authored-by: Jan Kundrát <jan.kundrat@telecominfraproject.com>
  • Loading branch information
EstherLerouzic and jktjkt committed Jun 3, 2019
1 parent f6d7634 commit d2df792
Show file tree
Hide file tree
Showing 15 changed files with 1,004 additions and 193 deletions.
8 changes: 6 additions & 2 deletions Excel_userguide.rst
Expand Up @@ -19,8 +19,8 @@ In order to work the excel file MUST contain at least 2 sheets:
Nodes sheet
-----------

Nodes sheet contains seven columns.
Each line represents a 'node' (ROADM site or an in line amplifier site ILA)::
Nodes sheet contains nine columns.
Each line represents a 'node' (ROADM site or an in line amplifier site ILA or a Fused)::

City (Mandatory) ; State ; Country ; Region ; Latitude ; Longitude ; Type

Expand All @@ -38,6 +38,9 @@ Each line represents a 'node' (ROADM site or an in line amplifier site ILA)::

- *Longitude*, *Latitude* are not mandatory. If filled they should contain numbers.

- **Booster_restriction** and **Preamp_restriction** are not mandatory.
If used, they must contain one or several amplifier type_variety names separated by ' | '. This information is used to restrict types of amplifiers used in a ROADM node during autodesign. If a ROADM booster or preamp is already specified in the Eqpt sheet , the field is ignored. The field is also ignored if the node is not a ROADM node.

**There MUST NOT be empty line(s) between two nodes lines**


Expand Down Expand Up @@ -166,6 +169,7 @@ This generates a text file meshTopologyExampleV2_eqt_sheet.txt whose content ca
- **amp type** is not mandatory.
If filled it must contain types listed in `eqpt_config.json <examples/eqpt_config.json>`_ in "Edfa" list "type_variety".
If not filled it takes "std_medium_gain" as default value.
If filled with fused, a fused element with 0.0 dB loss will be placed instead of an amplifier. This might be used to avoid booster amplifier on a ROADM direction.

- **amp_gain** is not mandatory. It is the value to be set on the amplifier (in dB).
If not filled, it will be determined with design rules in the convert.py file.
Expand Down
13 changes: 9 additions & 4 deletions README.rst
Expand Up @@ -408,10 +408,15 @@ existing parameters:
+--------------------------+-----------+---------------------------------------------+
| ``add_drop_osnr`` | (number) | OSNR contribution from the add/drop ports |
+--------------------------+-----------+---------------------------------------------+
| ``restrictions`` | (strings) | Authorized type_variety of amplifier for |
| | | booster or preamp. |
| | | Listed type_variety MUST be defined in the |
| | | Edfa catalog. |
| ``restrictions`` | (dict of | If non-empty, keys ``preamp_variety_list`` |
| | strings) | and ``booster_variety_list`` represent |
| | | list of ``type_variety`` amplifiers which |
| | | are allowed for auto-design within ROADM's |
| | | line degrees. |
| | | |
| | | If no booster should be placed on a degree, |
| | | insert a ``Fused`` node on the degree |
| | | output. |
+--------------------------+-----------+---------------------------------------------+

The ``SpectralInformation`` object can be configured as follows. The user can
Expand Down
4 changes: 2 additions & 2 deletions examples/eqpt_config.json
Expand Up @@ -177,8 +177,8 @@
"target_pch_out_db": -20,
"add_drop_osnr": 38,
"restrictions": {
"preamp_variety_list":["low_gain_preamp", "high_gain_preamp"],
"booster_variety_list":["std_booster"]
"preamp_variety_list":[],
"booster_variety_list":[]
}
}],
"SI":[{
Expand Down
Binary file modified examples/meshTopologyExampleV2.xls
Binary file not shown.
31 changes: 24 additions & 7 deletions gnpy/core/convert.py
Expand Up @@ -31,6 +31,7 @@
from json import dumps
from pathlib import Path
from difflib import get_close_matches
from gnpy.core.utils import silent_remove
import time

all_rows = lambda sh, start=0: (sh.row(x) for x in range(start, sh.nrows))
Expand All @@ -54,7 +55,9 @@ def update_attr(self, kwargs):
'region': '',
'latitude': 0,
'longitude': 0,
'node_type': 'ILA'
'node_type': 'ILA',
'booster_restriction' : '',
'preamp_restriction' : ''
}

class Link(object):
Expand Down Expand Up @@ -235,7 +238,6 @@ def sanity_check(nodes, links, nodes_by_city, links_by_city, eqpts_by_city):

def convert_file(input_filename, names_matching=False, filter_region=[]):
nodes, links, eqpts = parse_excel(input_filename)

if filter_region:
nodes = [n for n in nodes if n.region.lower() in filter_region]
cities = {n.city for n in nodes}
Expand All @@ -244,10 +246,8 @@ def convert_file(input_filename, names_matching=False, filter_region=[]):
cities = {lnk.from_city for lnk in links} | {lnk.to_city for lnk in links}
nodes = [n for n in nodes if n.city in cities]


global nodes_by_city
nodes_by_city = {n.city: n for n in nodes}

#create matching dictionary for node name mismatch analysis

cities = {''.join(c.strip() for c in n.city.split('C+L')).lower(): n.city for n in nodes}
Expand Down Expand Up @@ -298,7 +298,22 @@ def convert_file(input_filename, names_matching=False, filter_region=[]):
'latitude': x.latitude,
'longitude': x.longitude}},
'type': 'Roadm'}
for x in nodes_by_city.values() if x.node_type.lower() == 'roadm'] +
for x in nodes_by_city.values() if x.node_type.lower() == 'roadm' \
and x.booster_restriction == '' and x.preamp_restriction == ''] +
[{'uid': f'roadm {x.city}',
'params' : {
'restrictions': {
'preamp_variety_list': silent_remove(x.preamp_restriction.split(' | '),''),
'booster_variety_list': silent_remove(x.booster_restriction.split(' | '),'')
}
},
'metadata': {'location': {'city': x.city,
'region': x.region,
'latitude': x.latitude,
'longitude': x.longitude}},
'type': 'Roadm'}
for x in nodes_by_city.values() if x.node_type.lower() == 'roadm' and \
(x.booster_restriction!='' or x.preamp_restriction != '')] +
[{'uid': f'west fused spans in {x.city}',
'metadata': {'location': {'city': x.city,
'region': x.region,
Expand Down Expand Up @@ -414,7 +429,9 @@ def parse_excel(input_filename):
'Region': 'region',
'Latitude': 'latitude',
'Longitude': 'longitude',
'Type': 'node_type'
'Type': 'node_type',
'Booster_restriction' : 'booster_restriction',
'Preamp_restriction' : 'preamp_restriction'
}
eqpt_headers = \
{ 'Node A': 'from_city',
Expand Down Expand Up @@ -571,7 +588,7 @@ def midpoint(city_a, city_b):
#output_json_file_name = 'coronet_conus_example.json'
#TODO get column size automatically from tupple size

NODES_COLUMN = 8
NODES_COLUMN = 10
NODES_LINE = 4
LINKS_COLUMN = 16
LINKS_LINE = 3
Expand Down
13 changes: 10 additions & 3 deletions gnpy/core/elements.py
Expand Up @@ -117,7 +117,7 @@ def __call__(self, spectral_info):
self._calc_snr(spectral_info)
return spectral_info

RoadmParams = namedtuple('RoadmParams', 'target_pch_out_db add_drop_osnr')
RoadmParams = namedtuple('RoadmParams', 'target_pch_out_db add_drop_osnr restrictions')

class Roadm(Node):
def __init__(self, *args, params, **kwargs):
Expand All @@ -126,15 +126,19 @@ def __init__(self, *args, params, **kwargs):
self.effective_loss = None
self.effective_pch_out_db = self.params.target_pch_out_db
self.passive = True
self.restrictions = self.params.restrictions

@property
def to_json(self):
return {'uid' : self.uid,
'type' : type(self).__name__,
'params' : {'target_pch_out_db' : self.effective_pch_out_db},
'params' : {
'target_pch_out_db' : self.effective_pch_out_db,
'restrictions' : self.restrictions
},
'metadata' : {
'location': self.metadata['location']._asdict()
}
}
}

def __repr__(self):
Expand Down Expand Up @@ -186,6 +190,9 @@ def __init__(self, *args, params=None, **kwargs):
def to_json(self):
return {'uid' : self.uid,
'type' : type(self).__name__,
'params' :{
'loss': self.loss
},
'metadata' : {
'location': self.metadata['location']._asdict()
}
Expand Down
88 changes: 53 additions & 35 deletions gnpy/core/equipment.py
Expand Up @@ -27,28 +27,28 @@

class common:
def update_attr(self, default_values, kwargs, name):
clean_kwargs = {k:v for k,v in kwargs.items() if v !=''}
for k,v in default_values.items():
setattr(self, k, clean_kwargs.get(k,v))
if k not in clean_kwargs and name != 'Amp' :
clean_kwargs = {k:v for k, v in kwargs.items() if v != ''}
for k, v in default_values.items():
setattr(self, k, clean_kwargs.get(k, v))
if k not in clean_kwargs and name != 'Amp':
print(f'\x1b[1;31;40m'+
f'\n WARNING missing {k} attribute in eqpt_config.json[{name}]'
f'\n default value is {k} = {v}'
+ '\x1b[0m')
f'\n WARNING missing {k} attribute in eqpt_config.json[{name}]'+
f'\n default value is {k} = {v}'+
f'\x1b[0m')
time.sleep(1)

class SI(common):
default_values =\
{
"f_min": 191.35e12,
"f_max": 196.1e12,
"baud_rate": 32e9,
"baud_rate": 32e9,
"spacing": 50e9,
"power_dbm": 0,
"power_range_db": [0,0,0.5],
"power_range_db": [0, 0, 0.5],
"roll_off": 0.15,
"tx_osnr": 45,
"sys_margins": 0
"sys_margins": 0
}

def __init__(self, **kwargs):
Expand All @@ -69,16 +69,20 @@ class Span(common):
'con_in': 0,
'con_out': 0
}

def __init__(self, **kwargs):
self.update_attr(self.default_values, kwargs, 'Span')

class Roadm(common):
default_values = \
{
'target_pch_out_db': -17,
'add_drop_osnr': 100
}
'add_drop_osnr': 100,
'restrictions': {
'preamp_variety_list':[],
'booster_variety_list':[]
}
}

def __init__(self, **kwargs):
self.update_attr(self.default_values, kwargs, 'Roadm')
Expand Down Expand Up @@ -134,7 +138,7 @@ def from_json(cls, filename, **kwargs):
config = Path(filename).parent / 'default_edfa_config.json'

type_variety = kwargs['type_variety']
type_def = kwargs.get('type_def', 'variable_gain') #default compatibility with older json eqpt files
type_def = kwargs.get('type_def', 'variable_gain') # default compatibility with older json eqpt files
nf_def = None
dual_stage_def = None

Expand Down Expand Up @@ -180,7 +184,7 @@ def from_json(cls, filename, **kwargs):
with open(config, encoding='utf-8') as f:
json_data = load(f)

return cls(**{**kwargs, **json_data,
return cls(**{**kwargs, **json_data,
'nf_model': nf_def, 'dual_stage_model': dual_stage_def})


Expand Down Expand Up @@ -247,7 +251,7 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
"""return the trx and SI parameters from eqpt_config for a given type_variety and mode (ie format)"""
trx_params = {}
default_si_data = equipment['SI']['default']

try:
trxs = equipment['Transceiver']
#if called from path_requests_run.py, trx_mode is filled with None when not specified by user
Expand All @@ -264,14 +268,14 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
f'has baud rate: {trx_params["baud_rate"]*1e-9} GHz greater than min_spacing {trx_params["min_spacing"]*1e-9}.')
else:
mode_params = {"format": "undetermined",
"baud_rate": None,
"OSNR": None,
"bit_rate": None,
"roll_off": None,
"tx_osnr":None,
"min_spacing":None,
"cost":None}
trx_params = {**mode_params}
"baud_rate": None,
"OSNR": None,
"bit_rate": None,
"roll_off": None,
"tx_osnr":None,
"min_spacing":None,
"cost":None}
trx_params = {**mode_params}
trx_params['f_min'] = equipment['Transceiver'][trx_type_variety].frequency['min']
trx_params['f_max'] = equipment['Transceiver'][trx_type_variety].frequency['max']

Expand All @@ -298,16 +302,16 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
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

return trx_params

def automatic_spacing(baud_rate):
"""return the min possible channel spacing for a given baud rate"""
# TODO : this should parametrized in a cfg file
spacing_list = [(33e9,37.5e9), (38e9,50e9), (50e9,62.5e9), (67e9,75e9), (92e9,100e9)] #list of possible tuples
#[(max_baud_rate, spacing_for_this_baud_rate)]
# list of possible tuples [(max_baud_rate, spacing_for_this_baud_rate)]
spacing_list = [(33e9, 37.5e9), (38e9, 50e9), (50e9, 62.5e9), (67e9, 75e9), (92e9, 100e9)]
return min((s[1] for s in spacing_list if s[0] > baud_rate), default=baud_rate*1.2)

def automatic_nch(f_min, f_max, spacing):
Expand All @@ -333,18 +337,31 @@ def update_dual_stage(equipment):
if edfa.type_def == 'dual_stage':
edfa_preamp = edfa_dict[edfa.dual_stage_model.preamp_variety]
edfa_booster = edfa_dict[edfa.dual_stage_model.booster_variety]
for k,v in edfa_preamp.__dict__.items():
attr_k = 'preamp_'+k
setattr(edfa, attr_k, v)
for k,v in edfa_booster.__dict__.items():
attr_k = 'booster_'+k
setattr(edfa, attr_k, v)
for key, value in edfa_preamp.__dict__.items():
attr_k = 'preamp_' + key
setattr(edfa, attr_k, value)
for key, value in edfa_booster.__dict__.items():
attr_k = 'booster_' + key
setattr(edfa, attr_k, value)
edfa.p_max = edfa_booster.p_max
edfa.gain_flatmax = edfa_booster.gain_flatmax + edfa_preamp.gain_flatmax
if edfa.gain_min < edfa_preamp.gain_min:
raise EquipmentConfigError(f'Dual stage {edfa.type_variety} min gain is lower than its preamp min gain')
return equipment

def roadm_restrictions_sanity_check(equipment):
""" verifies that booster and preamp restrictions specified in roadm equipment are listed
in the edfa.
"""
restrictions = equipment['Roadm']['default'].restrictions['booster_variety_list'] + \
equipment['Roadm']['default'].restrictions['preamp_variety_list']
for amp_name in restrictions:
try:
if equipment['Edfa'][amp_name]:
pass
except KeyError:
raise EquipmentConfigError(f'ROADM restriction {amp_name} does not refer to a defined EDFA name')

def equipment_from_json(json_data, filename):
"""build global dictionnary eqpt_library that stores all eqpt characteristics:
edfa type type_variety, fiber type_variety
Expand All @@ -359,11 +376,12 @@ def equipment_from_json(json_data, filename):
equipment[key] = {}
typ = globals()[key]
for entry in entries:
subkey = entry.get('type_variety', 'default')
subkey = entry.get('type_variety', 'default')
if key == 'Edfa':
equipment[key][subkey] = Amp.from_json(filename, **entry)
else:
else:
equipment[key][subkey] = typ(**entry)
equipment = update_trx_osnr(equipment)
equipment = update_dual_stage(equipment)
roadm_restrictions_sanity_check(equipment)
return equipment

0 comments on commit d2df792

Please sign in to comment.