Merge pull request #215 from Orange-OpenSource/roadm_equalization

Roadm equalization
This commit is contained in:
James
2019-04-09 11:00:48 -04:00
committed by GitHub
16 changed files with 1051 additions and 270 deletions

View File

@@ -1,12 +1,13 @@
{ "Edfa":[{
"type_variety": "high_detail_model_example",
"type_def": "advanced_model",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false,
"allowed_for_design": false
},
},
{
"type_variety": "operator_model_example",
"type_def": "variable_gain",
@@ -37,6 +38,17 @@
"allowed_for_design": false
},
{
"type_variety": "std_high_gain",
"type_def": "variable_gain",
"gain_flatmax": 35,
"gain_min": 25,
"p_max": 21,
"nf_min": 5.5,
"nf_max": 7,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_medium_gain",
"type_def": "variable_gain",
"gain_flatmax": 26,
@@ -59,6 +71,17 @@
"allowed_for_design": true
},
{
"type_variety": "high_power",
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 25,
"nf_min": 9,
"nf_max": 15,
"out_voa_auto": false,
"allowed_for_design": false
},
{
"type_variety": "std_fixed_gain",
"type_def": "fixed_gain",
"gain_flatmax": 21,
@@ -66,7 +89,50 @@
"p_max": 21,
"nf0": 5.5,
"allowed_for_design": false
}
},
{
"type_variety": "4pumps_raman",
"type_def": "fixed_gain",
"gain_flatmax": 12,
"gain_min": 12,
"p_max": 21,
"nf0": -1,
"allowed_for_design": false
},
{
"type_variety": "hybrid_4pumps_lowgain",
"type_def": "dual_stage",
"raman": true,
"gain_min": 25,
"preamp_variety": "4pumps_raman",
"booster_variety": "std_low_gain",
"allowed_for_design": true
},
{
"type_variety": "hybrid_4pumps_mediumgain",
"type_def": "dual_stage",
"raman": true,
"gain_min": 25,
"preamp_variety": "4pumps_raman",
"booster_variety": "std_medium_gain",
"allowed_for_design": true
},
{
"type_variety": "medium+low_gain",
"type_def": "dual_stage",
"gain_min": 25,
"preamp_variety": "std_medium_gain",
"booster_variety": "std_low_gain",
"allowed_for_design": true
},
{
"type_variety": "medium+high_power",
"type_def": "dual_stage",
"gain_min": 25,
"preamp_variety": "std_medium_gain",
"booster_variety": "high_power",
"allowed_for_design": false
}
],
"Fiber":[{
"type_variety": "SSMF",
@@ -84,9 +150,11 @@
"gamma": 0.000843
}
],
"Spans":[{
"Span":[{
"power_mode":true,
"delta_power_range_db": [0,0,0.5],
"delta_power_range_db": [-2,3,0.5],
"max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 2.5,
"max_length": 150,
"length_units": "km",
"max_loss": 28,
@@ -96,10 +164,13 @@
"con_out": 0
}
],
"Roadms":[{
"gain_mode_default_loss": 20,
"power_mode_pout_target": -20,
"add_drop_osnr": 38
"Roadm":[{
"target_pch_out_db": -20,
"add_drop_osnr": 38,
"restrictions": {
"preamp_variety_list":["low_gain_preamp", "high_gain_preamp"],
"booster_variety_list":["std_booster"]
}
}],
"SI":[{
"f_min": 191.3e12,
@@ -107,10 +178,10 @@
"f_max":195.1e12,
"spacing": 50e9,
"power_dbm": 0,
"power_range_db": [0,0,0.5],
"power_range_db": [0,0,1],
"roll_off": 0.15,
"tx_osnr": 40,
"sys_margins": 0
"sys_margins": 2
}],
"Transceiver":[
{

View File

@@ -623,31 +623,473 @@
"con_in": null,
"con_out": null
}
},
{
"uid": "east edfa in Lannion_CAS to Corlay",
"metadata": {
"location": {
"city": "Lannion_CAS",
"region": "RLD",
"latitude": 2.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "east edfa in Corlay to Loudeac",
"metadata": {
"location": {
"city": "Corlay",
"region": "RLD",
"latitude": 2.0,
"longitude": 1.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "east edfa in Loudeac to Lorient_KMA",
"metadata": {
"location": {
"city": "Loudeac",
"region": "RLD",
"latitude": 2.0,
"longitude": 2.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "east edfa in Lorient_KMA to Vannes_KBE",
"metadata": {
"location": {
"city": "Lorient_KMA",
"region": "RLD",
"latitude": 2.0,
"longitude": 3.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "east edfa in Lannion_CAS to Stbrieuc",
"metadata": {
"location": {
"city": "Lannion_CAS",
"region": "RLD",
"latitude": 2.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "east edfa in Stbrieuc to Rennes_STA",
"metadata": {
"location": {
"city": "Stbrieuc",
"region": "RLD",
"latitude": 1.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "east edfa in Lannion_CAS to Morlaix",
"metadata": {
"location": {
"city": "Lannion_CAS",
"region": "RLD",
"latitude": 2.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "east edfa in Lorient_KMA to Loudeac",
"metadata": {
"location": {
"city": "Lorient_KMA",
"region": "RLD",
"latitude": 2.0,
"longitude": 3.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "east edfa in Vannes_KBE to Lorient_KMA",
"metadata": {
"location": {
"city": "Vannes_KBE",
"region": "RLD",
"latitude": 2.0,
"longitude": 4.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "east edfa in Rennes_STA to Stbrieuc",
"metadata": {
"location": {
"city": "Rennes_STA",
"region": "RLD",
"latitude": 0.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "east edfa in Brest_KLA to Morlaix",
"metadata": {
"location": {
"city": "Brest_KLA",
"region": "RLD",
"latitude": 4.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "west edfa in Lannion_CAS to Corlay",
"metadata": {
"location": {
"city": "Lannion_CAS",
"region": "RLD",
"latitude": 2.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "west edfa in Corlay to Loudeac",
"metadata": {
"location": {
"city": "Corlay",
"region": "RLD",
"latitude": 2.0,
"longitude": 1.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "west edfa in Loudeac to Lorient_KMA",
"metadata": {
"location": {
"city": "Loudeac",
"region": "RLD",
"latitude": 2.0,
"longitude": 2.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "west edfa in Lorient_KMA to Vannes_KBE",
"metadata": {
"location": {
"city": "Lorient_KMA",
"region": "RLD",
"latitude": 2.0,
"longitude": 3.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "west edfa in Lannion_CAS to Stbrieuc",
"metadata": {
"location": {
"city": "Lannion_CAS",
"region": "RLD",
"latitude": 2.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "west edfa in Stbrieuc to Rennes_STA",
"metadata": {
"location": {
"city": "Stbrieuc",
"region": "RLD",
"latitude": 1.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "west edfa in Lannion_CAS to Morlaix",
"metadata": {
"location": {
"city": "Lannion_CAS",
"region": "RLD",
"latitude": 2.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "west edfa in Lorient_KMA to Loudeac",
"metadata": {
"location": {
"city": "Lorient_KMA",
"region": "RLD",
"latitude": 2.0,
"longitude": 3.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "west edfa in Vannes_KBE to Lorient_KMA",
"metadata": {
"location": {
"city": "Vannes_KBE",
"region": "RLD",
"latitude": 2.0,
"longitude": 4.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "west edfa in Rennes_STA to Stbrieuc",
"metadata": {
"location": {
"city": "Rennes_STA",
"region": "RLD",
"latitude": 0.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "west edfa in Brest_KLA to Morlaix",
"metadata": {
"location": {
"city": "Brest_KLA",
"region": "RLD",
"latitude": 4.0,
"longitude": 0.0
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
"tilt_target": 0,
"out_voa": null
}
}
],
"connections": [
{
"from_node": "roadm Lannion_CAS",
"to_node": "east edfa in Lannion_CAS to Corlay"
},
{
"from_node": "east edfa in Lannion_CAS to Corlay",
"to_node": "fiber (Lannion_CAS → Corlay)-F061"
},
{
"from_node": "fiber (Corlay → Lannion_CAS)-F061",
"to_node": "west edfa in Lannion_CAS to Corlay"
},
{
"from_node": "west edfa in Lannion_CAS to Corlay",
"to_node": "roadm Lannion_CAS"
},
{
"from_node": "roadm Lannion_CAS",
"to_node": "east edfa in Lannion_CAS to Stbrieuc"
},
{
"from_node": "east edfa in Lannion_CAS to Stbrieuc",
"to_node": "fiber (Lannion_CAS → Stbrieuc)-F056"
},
{
"from_node": "fiber (Stbrieuc → Lannion_CAS)-F056",
"to_node": "west edfa in Lannion_CAS to Stbrieuc"
},
{
"from_node": "west edfa in Lannion_CAS to Stbrieuc",
"to_node": "roadm Lannion_CAS"
},
{
"from_node": "roadm Lannion_CAS",
"to_node": "east edfa in Lannion_CAS to Morlaix"
},
{
"from_node": "east edfa in Lannion_CAS to Morlaix",
"to_node": "fiber (Lannion_CAS → Morlaix)-F059"
},
{
"from_node": "fiber (Morlaix → Lannion_CAS)-F059",
"to_node": "west edfa in Lannion_CAS to Morlaix"
},
{
"from_node": "west edfa in Lannion_CAS to Morlaix",
"to_node": "roadm Lannion_CAS"
},
{
@@ -684,18 +1126,34 @@
},
{
"from_node": "roadm Lorient_KMA",
"to_node": "east edfa in Lorient_KMA to Loudeac"
},
{
"from_node": "east edfa in Lorient_KMA to Loudeac",
"to_node": "fiber (Lorient_KMA → Loudeac)-F054"
},
{
"from_node": "fiber (Loudeac → Lorient_KMA)-F054",
"to_node": "west edfa in Lorient_KMA to Loudeac"
},
{
"from_node": "west edfa in Lorient_KMA to Loudeac",
"to_node": "roadm Lorient_KMA"
},
{
"from_node": "roadm Lorient_KMA",
"to_node": "east edfa in Lorient_KMA to Vannes_KBE"
},
{
"from_node": "east edfa in Lorient_KMA to Vannes_KBE",
"to_node": "fiber (Lorient_KMA → Vannes_KBE)-F055"
},
{
"from_node": "fiber (Vannes_KBE → Lorient_KMA)-F055",
"to_node": "west edfa in Lorient_KMA to Vannes_KBE"
},
{
"from_node": "west edfa in Lorient_KMA to Vannes_KBE",
"to_node": "roadm Lorient_KMA"
},
{
@@ -708,10 +1166,18 @@
},
{
"from_node": "roadm Vannes_KBE",
"to_node": "east edfa in Vannes_KBE to Lorient_KMA"
},
{
"from_node": "east edfa in Vannes_KBE to Lorient_KMA",
"to_node": "fiber (Vannes_KBE → Lorient_KMA)-F055"
},
{
"from_node": "fiber (Lorient_KMA → Vannes_KBE)-F055",
"to_node": "west edfa in Vannes_KBE to Lorient_KMA"
},
{
"from_node": "west edfa in Vannes_KBE to Lorient_KMA",
"to_node": "roadm Vannes_KBE"
},
{
@@ -724,18 +1190,34 @@
},
{
"from_node": "fiber (Lannion_CAS → Stbrieuc)-F056",
"to_node": "east edfa in Stbrieuc to Rennes_STA"
},
{
"from_node": "east edfa in Stbrieuc to Rennes_STA",
"to_node": "fiber (Stbrieuc → Rennes_STA)-F057"
},
{
"from_node": "fiber (Rennes_STA → Stbrieuc)-F057",
"to_node": "west edfa in Stbrieuc to Rennes_STA"
},
{
"from_node": "west edfa in Stbrieuc to Rennes_STA",
"to_node": "fiber (Stbrieuc → Lannion_CAS)-F056"
},
{
"from_node": "roadm Rennes_STA",
"to_node": "east edfa in Rennes_STA to Stbrieuc"
},
{
"from_node": "east edfa in Rennes_STA to Stbrieuc",
"to_node": "fiber (Rennes_STA → Stbrieuc)-F057"
},
{
"from_node": "fiber (Stbrieuc → Rennes_STA)-F057",
"to_node": "west edfa in Rennes_STA to Stbrieuc"
},
{
"from_node": "west edfa in Rennes_STA to Stbrieuc",
"to_node": "roadm Rennes_STA"
},
{
@@ -764,10 +1246,18 @@
},
{
"from_node": "roadm Brest_KLA",
"to_node": "east edfa in Brest_KLA to Morlaix"
},
{
"from_node": "east edfa in Brest_KLA to Morlaix",
"to_node": "fiber (Brest_KLA → Morlaix)-F060"
},
{
"from_node": "fiber (Morlaix → Brest_KLA)-F060",
"to_node": "west edfa in Brest_KLA to Morlaix"
},
{
"from_node": "west edfa in Brest_KLA to Morlaix",
"to_node": "roadm Brest_KLA"
},
{

Binary file not shown.

View File

@@ -23,7 +23,7 @@ from networkx import (draw_networkx_nodes, draw_networkx_edges,
from numpy import mean
from gnpy.core.service_sheet import convert_service_sheet, Request_element, Element
from gnpy.core.utils import load_json
from gnpy.core.network import load_network, build_network, set_roadm_loss, save_network
from gnpy.core.network import load_network, build_network, save_network
from gnpy.core.equipment import load_equipment, trx_mode_params, automatic_nch, automatic_spacing
from gnpy.core.elements import Transceiver, Roadm, Edfa, Fused, Fiber
from gnpy.core.utils import db2lin, lin2db

View File

@@ -106,8 +106,8 @@ def main(network, equipment, source, destination, req = None):
}]
result_dicts.update({'network': network_data})
design_data = [{
'power_mode' : equipment['Spans']['default'].power_mode,
'span_power_range' : equipment['Spans']['default'].delta_power_range_db,
'power_mode' : equipment['Span']['default'].power_mode,
'span_power_range' : equipment['Span']['default'].delta_power_range_db,
'design_pch' : equipment['SI']['default'].power_dbm,
'baud_rate' : equipment['SI']['default'].baud_rate
}]
@@ -115,9 +115,9 @@ def main(network, equipment, source, destination, req = None):
simulation_data = []
result_dicts.update({'simulation results': simulation_data})
power_mode = equipment['Spans']['default'].power_mode
power_mode = equipment['Span']['default'].power_mode
print('\n'.join([f'Power mode is set to {power_mode}',
f'=> it can be modified in eqpt_config.json - Spans']))
f'=> it can be modified in eqpt_config.json - Span']))
pref_ch_db = lin2db(req.power*1e3) #reference channel power / span (SL=20dB)
pref_total_db = pref_ch_db + lin2db(req.nb_channel) #reference total power / span (SL=20dB)
@@ -136,25 +136,44 @@ def main(network, equipment, source, destination, req = None):
print('invalid power range definition in eqpt_config, should be power_range_db: [lower, upper, step]')
power_range = [0]
if not power_mode:
#power cannot be changed in gain mode
power_range = [0]
for dp_db in power_range:
req.power = db2lin(pref_ch_db + dp_db)*1e-3
print(f'\nPropagating with input power = {lin2db(req.power*1e3):.2f}dBm :')
if power_mode:
print(f'\nPropagating with input power = {lin2db(req.power*1e3):.2f}dBm :')
else:
print(f'\nPropagating in gain mode: power cannot be set manually')
infos = propagate2(path, req, equipment, show=len(power_range)==1)
print(f'\nTransmission result for input power = {lin2db(req.power*1e3):.2f}dBm :')
if power_mode:
print(f'\nTransmission result for input power = {lin2db(req.power*1e3):.2f}dBm :')
else:
print(f'\nTransmission results:')
#info message in gain mode
print(destination)
#print(f'\n !!!!!!!!!!!!!!!!! TEST POINT !!!!!!!!!!!!!!!!!!!!!')
#print(f'carriers ase output of {path[1]} =\n {list(path[1].carriers("out", "nli"))}')
# => use "in" or "out" parameter
# => use "nli" or "ase" or "signal" or "total" parameter
simulation_data.append({
'Pch_dBm' : pref_ch_db + dp_db,
'OSNR_ASE_0.1nm' : round(mean(destination.osnr_ase_01nm),2),
'OSNR_ASE_signal_bw' : round(mean(destination.osnr_ase),2),
'SNR_nli_signal_bw' : round(mean(destination.osnr_nli),2),
'SNR_total_signal_bw' : round(mean(destination.snr),2)
})
# => use "nli" or "ase" or "signal" or "total" parameter
if power_mode:
simulation_data.append({
'Pch_dBm' : pref_ch_db + dp_db,
'OSNR_ASE_0.1nm' : round(mean(destination.osnr_ase_01nm),2),
'OSNR_ASE_signal_bw' : round(mean(destination.osnr_ase),2),
'SNR_nli_signal_bw' : round(mean(destination.osnr_nli),2),
'SNR_total_signal_bw' : round(mean(destination.snr),2)
})
else:
simulation_data.append({
'gain_mode' : 'power canot be set',
'OSNR_ASE_0.1nm' : round(mean(destination.osnr_ase_01nm),2),
'OSNR_ASE_signal_bw' : round(mean(destination.osnr_ase),2),
'SNR_nli_signal_bw' : round(mean(destination.osnr_nli),2),
'SNR_total_signal_bw' : round(mean(destination.snr),2)
})
#info message in gain mode
write_csv(result_dicts, 'simulation_result.csv')
return path, infos

View File

@@ -111,11 +111,12 @@ class Eqpt(object):
{
'from_city': '',
'to_city': '',
'east_amp_type': '',
'east_att_in': 0,
'east_amp_gain': 0,
'east_tilt': 0,
'east_att_out': 0
'east_amp_type': '',
'east_att_in': 0,
'east_amp_gain': None,
'east_amp_dp': None,
'east_tilt': 0,
'east_att_out': None
}
@@ -163,8 +164,8 @@ def parse_headers(my_sheet, input_headers_dict, headers, start_line, slice_in):
slice_out = read_slice(my_sheet, start_line+iteration, slice_in, h0)
iteration += 1
if slice_out == (-1, -1):
if h0 == 'east':
print(f'\x1b[1;31;40m'+f'CRITICAL: missing _east_ header above other headers (hierarchical) _ ABORT'+ '\x1b[0m')
if h0 in ('east', 'Node A', 'Node Z', 'City') :
print(f'\x1b[1;31;40m'+f'CRITICAL: missing _{h0}_ header: EXECUTION ENDS'+ '\x1b[0m')
exit()
else:
print(f'missing header {h0}')
@@ -344,6 +345,7 @@ def convert_file(input_filename, names_matching=False, filter_region=[]):
'type': 'Edfa',
'type_variety': e.east_amp_type,
'operational': {'gain_target': e.east_amp_gain,
'delta_p': e.east_amp_dp,
'tilt_target': e.east_tilt,
'out_voa' : e.east_att_out}
}
@@ -356,6 +358,7 @@ def convert_file(input_filename, names_matching=False, filter_region=[]):
'type': 'Edfa',
'type_variety': e.west_amp_type,
'operational': {'gain_target': e.west_amp_gain,
'delta_p': e.west_amp_dp,
'tilt_target': e.west_tilt,
'out_voa' : e.west_att_out}
}
@@ -420,6 +423,7 @@ def parse_excel(input_filename):
'amp type': 'east_amp_type',
'att_in': 'east_att_in',
'amp gain': 'east_amp_gain',
'delta p': 'east_amp_dp',
'tilt': 'east_tilt',
'att_out': 'east_att_out'
},
@@ -427,6 +431,7 @@ def parse_excel(input_filename):
'amp type': 'west_amp_type',
'att_in': 'west_att_in',
'amp gain': 'west_amp_gain',
'delta p': 'west_amp_dp',
'tilt': 'west_tilt',
'att_out': 'west_att_out'
}
@@ -566,12 +571,12 @@ def midpoint(city_a, city_b):
#output_json_file_name = 'coronet_conus_example.json'
#TODO get column size automatically from tupple size
NODES_COLUMN = 7
NODES_COLUMN = 8
NODES_LINE = 4
LINKS_COLUMN = 16
LINKS_LINE = 3
EQPTS_LINE = 3
EQPTS_COLUMN = 12
EQPTS_COLUMN = 14
parser = ArgumentParser()
parser.add_argument('workbook', nargs='?', type=Path , default='meshTopologyExampleV2.xls')
parser.add_argument('-f', '--filter-region', action='append', default=[])

View File

@@ -41,7 +41,6 @@ class Transceiver(Node):
with errstate(divide='ignore'):
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]
@@ -112,25 +111,21 @@ class Transceiver(Node):
self._calc_snr(spectral_info)
return spectral_info
RoadmParams = namedtuple('RoadmParams', 'loss')
RoadmParams = namedtuple('RoadmParams', 'target_pch_out_db add_drop_osnr')
class Roadm(Node):
def __init__(self, *args, params=None, **kwargs):
if params is None:
# default loss value if not mentioned in loaded network json
params = {'loss':None}
def __init__(self, *args, params, **kwargs):
super().__init__(*args, params=RoadmParams(**params), **kwargs)
self.loss = self.params.loss
self.target_pch_out_db = None #set in Networks.py by def set_roadm_loss
self.effective_pch_out_db = None
self.effective_loss = None #set in self.propagate
self.loss = 0 #auto-design interest
self.effective_loss = None
self.effective_pch_out_db = self.params.target_pch_out_db
self.passive = True
@property
def to_json(self):
return {'uid' : self.uid,
'type' : type(self).__name__,
'params' : {'loss' : self.loss},
'params' : {'target_pch_out_db' : self.effective_pch_out_db},
'metadata' : {
'location': self.metadata['location']._asdict()
}
@@ -141,26 +136,25 @@ class Roadm(Node):
def __str__(self):
return '\n'.join([f'{type(self).__name__} {self.uid}',
f' loss (dB): {self.effective_loss:.2f}',
f' pch out (dBm): {self.effective_pch_out_db!r}'])
f' effective loss (dB): {self.effective_loss:.2f}',
f' pch out (dBm): {self.effective_pch_out_db!r}'])
def propagate(self, pref, *carriers):
#pin_target and loss are read from eqpt_config.json['Roadm']
#all ingress channels in xpress are set to this power level
#but add channels are not, so we define an effective loss
#in the case of add channels
if self.target_pch_out_db:
self.effective_loss = pref.pi - self.target_pch_out_db
else:
self.effective_loss = self.loss
self.effective_pch_out_db = pref.pi - self.effective_loss
attenuation = db2lin(self.effective_loss)
for carrier in carriers:
self.effective_pch_out_db = min(pref.pi, self.params.target_pch_out_db)
self.effective_loss = pref.pi - self.effective_pch_out_db
carriers_power = array([c.power.signal +c.power.nli+c.power.ase for c in carriers])
carriers_att = list(map(lambda x : lin2db(x*1e3)-self.params.target_pch_out_db, carriers_power))
exceeding_att = -min(list(filter(lambda x: x < 0, carriers_att)), default = 0)
carriers_att = list(map(lambda x: db2lin(x+exceeding_att), carriers_att))
for carrier_att, carrier in zip(carriers_att, carriers) :
pwr = carrier.power
pwr = pwr._replace(signal=pwr.signal/attenuation,
nonlinear_interference=pwr.nli/attenuation,
amplified_spontaneous_emission=pwr.ase/attenuation)
pwr = pwr._replace( signal = pwr.signal/carrier_att,
nonlinear_interference = pwr.nli/carrier_att,
amplified_spontaneous_emission = pwr.ase/carrier_att)
yield carrier._replace(power=pwr)
def update_pref(self, pref):
@@ -430,16 +424,16 @@ class EdfaParams:
if params == {}:
self.type_variety = ''
self.type_def = ''
self.gain_flatmax = 0
self.gain_min = 0
self.p_max = 0
self.nf_model = None
self.nf_fit_coeff = None
self.nf_ripple = None
self.dgt = None
self.gain_ripple = None
self.out_voa_auto = False
self.allowed_for_design = None
# self.gain_flatmax = 0
# self.gain_min = 0
# self.p_max = 0
# self.nf_model = None
# self.nf_fit_coeff = None
# self.nf_ripple = None
# self.dgt = None
# self.gain_ripple = None
# self.out_voa_auto = False
# self.allowed_for_design = None
def update_params(self, kwargs):
for k,v in kwargs.items() :
@@ -447,10 +441,22 @@ class EdfaParams:
if isinstance(v, dict) else v)
class EdfaOperational:
def __init__(self, gain_target, tilt_target, out_voa=None):
self.gain_target = gain_target
self.tilt_target = tilt_target
self.out_voa = out_voa
default_values = \
{
'gain_target': None,
'delta_p': None,
'out_voa': None,
'tilt_target': 0
}
def __init__(self, **operational):
self.update_attr(operational)
def update_attr(self, kwargs):
clean_kwargs = {k:v for k,v in kwargs.items() if v !=''}
for k,v in self.default_values.items():
setattr(self, k, clean_kwargs.get(k,v))
def __repr__(self):
return (f'{type(self).__name__}('
f'gain_target={self.gain_target!r}, '
@@ -479,14 +485,16 @@ class Edfa(Node):
self.pin_db = None
self.nch = None
self.pout_db = None
self.dp_db = None #delta P with Pref (power swwep) in power mode
self.target_pch_out_db = None
self.effective_pch_out_db = None
self.passive = False
self.effective_gain = self.operational.gain_target
self.att_in = None
self.carriers_in = None
self.carriers_out = None
self.effective_gain = self.operational.gain_target
self.delta_p = self.operational.delta_p #delta P with Pref (power swwep) in power mode
self.tilt_target = self.operational.tilt_target
self.out_voa = self.operational.out_voa
@property
def to_json(self):
@@ -494,9 +502,10 @@ class Edfa(Node):
'type' : type(self).__name__,
'type_variety' : self.params.type_variety,
'operational' : {
'gain_target' : self.operational.gain_target,
'tilt_target' : self.operational.tilt_target,
'out_voa' : self.operational.out_voa
'gain_target' : self.effective_gain,
'delta_p' : self.delta_p,
'tilt_target' : self.tilt_target,
'out_voa' : self.out_voa
},
'metadata' : {
'location': self.metadata['location']._asdict()
@@ -528,10 +537,10 @@ class Edfa(Node):
f' pad att_in (dB): {self.att_in:.2f}',
f' Power In (dBm): {self.pin_db:.2f}',
f' Power Out (dBm): {self.pout_db:.2f}',
f' Delta_P (dB): {self.dp_db!r}',
f' Delta_P (dB): {self.delta_p!r}',
f' target pch (dBm): {self.target_pch_out_db!r}',
f' effective pch (dBm): {self.effective_pch_out_db!r}',
f' output VOA (dB): {self.operational.out_voa:.2f}'])
f' output VOA (dB): {self.out_voa:.2f}'])
def carriers(self, loc, attr):
"""retrieve carriers information
@@ -561,58 +570,101 @@ class Edfa(Node):
amplifier_freq = itufs(0.05) * 1e12 # Hz
self.channel_freq = frequencies
self.interpol_dgt = interp(self.channel_freq, amplifier_freq, self.params.dgt)
self.interpol_gain_ripple = interp(self.channel_freq, amplifier_freq, self.params.gain_ripple)
self.interpol_nf_ripple =interp(self.channel_freq, amplifier_freq, self.params.nf_ripple)
self.nch = frequencies.size
self.pin_db = lin2db(sum(pin*1e3))
"""in power mode: dp_db is defined and can be used to calculate the power target
"""in power mode: delta_p is defined and can be used to calculate the power target
This power target is used calculate the amplifier gain"""
if self.dp_db is not None:
self.target_pch_out_db = round(self.dp_db + pref.p0, 2)
if self.delta_p is not None:
self.target_pch_out_db = round(self.delta_p + pref.p0, 2)
self.effective_gain = self.target_pch_out_db - pref.pi
else:
self.effective_gain = self.operational.gain_target
"""check power saturation and correct target_gain accordingly:"""
self.effective_gain = min(self.effective_gain, self.params.p_max - self.pin_db)
"""check power saturation and correct effective gain & power accordingly:"""
self.effective_gain = min(
self.effective_gain,
self.params.p_max - (pref.pi + pref.neq_ch)
)
#print(self.uid, self.effective_gain, self.operational.gain_target)
self.effective_pch_out_db = round(pref.pi + self.effective_gain, 2)
"""check power saturation and correct target_gain accordingly:"""
#print(self.uid, self.effective_gain, self.pin_db, pref.pi)
self.nf = self._calc_nf()
self.gprofile = self._gain_profile(pin)
pout = (pin + self.noise_profile(baud_rates))*db2lin(self.gprofile)
self.pout_db = lin2db(sum(pout*1e3))
self.operational.gain_target = self.effective_gain
# ase & nli are only calculated in signal bandwidth
# pout_db is not the absolute full output power (negligible if sufficient channels)
def _nf(self, type_def, nf_model, nf_fit_coeff, gain_min, gain_flatmax, gain_target):
#if hybrid raman, use edfa_gain_flatmax attribute, else use gain_flatmax
#gain_flatmax = getattr(params, 'edfa_gain_flatmax', params.gain_flatmax)
pad = max(gain_min - gain_target, 0)
gain_target += pad
dg = max(gain_flatmax - gain_target, 0)
if type_def == 'variable_gain':
g1a = gain_target - nf_model.delta_p - dg
nf_avg = lin2db(db2lin(nf_model.nf1) + db2lin(nf_model.nf2)/db2lin(g1a))
elif type_def == 'fixed_gain':
nf_avg = nf_model.nf0
elif type_def == 'openroadm':
pin_ch = self.pin_db - lin2db(self.nch)
# model OSNR = f(Pin)
nf_avg = pin_ch - polyval(nf_model.nf_coef, pin_ch) + 58
elif type_def == 'advanced_model':
nf_avg = polyval(nf_fit_coeff, -dg)
else :
print(
f'\x1b[1;31;40m'\
+ f'CRITICAL: unrecognized type def _{self.params.type_def}_\n\
=> please check eqpt_config.json'\
+ '\x1b[0m'
)
exit()
return nf_avg+pad, pad
def _calc_nf(self, avg = False):
"""nf calculation based on 2 models: self.params.nf_model.enabled from json import:
True => 2 stages amp modelling based on precalculated nf1, nf2 and delta_p in build_OA_json
False => polynomial fit based on self.params.nf_fit_coeff"""
# TODO|jla: TBD alarm rising or input VOA padding in case
# gain_min > gain_target TBD:
pad = max(self.params.gain_min - self.effective_gain, 0)
self.att_in = pad
gain_target = self.effective_gain + pad
dg = max(self.params.gain_flatmax - gain_target, 0)
if self.params.type_def == 'variable_gain':
g1a = gain_target - self.params.nf_model.delta_p - dg
nf_avg = lin2db(db2lin(self.params.nf_model.nf1) + db2lin(self.params.nf_model.nf2)/db2lin(g1a))
elif self.params.type_def == 'fixed_gain':
nf_avg = self.params.nf_model.nf0
elif self.params.type_def == 'openroadm':
pin_ch = self.pin_db - lin2db(self.nch)
# model OSNR = f(Pin)
nf_avg = pin_ch - polyval(self.params.nf_model.nf_coef, pin_ch) + 58
else:
nf_avg = polyval(self.params.nf_fit_coeff, -dg)
if self.params.type_def == 'dual_stage':
g1 = self.params.preamp_gain_flatmax
g2 = self.effective_gain - g1
nf1_avg, pad = self._nf( self.params.preamp_type_def,
self.params.preamp_nf_model,
self.params.preamp_nf_fit_coeff,
self.params.preamp_gain_min,
self.params.preamp_gain_flatmax,
g1)
#no padding expected for the 1stage because g1 = gain_max
nf2_avg, pad = self._nf( self.params.booster_type_def,
self.params.booster_nf_model,
self.params.booster_nf_fit_coeff,
self.params.booster_gain_min,
self.params.booster_gain_flatmax,
g2)
nf_avg = lin2db(db2lin(nf1_avg) + db2lin(nf2_avg-g1))
#no padding expected for the 1stage because g1 = gain_max
pad = 0
else:
nf_avg, pad = self._nf( self.params.type_def,
self.params.nf_model,
self.params.nf_fit_coeff,
self.params.gain_min,
self.params.gain_flatmax,
self.effective_gain)
self.att_in = pad # not used to attenuate carriers, only used in _repr_ and _str_
if avg:
return nf_avg + pad
return nf_avg
else:
return self.interpol_nf_ripple + nf_avg + pad # input VOA = 1 for 1 NF degradation
return self.interpol_nf_ripple + nf_avg # input VOA = 1 for 1 NF degradation
def noise_profile(self, df):
""" noise_profile(bw) computes amplifier ase (W) in signal bw (Hz)
@@ -705,7 +757,7 @@ class Edfa(Node):
# Calculate the target slope - currently assumes equal spaced channels
# TODO|jla: support arbitrary channel spacing
targ_slope = self.operational.tilt_target / (len(nb_channel) - 1)
targ_slope = self.tilt_target / (len(nb_channel) - 1)
# first estimate of DGT scaling
if abs(dgt_slope) > 0.001: # check for zero value due to flat dgt
@@ -779,7 +831,7 @@ class Edfa(Node):
gains = db2lin(self.gprofile)
carrier_ases = self.noise_profile(brate)
att = db2lin(self.operational.out_voa)
att = db2lin(self.out_voa)
for gain, carrier_ase, carrier in zip(gains, carrier_ases, carriers):
pwr = carrier.power
@@ -790,7 +842,7 @@ class Edfa(Node):
def update_pref(self, pref):
return pref._replace(p_span0=pref.p0,
p_spani=pref.pi + self.effective_gain - self.operational.out_voa)
p_spani=pref.pi + self.effective_gain - self.out_voa)
def __call__(self, spectral_info):
self.carriers_in = spectral_info.carriers

View File

@@ -17,44 +17,124 @@ from json import load
from gnpy.core.utils import lin2db, db2lin, load_json
from collections import namedtuple
from gnpy.core.elements import Edfa
import time
Model_vg = namedtuple('Model_vg', 'nf1 nf2 delta_p')
Model_fg = namedtuple('Model_fg', 'nf0')
Model_openroadm = namedtuple('Model_openroadm', 'nf_coef')
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 add_drop_osnr')
SI = namedtuple('SI', 'f_min f_max baud_rate spacing roll_off \
power_dbm power_range_db tx_osnr sys_margins')
AmpBase = namedtuple(
'AmpBase',
'type_variety type_def gain_flatmax gain_min p_max'
' nf_model nf_fit_coeff nf_ripple dgt gain_ripple out_voa_auto allowed_for_design')
class Amp(AmpBase):
def __new__(cls,
type_variety, type_def, gain_flatmax, gain_min, p_max, nf_model=None,
nf_fit_coeff=None, nf_ripple=None, dgt=None, gain_ripple=None,
out_voa_auto=False, allowed_for_design=True):
return super().__new__(cls,
type_variety, type_def, gain_flatmax, gain_min, p_max,
nf_model, nf_fit_coeff, nf_ripple, dgt, gain_ripple,
out_voa_auto, allowed_for_design)
Model_hybrid = namedtuple('Model_hybrid', 'nf_ram gain_ram edfa_variety')
Model_dual_stage = namedtuple('Model_dual_stage', 'preamp_variety booster_variety')
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' :
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')
time.sleep(1)
class SI(common):
default_values =\
{
"f_min": 191.35e12,
"f_max": 196.1e12,
"baud_rate": 32e9,
"spacing": 50e9,
"power_dbm": 0,
"power_range_db": [0,0,0.5],
"roll_off": 0.15,
"tx_osnr": 45,
"sys_margins": 0
}
def __init__(self, **kwargs):
self.update_attr(self.default_values, kwargs, 'SI')
class Span(common):
default_values = \
{
'power_mode': True,
'delta_power_range_db': None,
'max_fiber_lineic_loss_for_raman': 0.25,
'target_extended_gain': 2.5,
'max_length': 150,
'length_units': 'km',
'max_loss': None,
'padding': 10,
'EOL': 0,
'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
}
def __init__(self, **kwargs):
self.update_attr(self.default_values, kwargs, 'Roadm')
class Transceiver(common):
default_values = \
{
'type_variety': None,
'frequency': None,
'mode': {}
}
def __init__(self, **kwargs):
self.update_attr(self.default_values, kwargs, 'Transceiver')
class Fiber(common):
default_values = \
{
'type_variety': '',
'dispersion': None,
'gamma': 0
}
def __init__(self, **kwargs):
self.update_attr(self.default_values, kwargs, 'Fiber')
class Amp(common):
default_values = \
{
'type_variety': '',
'type_def': '',
'gain_flatmax': None,
'gain_min': None,
'p_max': None,
'nf_model': None,
'dual_stage_model': None,
'nf_fit_coeff': None,
'nf_ripple': None,
'dgt': None,
'gain_ripple': None,
'out_voa_auto': False,
'allowed_for_design': False,
'raman': False
}
def __init__(self, **kwargs):
self.update_attr(self.default_values, kwargs, 'Amp')
@classmethod
def from_advanced_json(cls, filename, **kwargs):
with open(filename, encoding='utf-8') as f:
json_data = load(f)
return cls(**{**kwargs, **json_data, 'type_def':None, 'nf_model':None})
def from_json(cls, filename, **kwargs):
config = Path(filename).parent / 'default_edfa_config.json'
@classmethod
def from_default_json(cls, filename, **kwargs):
with open(filename, encoding='utf-8') as f:
json_data = load(f)
type_variety = kwargs['type_variety']
type_def = kwargs.get('type_def', 'variable_gain') #default compatibility with older json eqpt files
nf_def = None
dual_stage_def = None
if type_def == 'fixed_gain':
try:
@@ -67,6 +147,8 @@ class Amp(AmpBase):
del kwargs['nf_max']
except KeyError: pass #nf_min and nf_max are not needed for fixed gain amp
nf_def = Model_fg(nf0)
elif type_def == 'advanced_model':
config = Path(filename).parent / kwargs.pop('advanced_config_from_json')
elif type_def == 'variable_gain':
gain_min, gain_max = kwargs['gain_min'], kwargs['gain_flatmax']
try: #nf_min and nf_max are expected for a variable gain amp
@@ -87,7 +169,20 @@ class Amp(AmpBase):
print(f'missing nf_coef input for amplifier: {type_variety} in eqpt_config.json')
exit()
nf_def = Model_openroadm(nf_coef)
return cls(**{**kwargs, **json_data, 'nf_model': nf_def})
elif type_def == 'dual_stage':
try: #nf_ram and gain_ram are expected for a hybrid amp
preamp_variety = kwargs.pop('preamp_variety')
booster_variety = kwargs.pop('booster_variety')
except KeyError:
print(f'missing preamp/booster variety input for amplifier: {type_variety} in eqpt_config.json')
exit()
dual_stage_def = Model_dual_stage(preamp_variety, booster_variety)
with open(config, encoding='utf-8') as f:
json_data = load(f)
return cls(**{**kwargs, **json_data,
'nf_model': nf_def, 'dual_stage_model': dual_stage_def})
def nf_model(type_variety, gain_min, gain_max, nf_min, nf_max):
@@ -123,7 +218,7 @@ def nf_model(type_variety, gain_min, gain_max, nf_min, nf_max):
g1a_max = lin2db(db2lin(nf2) / (db2lin(nf_min) - db2lin(nf1)))
delta_p = gain_max - g1a_max
g1a_min = gain_min - (gain_max-gain_min) - delta_p
if not 1 < delta_p < 6:
if not 1 < delta_p < 11:
print(f'Computed \N{greek capital letter delta}P invalid \
\n 1st coil vs 2nd coil calculated DeltaP {delta_p:.2f} for \
\n amplifier {type_variety} is not valid: revise inputs \
@@ -145,7 +240,7 @@ def edfa_nf(gain_target, variety_type, equipment):
amp_params = equipment['Edfa'][variety_type]
amp = Edfa(
uid = f'calc_NF',
params = amp_params._asdict(),
params = amp_params.__dict__,
operational = {
'gain_target': gain_target,
'tilt_target': 0
@@ -243,6 +338,30 @@ def update_trx_osnr(equipment):
m['OSNR'] = m['OSNR'] + equipment['SI']['default'].sys_margins
return equipment
def update_dual_stage(equipment):
edfa_dict = equipment['Edfa']
for edfa in edfa_dict.values():
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)
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:
print(
f'\x1b[1;31;40m'\
+ f'CRITICAL: dual stage {edfa.type_variety} min gain is lower than its preamp min gain\
=> please increase its min gain in eqpt_config.json'\
+ '\x1b[0m'
)
exit()
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
@@ -259,13 +378,9 @@ def equipment_from_json(json_data, filename):
for entry in entries:
subkey = entry.get('type_variety', 'default')
if key == 'Edfa':
if 'advanced_config_from_json' in entry:
config = Path(filename).parent / entry.pop('advanced_config_from_json')
equipment[key][subkey] = Amp.from_advanced_json(config, **entry)
else:
config = Path(filename).parent / 'default_edfa_config.json'
equipment[key][subkey] = Amp.from_default_json(config, **entry)
equipment[key][subkey] = Amp.from_json(filename, **entry)
else:
equipment[key][subkey] = typ(**entry)
equipment = update_trx_osnr(equipment)
equipment = update_dual_stage(equipment)
return equipment

View File

@@ -30,7 +30,7 @@ class ConvenienceAccess:
class Power(namedtuple('Power', 'signal nonlinear_interference amplified_spontaneous_emission'), ConvenienceAccess):
"""carriers power in W"""
_ABBREVS = {'nli': 'nonlinear_interference',
'ase': 'amplified_spontaneous_emission',}
@@ -42,14 +42,18 @@ class Channel(namedtuple('Channel', 'channel_number frequency baud_rate roll_off
'ffs': 'frequency',
'freq': 'frequency',}
class Pref(namedtuple('Pref', 'p_span0, p_spani'), ConvenienceAccess):
class Pref(namedtuple('Pref', 'p_span0, p_spani, neq_ch '), ConvenienceAccess):
"""noiseless reference power in dBm:
p0: inital target carrier power
pi: carrier power after element i
neqch: equivalent channel count in dB"""
_ABBREVS = {'p0' : 'p_span0',
'pi' : 'p_spani'}
class SpectralInformation(namedtuple('SpectralInformation', 'pref carriers'), ConvenienceAccess):
def __new__(cls, pref=Pref(0, 0), *carriers):
def __new__(cls, pref, carriers):
return super().__new__(cls, pref, carriers)
def merge_input_spectral_information(*si):
@@ -60,9 +64,10 @@ def merge_input_spectral_information(*si):
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)
si = SpectralInformation(pref=Pref(pref, pref))
nb_channel = automatic_nch(f_min, f_max, spacing)
si = si.update(carriers=[
si = SpectralInformation(
pref=Pref(pref, pref, lin2db(nb_channel)),
carriers=[
Channel(f, (f_min+spacing*f),
baud_rate, roll_off, Power(power, 0, 0)) for f in range(1,nb_channel+1)
])

View File

@@ -14,7 +14,7 @@ from numpy import arange
from scipy.interpolate import interp1d
from logging import getLogger
from os import path
from operator import itemgetter
from operator import itemgetter, attrgetter
from gnpy.core import elements
from gnpy.core.elements import Fiber, Edfa, Transceiver, Roadm, Fused
from gnpy.core.equipment import edfa_nf
@@ -50,9 +50,9 @@ def network_from_json(json_data, equipment):
for el_config in json_data['elements']:
typ = el_config.pop('type')
variety = el_config.pop('type_variety', 'default')
if typ in equipment and variety in equipment[typ]:
if typ in equipment and variety in equipment[typ]:
extra_params = equipment[typ][variety]
el_config.setdefault('params', {}).update(extra_params._asdict())
el_config.setdefault('params', {}).update(extra_params.__dict__)
elif typ in ['Edfa', 'Fiber']: #catch it now because the code will crash later!
print( f'The {typ} of variety type {variety} was not recognized:'
'\nplease check it is properly defined in the eqpt_config json file')
@@ -87,17 +87,17 @@ def network_to_json(network):
data.update(connections)
return data
def select_edfa(gain_target, power_target, equipment):
def select_edfa(raman_allowed, gain_target, power_target, equipment, uid):
"""amplifer selection algorithm
@Orange Jean-Luc Augé
"""
Edfa_list = namedtuple('Edfa_list', 'variety power gain nf')
TARGET_EXTENDED_GAIN = 2.1
#MAX_EXTENDED_GAIN = 5
Edfa_list = namedtuple('Edfa_list', 'raman variety power gain_min nf')
TARGET_EXTENDED_GAIN = equipment['Span']['default'].target_extended_gain
edfa_dict = equipment['Edfa']
pin = power_target - gain_target
edfa_list = [Edfa_list(
raman=edfa.raman,
variety=edfa_variety,
power=min(
pin
@@ -106,76 +106,87 @@ def select_edfa(gain_target, power_target, equipment):
edfa.p_max
)
-power_target,
gain=edfa.gain_flatmax-gain_target,
gain_min=
gain_target+3
-edfa.gain_min,
nf=edfa_nf(gain_target, edfa_variety, equipment)) \
for edfa_variety, edfa in edfa_dict.items()
if edfa.allowed_for_design]
acceptable_gain_list = \
list(filter(lambda x : x.gain>-TARGET_EXTENDED_GAIN, edfa_list))
if len(acceptable_gain_list) < 1:
#no amplifier satisfies the required gain, so pick the highest gain:
gain_max = max(edfa_list, key=itemgetter(2)).gain
#pick up all amplifiers that share this max gain:
acceptable_gain_list = \
list(filter(lambda x : x.gain-gain_max>-0.1, edfa_list))
#filter on raman restriction
raman_filter = lambda edfa: (edfa.raman and raman_allowed) or not edfa.raman
edfa_list = list(filter(raman_filter, edfa_list))
#print(f'\n{uid}, gain {gain_target}, {power_target}')
#print('edfa',edfa_list)
#filter on min gain limitation:
#consider gain_target+3 to allow some operation below min gain
#(~counterpart to the extended gain range)
acceptable_gain_min_list = \
list(filter(lambda x : x.gain_min>0, edfa_list))
if len(acceptable_gain_min_list) < 1:
#do not take this empty list into account for the rest of the code
#but issue a warning to the user
print(
f'\x1b[1;31;40m'\
+ f'WARNING: target gain in node {uid} is below all available amplifiers min gain: \
amplifier input padding will be assumed, consider increase fiber padding instead'\
+ '\x1b[0m'
)
else:
edfa_list = acceptable_gain_min_list
#print('gain_min', acceptable_gain_min_list)
#filter on max power limitation:
acceptable_power_list = \
list(filter(lambda x : x.power>=0, acceptable_gain_list))
list(filter(lambda x : x.power>=0, edfa_list))
if len(acceptable_power_list) < 1:
#no amplifier satisfies the required power, so pick the highest power:
power_max = \
max(acceptable_gain_list, key=itemgetter(1)).power
power_max = max(edfa_list, key=attrgetter('power')).power
#pick up all amplifiers that share this max gain:
acceptable_power_list = \
list(filter(lambda x : x.power-power_max>-0.1, acceptable_gain_list))
list(filter(lambda x : x.power-power_max>-0.3, edfa_list))
#print('power', acceptable_power_list)
# debug:
# print(gain_target, power_target, '=>\n',acceptable_power_list)
# gain and power requirements are resolved,
# =>chose the amp with the best NF among the acceptable ones:
return min(acceptable_power_list, key=itemgetter(3)).variety #filter on NF
selected_edfa = min(acceptable_power_list, key=attrgetter('nf')) #filter on NF
power_reduction = round(min(selected_edfa.power, 0),2)
if power_reduction < -0.5:
print(
f'\x1b[1;31;40m'\
+ f'WARNING: target gain and power in node {uid}\n \
is beyond all available amplifiers capabilities and/or extended_gain_range:\n\
a power reduction of {power_reduction} is applied\n'\
+ '\x1b[0m'
)
return selected_edfa.variety, power_reduction
def set_roadm_loss(network, equipment, pref_ch_db):
roadms = [roadm for roadm in network if isinstance(roadm, Roadm)]
power_mode = equipment['Spans']['default'].power_mode
default_roadm_loss = equipment['Roadms']['default'].gain_mode_default_loss
pout_target = equipment['Roadms']['default'].power_mode_pout_target
roadm_loss = pref_ch_db - pout_target
for roadm in roadms:
if power_mode:
roadm.loss = roadm_loss
roadm.target_pch_out_db = pout_target
elif roadm.loss == None:
roadm.loss = default_roadm_loss
def target_power(dp_from_gain, network, node, equipment): #get_fiber_dp
def target_power(network, node, equipment): #get_fiber_dp
SPAN_LOSS_REF = 20
POWER_SLOPE = 0.3
power_mode = equipment['Spans']['default'].power_mode
dp_range = list(equipment['Spans']['default'].delta_power_range_db)
power_mode = equipment['Span']['default'].power_mode
dp_range = list(equipment['Span']['default'].delta_power_range_db)
node_loss = span_loss(network, node)
dp_gain_mode = 0
try:
dp_power_mode = round2float((node_loss - SPAN_LOSS_REF) * POWER_SLOPE, dp_range[2])
dp_power_mode = max(dp_range[0], dp_power_mode)
dp_power_mode = min(dp_range[1], dp_power_mode)
dp = round2float((node_loss - SPAN_LOSS_REF) * POWER_SLOPE, dp_range[2])
dp = max(dp_range[0], dp)
dp = min(dp_range[1], dp)
except KeyError:
print(f'invalid delta_power_range_db definition in eqpt_config[Spans]'
print(f'invalid delta_power_range_db definition in eqpt_config[Span]'
f'delta_power_range_db: [lower_bound, upper_bound, step]')
exit()
if dp_from_gain:
dp_power_mode = dp_from_gain
dp_gain_mode = dp_from_gain
if isinstance(node, Roadm):
dp_power_mode = 0
dp = dp_power_mode if power_mode else dp_gain_mode
#print(f'{repr(node)} delta power in:\n{dp}dB')
dp = 0
return dp
def prev_node_generator(network, node):
"""fused spans interest:
iterate over all predecessors while they are Fused or Fiber type"""
@@ -244,23 +255,22 @@ def find_last_node(network, node):
pass
return this_node
def set_amplifier_voa(amp, pref_total_db, power_mode):
VOA_MARGIN = 0
if amp.operational.out_voa is None:
def set_amplifier_voa(amp, power_target, power_mode):
VOA_MARGIN = 1 #do not maximize the VOA optimization
if amp.out_voa is None:
if power_mode:
gain_target = amp.operational.gain_target
pout = pref_total_db + amp.dp_db
voa = min(amp.params.p_max-pout,
amp.params.gain_flatmax-amp.operational.gain_target)
voa = round2float(max(voa, 0), 0.5) - VOA_MARGIN if amp.params.out_voa_auto else 0
amp.dp_db = amp.dp_db + voa
amp.operational.gain_target = amp.operational.gain_target + voa
gain_target = amp.effective_gain
voa = min(amp.params.p_max-power_target,
amp.params.gain_flatmax-amp.effective_gain)
voa = max(round2float(max(voa, 0), 0.5) - VOA_MARGIN, 0) if amp.params.out_voa_auto else 0
amp.delta_p = amp.delta_p + voa
amp.effective_gain = amp.effective_gain + voa
else:
voa = 0 # no output voa optimization in gain mode
amp.operational.out_voa = voa
amp.out_voa = voa
def set_egress_amplifier(network, roadm, equipment, pref_total_db):
power_mode = equipment['Spans']['default'].power_mode
power_mode = equipment['Span']['default'].power_mode
next_oms = (n for n in network.successors(roadm) if not isinstance(n, Transceiver))
for oms in next_oms:
#go through all the OMS departing from the Roadm
@@ -271,30 +281,49 @@ def set_egress_amplifier(network, roadm, equipment, pref_total_db):
# node = find_last_node(next_node)
# next_node = next(n for n in network.successors(node))
# next_node = find_last_node(next_node)
prev_dp = 0
dp = 0
prev_dp = getattr(node.params, 'target_pch_out_db', 0)
dp = prev_dp
prev_voa = 0
voa = 0
while True:
#go through all nodes in the OMS (loop until next Roadm instance)
if isinstance(node, Edfa):
node_loss = span_loss(network, prev_node)
dp_from_gain = prev_dp + node.operational.gain_target - node_loss \
if node.operational.gain_target > 0 else None
dp = target_power(dp_from_gain, network, next_node, equipment)
gain_target = node_loss + dp - prev_dp
if node.out_voa:
voa = node.out_voa
if node.delta_p is None:
dp = target_power(network, next_node, equipment)
else:
dp = node.delta_p
gain_from_dp = node_loss + dp - prev_dp + prev_voa
if node.effective_gain is None or power_mode:
gain_target = gain_from_dp
else: #gain mode with effective_gain
gain_target = node.effective_gain
dp = prev_dp - node_loss + gain_target
#print(node.delta_p, dp, gain_target)
power_target = pref_total_db + dp
if power_mode:
node.dp_db = dp
node.operational.gain_target = gain_target
if node.params.type_variety == '':
power_target = pref_total_db + dp
edfa_variety = select_edfa(gain_target, power_target, equipment)
if node.params.type_variety == '' :
raman_allowed = False
if isinstance(prev_node, Fiber):
max_fiber_lineic_loss_for_raman = \
equipment['Span']['default'].max_fiber_lineic_loss_for_raman
raman_allowed = prev_node.params.loss_coef < max_fiber_lineic_loss_for_raman
edfa_variety, power_reduction = select_edfa(raman_allowed,
gain_target, power_target, equipment, node.uid)
extra_params = equipment['Edfa'][edfa_variety]
node.params.update_params(extra_params._asdict())
set_amplifier_voa(node, pref_total_db, power_mode)
node.params.update_params(extra_params.__dict__)
dp += power_reduction
gain_target += power_reduction
node.delta_p = dp if power_mode else None
node.effective_gain = gain_target
set_amplifier_voa(node, power_target, power_mode)
if isinstance(next_node, Roadm) or isinstance(next_node, Transceiver):
break
prev_dp = dp
prev_voa = voa
prev_node = node
node = next_node
# print(f'{node.uid}')
@@ -319,7 +348,7 @@ def add_egress_amplifier(network, node):
}
},
operational = {
'gain_target': 0,
'gain_target': None,
'tilt_target': 0,
})
network.add_node(amp)
@@ -421,7 +450,7 @@ def add_fiber_padding(network, fibers, padding):
first_fiber.att_in = first_fiber.att_in + padding - this_span_loss
def build_network(network, equipment, pref_ch_db, pref_total_db):
default_span_data = equipment['Spans']['default']
default_span_data = equipment['Span']['default']
max_length = int(default_span_data.max_length * UNITS[default_span_data.length_units])
min_length = max(int(default_span_data.padding/0.2*1e3),50_000)
bounds = range(min_length, max_length)
@@ -431,7 +460,6 @@ def build_network(network, equipment, pref_ch_db, pref_total_db):
padding = default_span_data.padding
#set raodm loss for gain_mode before to build network
set_roadm_loss(network, equipment, pref_ch_db)
fibers = [f for f in network.nodes() if isinstance(f, Fiber)]
add_connector_loss(fibers, con_in, con_out, default_span_data.EOL)
add_fiber_padding(network, fibers, padding)

View File

@@ -23,7 +23,6 @@ from networkx.utils import pairwise
from numpy import mean
from gnpy.core.service_sheet import convert_service_sheet, Request_element, Element
from gnpy.core.elements import Transceiver, Roadm, Edfa, Fused
from gnpy.core.network import set_roadm_loss
from gnpy.core.utils import db2lin, lin2db
from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power
from copy import copy, deepcopy
@@ -386,8 +385,6 @@ def compute_constrained_path(network, req):
return total_path
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.f_min, req.f_max, req.roll_off, req.baud_rate,
req.power, req.spacing)
@@ -395,12 +392,10 @@ def propagate(path, req, equipment, show=False):
si = el(si)
if show :
print(el)
path[-1].update_snr(req.tx_osnr, equipment['Roadms']['default'].add_drop_osnr)
path[-1].update_snr(req.tx_osnr, equipment['Roadm']['default'].add_drop_osnr)
return path
def propagate2(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.f_min, req.f_max, req.roll_off, req.baud_rate,
req.power, req.spacing)
@@ -411,12 +406,10 @@ def propagate2(path, req, equipment, show=False):
infos[el] = before_si, after_si
if show :
print(el)
path[-1].update_snr(req.tx_osnr, equipment['Roadms']['default'].add_drop_osnr)
path[-1].update_snr(req.tx_osnr, equipment['Roadm']['default'].add_drop_osnr)
return infos
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
# 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
@@ -442,7 +435,7 @@ def propagate_and_optimize_mode(path, req, equipment):
si = el(si)
for m in modes_to_explore :
if path[-1].snr is not None:
path[-1].update_snr(m['tx_osnr'], equipment['Roadms']['default'].add_drop_osnr)
path[-1].update_snr(m['tx_osnr'], equipment['Roadm']['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

View File

@@ -1,9 +1,11 @@
{ "Edfa":[{
"type_variety": "CienaDB_medium_gain",
"type_def": "advanced_model",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false,
"allowed_for_design": true
},
{
@@ -52,7 +54,7 @@
"gamma": 0.00127
}
],
"Spans":[{
"Span":[{
"power_mode": true,
"delta_power_range_db": [0,0,1],
"max_length": 150,
@@ -64,8 +66,7 @@
"con_out": 0
}
],
"Roadms":[{
"gain_mode_default_loss": 20,
"Roadm":[{
"power_mode_pout_target": -20,
"add_drop_osnr": 100
}],

View File

@@ -469,9 +469,10 @@
"type": "Edfa",
"type_variety": "test",
"operational": {
"gain_target": 0,
"gain_target": null,
"delta_p": null,
"tilt_target": 0,
"out_voa": 0
"out_voa": null
}
},
{
@@ -487,9 +488,10 @@
"type": "Edfa",
"type_variety": "test",
"operational": {
"gain_target": 0,
"gain_target": null,
"delta_p": null,
"tilt_target": 0,
"out_voa": 0
"out_voa": null
}
},
{
@@ -505,9 +507,10 @@
"type": "Edfa",
"type_variety": "test",
"operational": {
"gain_target": 0,
"gain_target": null,
"delta_p": null,
"tilt_target": 0,
"out_voa": 0
"out_voa": null
}
},
{
@@ -523,9 +526,10 @@
"type": "Edfa",
"type_variety": "test",
"operational": {
"gain_target": 0,
"gain_target": null,
"delta_p": null,
"tilt_target": 0,
"out_voa": 0
"out_voa": null
}
}
],

View File

@@ -577,9 +577,10 @@
"type": "Edfa",
"type_variety": "test",
"operational": {
"gain_target": 0,
"gain_target": null,
"delta_p": null,
"tilt_target": 0,
"out_voa": 0
"out_voa": null
}
},
{
@@ -595,9 +596,10 @@
"type": "Edfa",
"type_variety": "test",
"operational": {
"gain_target": 0,
"gain_target": null,
"delta_p": null,
"tilt_target": 0,
"out_voa": 0
"out_voa": null
}
}
],

View File

@@ -9,8 +9,8 @@ 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, automatic_fmax
from gnpy.core.network import build_network, load_network, set_roadm_loss
from gnpy.core.equipment import load_equipment, automatic_fmax, automatic_nch
from gnpy.core.network import build_network, load_network
from pathlib import Path
import pytest
@@ -79,7 +79,7 @@ def test_variable_gain_nf(gain, nf_expected, setup_edfa_variable_gain, si):
pin = pin/db2lin(gain)
baud_rates = array([c.baud_rate for c in si.carriers])
edfa.operational.gain_target = gain
pref = Pref(0, -gain)
pref = Pref(0, -gain, lin2db(len(frequencies)))
edfa.interpol_params(frequencies, pin, baud_rates, pref)
result = edfa.nf
assert pytest.approx(nf_expected, abs=0.01) == result[0]
@@ -93,7 +93,7 @@ def test_fixed_gain_nf(gain, nf_expected, setup_edfa_fixed_gain, si):
pin = pin/db2lin(gain)
baud_rates = array([c.baud_rate for c in si.carriers])
edfa.operational.gain_target = gain
pref = Pref(0, -gain)
pref = Pref(0, -gain, lin2db(len(frequencies)))
edfa.interpol_params(frequencies, pin, baud_rates, pref)
assert pytest.approx(nf_expected, abs=0.01) == edfa.nf[0]
@@ -118,7 +118,7 @@ def test_compare_nf_models(gain, setup_edfa_variable_gain, si):
pin = pin/db2lin(gain)
baud_rates = array([c.baud_rate for c in si.carriers])
edfa.operational.gain_target = gain
pref = Pref(0, -gain)
pref = Pref(0, -gain, lin2db(len(frequencies)))
edfa.interpol_params(frequencies, pin, baud_rates, pref)
nf_model = edfa.nf[0]
edfa.interpol_params(frequencies, pin, baud_rates, pref)
@@ -137,7 +137,7 @@ def test_ase_noise(gain, si, setup_edfa_variable_gain, setup_trx, bw):
pin = array([c.power.signal+c.power.nli+c.power.ase for c in si.carriers])
baud_rates = array([c.baud_rate for c in si.carriers])
edfa.operational.gain_target = gain
pref = Pref(0, 0)
pref = Pref(0, 0, lin2db(len(frequencies)))
edfa.interpol_params(frequencies, pin, baud_rates, pref)
nf = edfa.nf
pin = lin2db(pin[0]*1e3)

View File

@@ -47,12 +47,8 @@ def propagation(input_power, con_in, con_out,dest):
p = input_power
p = db2lin(p) * 1e-3
spacing = 0.05 # THz
si = SpectralInformation() # SI units: W, Hz
si = si.update(carriers=[
Channel(f, (191.3 + spacing * f) * 1e12, 32e9, 0.15, Power(p, 0, 0))
for f in range(1,80)
])
spacing = 50e9 # THz
si = create_input_spectral_information(191.3e12, 191.3e12+79*spacing, 0.15, 32e9, p, spacing)
source = next(transceivers[uid] for uid in transceivers if uid == 'trx A')
sink = next(transceivers[uid] for uid in transceivers if uid == dest)
path = dijkstra_path(network, source, sink)