From 72ce4e2fad6ef6aaad5af9ef197123e826221208 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Sat, 18 May 2019 15:39:48 +0200 Subject: [PATCH 001/117] add json sim_params --- examples/sim_params.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/sim_params.json diff --git a/examples/sim_params.json b/examples/sim_params.json new file mode 100644 index 000000000..765132498 --- /dev/null +++ b/examples/sim_params.json @@ -0,0 +1,3 @@ +{ + "raman_feature": true +} From 0e2316513e62c29c88f86b6129670071d8c814b7 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Sat, 18 May 2019 15:41:59 +0200 Subject: [PATCH 002/117] add raman transmission --- .../transmission_with_raman_main_example.py | 283 ++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100755 examples/transmission_with_raman_main_example.py diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py new file mode 100755 index 000000000..c442bb04a --- /dev/null +++ b/examples/transmission_with_raman_main_example.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +''' +transmission_main_example.py +============================ + +Main example for transmission simulation. + +Reads from network JSON (by default, `edfa_example_network.json`) +''' + +from gnpy.core.equipment import load_equipment, trx_mode_params +from gnpy.core.utils import db2lin, lin2db, write_csv +from argparse import ArgumentParser +from sys import exit +from pathlib import Path +from json import loads +from collections import Counter +from logging import getLogger, basicConfig, INFO, ERROR, DEBUG +from numpy import linspace, mean +from matplotlib.pyplot import show, axis, figure, title, text +from networkx import (draw_networkx_nodes, draw_networkx_edges, + draw_networkx_labels, dijkstra_path) +from gnpy.core.network import load_network, build_network, save_network +from gnpy.core.elements import Transceiver, Fiber, Edfa, Roadm +from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power, Pref +from gnpy.core.request import Path_request, RequestParams, compute_constrained_path, propagate2 + +logger = getLogger(__name__) + +def plot_baseline(network): + edges = set(network.edges()) + pos = {n: (n.lng, n.lat) for n in network.nodes()} + labels = {n: n.location.city for n in network.nodes() if isinstance(n, Transceiver)} + city_labels = set(labels.values()) + for n in network.nodes(): + if n.location.city and n.location.city not in city_labels: + labels[n] = n.location.city + city_labels.add(n.location.city) + label_pos = pos + + fig = figure() + kwargs = {'figure': fig, 'pos': pos} + plot = draw_networkx_nodes(network, nodelist=network.nodes(), node_color='#ababab', **kwargs) + draw_networkx_edges(network, edgelist=edges, edge_color='#ababab', **kwargs) + draw_networkx_labels(network, labels=labels, font_size=14, **{**kwargs, 'pos': label_pos}) + axis('off') + show() + +def plot_results(network, path, source, destination, infos): + path_edges = set(zip(path[:-1], path[1:])) + edges = set(network.edges()) - path_edges + pos = {n: (n.lng, n.lat) for n in network.nodes()} + nodes = {} + for k, (x, y) in pos.items(): + nodes.setdefault((round(x, 1), round(y, 1)), []).append(k) + labels = {n: n.location.city for n in network.nodes() if isinstance(n, Transceiver)} + city_labels = set(labels.values()) + for n in network.nodes(): + if n.location.city and n.location.city not in city_labels: + labels[n] = n.location.city + city_labels.add(n.location.city) + label_pos = pos + + fig = figure() + kwargs = {'figure': fig, 'pos': pos} + all_nodes = [n for n in network.nodes() if n not in path] + plot = draw_networkx_nodes(network, nodelist=all_nodes, node_color='#ababab', node_size=50, **kwargs) + draw_networkx_nodes(network, nodelist=path, node_color='#ff0000', node_size=55, **kwargs) + draw_networkx_edges(network, edgelist=edges, edge_color='#ababab', **kwargs) + draw_networkx_edges(network, edgelist=path_edges, edge_color='#ff0000', **kwargs) + draw_networkx_labels(network, labels=labels, font_size=14, **{**kwargs, 'pos': label_pos}) + title(f'Propagating from {source.loc.city} to {destination.loc.city}') + axis('off') + + heading = 'Spectral Information\n\n' + textbox = text(0.85, 0.20, heading, fontsize=14, fontname='Ubuntu Mono', + verticalalignment='top', transform=fig.axes[0].transAxes, + bbox={'boxstyle': 'round', 'facecolor': 'wheat', 'alpha': 0.5}) + + msgs = {(x, y): heading + '\n\n'.join(str(n) for n in ns if n in path) + for (x, y), ns in nodes.items()} + + def hover(event): + if event.xdata is None or event.ydata is None: + return + if fig.contains(event): + x, y = round(event.xdata, 1), round(event.ydata, 1) + if (x, y) in msgs: + textbox.set_text(msgs[x, y]) + else: + textbox.set_text(heading) + fig.canvas.draw_idle() + + fig.canvas.mpl_connect('motion_notify_event', hover) + show() + + +def main(network, equipment, source, destination, req = None): + result_dicts = {} + network_data = [{ + 'network_name' : str(args.filename), + 'source' : source.uid, + 'destination' : destination.uid + }] + result_dicts.update({'network': network_data}) + design_data = [{ + '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 + }] + result_dicts.update({'design': design_data}) + simulation_data = [] + result_dicts.update({'simulation results': simulation_data}) + + 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 - 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) + build_network(network, equipment, pref_ch_db, pref_total_db) + path = compute_constrained_path(network, req) + + spans = [s.length for s in path if isinstance(s, Fiber)] + print(f'\nThere are {len(spans)} fiber spans over {sum(spans):.0f}m between {source.uid} and {destination.uid}') + print(f'\nNow propagating between {source.uid} and {destination.uid}:') + + try: + p_start, p_stop, p_step = equipment['SI']['default'].power_range_db + p_num = abs(int(round((p_stop - p_start)/p_step))) + 1 if p_step != 0 else 1 + power_range = list(linspace(p_start, p_stop, p_num)) + except TypeError: + 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 + 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) + 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 + 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 + + +parser = ArgumentParser() +parser.add_argument('-e', '--equipment', type=Path, + default=Path(__file__).parent / 'eqpt_config.json') +parser.add_argument('-pl', '--plot', action='store_true') +parser.add_argument('-v', '--verbose', action='count', default=0, help='increases verbosity for each occurence') +parser.add_argument('-l', '--list-nodes', action='store_true', help='list all transceiver nodes') +parser.add_argument('-po', '--power', default=0, help='channel ref power in dBm') +parser.add_argument('-names', '--names-matching', action='store_true', help='display network names that are closed matches') +parser.add_argument('filename', nargs='?', type=Path, + default=Path(__file__).parent / 'edfa_example_network.json') +parser.add_argument('source', nargs='?', help='source node') +parser.add_argument('destination', nargs='?', help='destination node') + + +if __name__ == '__main__': + args = parser.parse_args() + basicConfig(level={0: ERROR, 1: INFO, 2: DEBUG}.get(args.verbose, DEBUG)) + + equipment = load_equipment(args.equipment) + network = load_network(args.filename, equipment, args.names_matching) + + if args.plot: + plot_baseline(network) + + transceivers = {n.uid: n for n in network.nodes() if isinstance(n, Transceiver)} + + if not transceivers: + exit('Network has no transceivers!') + if len(transceivers) < 2: + exit('Network has only one transceiver!') + + if args.list_nodes: + for uid in transceivers: + print(uid) + exit() + + #First try to find exact match if source/destination provided + if args.source: + source = transceivers.pop(args.source, None) + valid_source = True if source else False + else: + source = None + logger.info('No source node specified: picking random transceiver') + + if args.destination: + destination = transceivers.pop(args.destination, None) + valid_destination = True if destination else False + else: + destination = None + logger.info('No destination node specified: picking random transceiver') + + #If no exact match try to find partial match + if args.source and not source: + #TODO code a more advanced regex to find nodes match + source = next((transceivers.pop(uid) for uid in transceivers \ + if args.source.lower() in uid.lower()), None) + + if args.destination and not destination: + #TODO code a more advanced regex to find nodes match + destination = next((transceivers.pop(uid) for uid in transceivers \ + if args.destination.lower() in uid.lower()), None) + + #If no partial match or no source/destination provided pick random + if not source: + source = list(transceivers.values())[0] + del transceivers[source.uid] + + if not destination: + destination = list(transceivers.values())[0] + + logger.info(f'source = {args.source!r}') + logger.info(f'destination = {args.destination!r}') + + params = {} + params['request_id'] = 0 + params['trx_type'] = '' + params['trx_mode'] = '' + params['source'] = source.uid + params['destination'] = destination.uid + params['nodes_list'] = [destination.uid] + params['loose_list'] = ['strict'] + params['format'] = '' + params['path_bandwidth'] = 0 + trx_params = trx_mode_params(equipment) + if args.power: + trx_params['power'] = db2lin(float(args.power))*1e-3 + params.update(trx_params) + req = Path_request(**params) + path, infos = main(network, equipment, source, destination, req) + save_network(args.filename, network) + + if not args.source: + print(f'\n(No source node specified: picked {source.uid})') + elif not valid_source: + print(f'\n(Invalid source node {args.source!r} replaced with {source.uid})') + + if not args.destination: + print(f'\n(No destination node specified: picked {destination.uid})') + elif not valid_destination: + print(f'\n(Invalid destination node {args.destination!r} replaced with {destination.uid})') + + if args.plot: + plot_results(network, path, source, destination, infos) From aadd038bbe012b5055e2f3e867500f076796519e Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Mon, 20 May 2019 17:17:34 +0200 Subject: [PATCH 003/117] transmission w Raman gets sim params --- examples/transmission_with_raman_main_example.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index c442bb04a..868fec009 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -11,7 +11,7 @@ ''' from gnpy.core.equipment import load_equipment, trx_mode_params -from gnpy.core.utils import db2lin, lin2db, write_csv +from gnpy.core.utils import db2lin, lin2db, write_csv, load_json from argparse import ArgumentParser from sys import exit from pathlib import Path @@ -97,7 +97,7 @@ def hover(event): show() -def main(network, equipment, source, destination, req = None): +def main(network, equipment, source, destination, sim_params, req = None): result_dicts = {} network_data = [{ 'network_name' : str(args.filename), @@ -181,6 +181,8 @@ def main(network, equipment, source, destination, req = None): parser = ArgumentParser() parser.add_argument('-e', '--equipment', type=Path, default=Path(__file__).parent / 'eqpt_config.json') +parser.add_argument('-sim', '--sim-params', type=Path, + default=Path(__file__).parent / 'sim_params.json', help='json containing simulation parameters') parser.add_argument('-pl', '--plot', action='store_true') parser.add_argument('-v', '--verbose', action='count', default=0, help='increases verbosity for each occurence') parser.add_argument('-l', '--list-nodes', action='store_true', help='list all transceiver nodes') @@ -214,6 +216,8 @@ def main(network, equipment, source, destination, req = None): print(uid) exit() + sim_params = load_json(args.sim_params) + #First try to find exact match if source/destination provided if args.source: source = transceivers.pop(args.source, None) @@ -266,7 +270,7 @@ def main(network, equipment, source, destination, req = None): trx_params['power'] = db2lin(float(args.power))*1e-3 params.update(trx_params) req = Path_request(**params) - path, infos = main(network, equipment, source, destination, req) + path, infos = main(network, equipment, source, destination, sim_params, req) save_network(args.filename, network) if not args.source: From 2cb3858330236ead8265ee875783899f0839218f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 21 May 2019 16:20:26 +0200 Subject: [PATCH 004/117] Exceptions must be raised, not just instantiated Found by lgtm.com code scanner, thanks! --- gnpy/core/convert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnpy/core/convert.py b/gnpy/core/convert.py index da5f2634e..9e5d3a1d6 100755 --- a/gnpy/core/convert.py +++ b/gnpy/core/convert.py @@ -467,10 +467,10 @@ def parse_excel(input_filename): # sanity check all_cities = Counter(n.city for n in nodes) if len(all_cities) != len(nodes): - ValueError(f'Duplicate city: {all_cities}') + raise ValueError(f'Duplicate city: {all_cities}') if any(ln.from_city not in all_cities or ln.to_city not in all_cities for ln in links): - ValueError(f'Bad link.') + raise ValueError(f'Bad link.') return nodes, links, eqpts From 3c20d57cc4a3e6a0e089edace0404d954006c37f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 21 May 2019 16:29:09 +0200 Subject: [PATCH 005/117] Be sure that both nf_min and nf_max are removed If `nf_min` was not present, then `nf_max` would be left remaining because an attempt to nuke `nf_min` raised an exception. Thanks to codecov.io for making it easy to spot this on the coverage page. --- gnpy/core/equipment.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gnpy/core/equipment.py b/gnpy/core/equipment.py index 456e3a041..2aa10d665 100644 --- a/gnpy/core/equipment.py +++ b/gnpy/core/equipment.py @@ -144,10 +144,11 @@ def from_json(cls, filename, **kwargs): except KeyError: #nf0 is expected for a fixed gain amp print(f'missing nf0 value input for amplifier: {type_variety} in eqpt_config.json') exit() - try: #remove all remaining nf inputs - del kwargs['nf_min'] - del kwargs['nf_max'] - except KeyError: pass #nf_min and nf_max are not needed for fixed gain amp + for k in ('nf_min', 'nf_max'): + try: + del kwargs[k] + except KeyError: + pass nf_def = Model_fg(nf0) elif type_def == 'advanced_model': config = Path(filename).parent / kwargs.pop('advanced_config_from_json') From f8fc2a505078b5539ccbd8b158232e1e47801a63 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Wed, 22 May 2019 10:02:45 +0200 Subject: [PATCH 006/117] simparams enriched with Raman and NLI parameters --- examples/sim_params.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/sim_params.json b/examples/sim_params.json index 765132498..35d729c3c 100644 --- a/examples/sim_params.json +++ b/examples/sim_params.json @@ -1,3 +1,16 @@ { - "raman_feature": true + "list_of_channels_under_test": [1, 2, 3, 5, 40, 90], + "raman_parameters": { + "flag_raman": true, + "space_resolution": 1e3, + "tolerance": 1e-8, + "verbose": 2 + }, + "nli_parameters": { + "method_nli": "GGN", + "wdm_grid_size": 50e9, + "dispersion_tolerance": 1, + "phase_shift_tollerance": 0.1, + "verbose": 1 + } } From 45e8c8692b9368487c9d6b1998d85f2a5d91ef9f Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Wed, 22 May 2019 10:09:27 +0200 Subject: [PATCH 007/117] add science utils module --- gnpy/core/science_utils.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 gnpy/core/science_utils.py diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py new file mode 100644 index 000000000..ecafd64bb --- /dev/null +++ b/gnpy/core/science_utils.py @@ -0,0 +1,23 @@ +from collections import namedtuple + + +class RamanParameters(): + def __init__(self, params=None): + self.flag_raman = params['flag_raman'] + self.space_resolution = params['space_resolution'] + self.tolerance = params['tolerance'] + self.verbose = params['verbose'] + +class NLIParameters(): + def __init__(self, params=None): + self.nli_method_name = params['nli_method_name'] + self.wdm_grid_size = params['wdm_grid_size'] + self.dispersion_tolerance = params['dispersion_tolerance'] + self.phase_shift_tollerance = params['phase_shift_tollerance'] + self.verbose = params['verbose'] + +class SimParams(): + def __init__(self, params=None): + self.list_of_channels_under_test = params['list_of_channels_under_test'] + self.raman_params = RamanParameters(params=params['raman_parametes']) + self.nli_params = NLIParameters(params=params['nli_parametes']) \ No newline at end of file From efae43f122c944bc7bb0d2abc1a832626836fd82 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Wed, 22 May 2019 10:26:24 +0200 Subject: [PATCH 008/117] utility to load sim params from json --- gnpy/core/science_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index ecafd64bb..0916e6cb2 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -1,6 +1,10 @@ -from collections import namedtuple +from gnpy.core.utils import load_json +def load_sim_params(path_sim_params): + sim_params = load_json(path_sim_params) + return SimParams(**sim_params) + class RamanParameters(): def __init__(self, params=None): self.flag_raman = params['flag_raman'] From 13b4b5072f07f0edd7a732d38ef316aba9ac6c24 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Wed, 22 May 2019 10:59:00 +0200 Subject: [PATCH 009/117] fix sim params --- examples/sim_params.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sim_params.json b/examples/sim_params.json index 35d729c3c..dadad9913 100644 --- a/examples/sim_params.json +++ b/examples/sim_params.json @@ -7,7 +7,7 @@ "verbose": 2 }, "nli_parameters": { - "method_nli": "GGN", + "nli_method_name": "GGN", "wdm_grid_size": 50e9, "dispersion_tolerance": 1, "phase_shift_tollerance": 0.1, From d86bea80d3a5254ce8edd48c38b9c10a06680336 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Wed, 22 May 2019 11:00:02 +0200 Subject: [PATCH 010/117] fix load_sim_params --- gnpy/core/science_utils.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 0916e6cb2..4ccf05f6b 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -3,25 +3,28 @@ def load_sim_params(path_sim_params): sim_params = load_json(path_sim_params) - return SimParams(**sim_params) + return SimParams(params=sim_params) -class RamanParameters(): +class RamanParams(): def __init__(self, params=None): - self.flag_raman = params['flag_raman'] - self.space_resolution = params['space_resolution'] - self.tolerance = params['tolerance'] - self.verbose = params['verbose'] + if params: + self.flag_raman = params['flag_raman'] + self.space_resolution = params['space_resolution'] + self.tolerance = params['tolerance'] + self.verbose = params['verbose'] -class NLIParameters(): +class NLIParams(): def __init__(self, params=None): - self.nli_method_name = params['nli_method_name'] - self.wdm_grid_size = params['wdm_grid_size'] - self.dispersion_tolerance = params['dispersion_tolerance'] - self.phase_shift_tollerance = params['phase_shift_tollerance'] - self.verbose = params['verbose'] + if params: + self.nli_method_name = params['nli_method_name'] + self.wdm_grid_size = params['wdm_grid_size'] + self.dispersion_tolerance = params['dispersion_tolerance'] + self.phase_shift_tollerance = params['phase_shift_tollerance'] + self.verbose = params['verbose'] class SimParams(): def __init__(self, params=None): - self.list_of_channels_under_test = params['list_of_channels_under_test'] - self.raman_params = RamanParameters(params=params['raman_parametes']) - self.nli_params = NLIParameters(params=params['nli_parametes']) \ No newline at end of file + if params: + self.list_of_channels_under_test = params['list_of_channels_under_test'] + self.raman_params = RamanParams(params=params['raman_parameters']) + self.nli_params = NLIParams(params=params['nli_parameters']) \ No newline at end of file From 35f38668824aa6dcf790aa17695aff8eda3229a5 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Wed, 22 May 2019 11:06:58 +0200 Subject: [PATCH 011/117] load_sim_params used to parse sim_params.json and new default filename --- examples/transmission_with_raman_main_example.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index 868fec009..a7ff13e4f 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -11,7 +11,7 @@ ''' from gnpy.core.equipment import load_equipment, trx_mode_params -from gnpy.core.utils import db2lin, lin2db, write_csv, load_json +from gnpy.core.utils import db2lin, lin2db, write_csv from argparse import ArgumentParser from sys import exit from pathlib import Path @@ -26,6 +26,7 @@ from gnpy.core.elements import Transceiver, Fiber, Edfa, Roadm from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power, Pref from gnpy.core.request import Path_request, RequestParams, compute_constrained_path, propagate2 +from gnpy.core.science_utils import load_sim_params logger = getLogger(__name__) @@ -182,14 +183,14 @@ def main(network, equipment, source, destination, sim_params, req = None): parser.add_argument('-e', '--equipment', type=Path, default=Path(__file__).parent / 'eqpt_config.json') parser.add_argument('-sim', '--sim-params', type=Path, - default=Path(__file__).parent / 'sim_params.json', help='json containing simulation parameters') + default=Path(__file__).parent / 'sim_params.json', help='Path to the json containing simulation parameters') parser.add_argument('-pl', '--plot', action='store_true') parser.add_argument('-v', '--verbose', action='count', default=0, help='increases verbosity for each occurence') parser.add_argument('-l', '--list-nodes', action='store_true', help='list all transceiver nodes') parser.add_argument('-po', '--power', default=0, help='channel ref power in dBm') parser.add_argument('-names', '--names-matching', action='store_true', help='display network names that are closed matches') parser.add_argument('filename', nargs='?', type=Path, - default=Path(__file__).parent / 'edfa_example_network.json') + default=Path(__file__).parent / 'raman_edfa_example_network.json') parser.add_argument('source', nargs='?', help='source node') parser.add_argument('destination', nargs='?', help='destination node') @@ -200,6 +201,7 @@ def main(network, equipment, source, destination, sim_params, req = None): equipment = load_equipment(args.equipment) network = load_network(args.filename, equipment, args.names_matching) + sim_params = load_sim_params(args.sim_params) if args.plot: plot_baseline(network) @@ -216,8 +218,6 @@ def main(network, equipment, source, destination, sim_params, req = None): print(uid) exit() - sim_params = load_json(args.sim_params) - #First try to find exact match if source/destination provided if args.source: source = transceivers.pop(args.source, None) From 0b2ee6fdaf673d9be12562ba0c10b613f5d379d9 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Wed, 22 May 2019 12:05:51 +0200 Subject: [PATCH 012/117] add Raman solver --- gnpy/core/science_utils.py | 309 ++++++++++++++++++++++++++++++++++++- 1 file changed, 308 insertions(+), 1 deletion(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 4ccf05f6b..c6880a46d 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -1,4 +1,9 @@ from gnpy.core.utils import load_json +import scipy.constants as ph +from scipy.integrate import solve_bvp +from scipy.integrate import cumtrapz +from scipy.interpolate import interp1d +from scipy.optimize import OptimizeResult def load_sim_params(path_sim_params): @@ -27,4 +32,306 @@ def __init__(self, params=None): if params: self.list_of_channels_under_test = params['list_of_channels_under_test'] self.raman_params = RamanParams(params=params['raman_parameters']) - self.nli_params = NLIParams(params=params['nli_parameters']) \ No newline at end of file + self.nli_params = NLIParams(params=params['nli_parameters']) + + class RamanSolver: + + def __init__(self, fiber_information=None): + """ Initialize the fiber object with its physical parameters + :param length: fiber length in m. + :param alphap: fiber power attenuation coefficient vs frequency in 1/m. numpy array + :param freq_alpha: frequency axis of alphap in Hz. numpy array + :param cr_raman: Raman efficiency vs frequency offset in 1/W/m. numpy array + :param freq_cr: reference frequency offset axis for cr_raman. numpy array + :param solver_params: namedtuple containing the solver parameters (optional). + """ + self._fiber_information = fiber_information + self._solver_params = None + self._spectral_information = None + self._raman_pump_information = None + self._stimulated_raman_scattering = None + self._spontaneous_raman_scattering = None + + @property + def fiber_information(self): + return self._fiber_information + + @fiber_information.setter + def fiber_information(self, fiber_information): + self._fiber_information = fiber_information + self._stimulated_raman_scattering = None + + @property + def spectral_information(self): + return self._spectral_information + + @spectral_information.setter + def spectral_information(self, spectral_information): + """ + :param spectral_information: namedtuple containing all the spectral information about carriers and eventual Raman pumps + :return: + """ + self._spectral_information = spectral_information + self._stimulated_raman_scattering = None + + @property + def raman_pump_information(self): + return self._raman_pump_information + + @raman_pump_information.setter + def raman_pump_information(self, raman_pump_information): + self._raman_pump_information = raman_pump_information + + @property + def solver_params(self): + return self._solver_params + + @solver_params.setter + def solver_params(self, solver_params): + """ + :param solver_params: namedtuple containing the solver parameters (optional). + :return: + """ + self._solver_params = solver_params + self._stimulated_raman_scattering = None + self._spontaneous_raman_scattering = None + + @property + def spontaneous_raman_scattering(self): + if self._spontaneous_raman_scattering is None: + # SET STUFF + attenuation_coefficient = self.fiber_information.attenuation_coefficient + raman_coefficient = self.fiber_information.raman_coefficient + temp_k = self.fiber_information.temperature + + spectral_info = self.spectral_information + raman_pump_information = self.raman_pump_information + + verbose = self.solver_params.verbose + + if verbose: + print('Start computing fiber Spontaneous Raman Scattering') + + power_spectrum, freq_array, prop_direct, bn_array = self._compute_power_spectrum(spectral_info, raman_pump_information) + temperature = temp_k.temperature + + if len(attenuation_coefficient.alpha_power) >= 2: + interp_alphap = interp1d(attenuation_coefficient.frequency, attenuation_coefficient.alpha_power) + alphap_fiber = interp_alphap(freq_array) + else: + alphap_fiber = attenuation_coefficient.alpha_power * np.ones(freq_array.shape) + + freq_diff = abs(freq_array - np.reshape(freq_array, (len(freq_array), 1))) + if len(raman_coefficient.cr) >= 2: + interp_cr = interp1d(raman_coefficient.frequency, raman_coefficient.cr) + cr = interp_cr(freq_diff) + else: + cr = raman_coefficient.cr * np.ones(freq_diff.shape) + + # z propagation axis + z_array = self.stimulated_raman_scattering.z + ase_bc = np.zeros(freq_array.shape) + + # calculate ase power + spontaneous_raman_scattering = self._int_spontaneous_raman(z_array, self.stimulated_raman_scattering.power, alphap_fiber, freq_array, cr, freq_diff, ase_bc, bn_array, temperature) + + setattr(spontaneous_raman_scattering, 'frequency', freq_array) + setattr(spontaneous_raman_scattering, 'z', z_array) + setattr(spontaneous_raman_scattering, 'power', spontaneous_raman_scattering.x) + delattr(spontaneous_raman_scattering, 'x') + + if verbose: + print(spontaneous_raman_scattering.message) + + self._spontaneous_raman_scattering = spontaneous_raman_scattering + + return self._spontaneous_raman_scattering + + @staticmethod + def _compute_power_spectrum(spectral_information, raman_pump_information): + """ + Rearrangement of spectral and Raman pump information to make them compatible with Raman solver + :param spectral_information: a namedtuple describing the transmitted channels + :param raman_pump_information: a namedtuple describing the Raman pumps + :return: + """ + + # Signal power spectrum + pow_array = np.array([]) + f_array = np.array([]) + noise_bandwidth_array = np.array([]) + for carrier in sorted(spectral_information.carriers, key=attrgetter('frequency')): + f_array = np.append(f_array, carrier.frequency) + pow_array = np.append(pow_array, carrier.power.signal) + noise_bandwidth_array = np.append(noise_bandwidth_array, carrier.baud_rate) + + propagation_direction = np.ones(len(f_array)) + + # Raman pump power spectrum + if raman_pump_information: + for pump in raman_pump_information.raman_pumps: + pow_array = np.append(pow_array, pump.power) + f_array = np.append(f_array, pump.frequency) + propagation_direction = np.append(propagation_direction, pump.propagation_direction) + noise_bandwidth_array = np.append(noise_bandwidth_array, pump.pump_bandwidth) + + # Final sorting + ind = np.argsort(f_array) + f_array = f_array[ind] + pow_array = pow_array[ind] + propagation_direction = propagation_direction[ind] + + return pow_array, f_array, propagation_direction, noise_bandwidth_array + + def _int_spontaneous_raman(self, z_array, raman_matrix, alphap_fiber, freq_array, cr_raman_matrix, freq_diff, ase_bc, bn_array, temperature): + spontaneous_raman_scattering = OptimizeResult() + + dx = self.solver_params.z_resolution + h = ph.value('Planck constant') + kb = ph.value('Boltzmann constant') + + power_ase = np.nan * np.ones(raman_matrix.shape) + int_pump = cumtrapz(raman_matrix, z_array, dx=dx, axis=1, initial=0) + + for f_ind, f_ase in enumerate(freq_array): + cr_raman = cr_raman_matrix[f_ind, :] + vibrational_loss = f_ase / freq_array[:f_ind] + eta = 1/(np.exp((h*freq_diff[f_ind, f_ind+1:])/(kb*temperature)) - 1) + + int_fiber_loss = -alphap_fiber[f_ind] * z_array + int_raman_loss = np.sum((cr_raman[:f_ind] * vibrational_loss * int_pump[:f_ind, :].transpose()).transpose(), axis=0) + int_raman_gain = np.sum((cr_raman[f_ind + 1:] * int_pump[f_ind + 1:, :].transpose()).transpose(), axis=0) + + int_gain_loss = int_fiber_loss + int_raman_gain + int_raman_loss + + new_ase = np.sum((cr_raman[f_ind+1:] * (1 + eta) * raman_matrix[f_ind+1:, :].transpose()).transpose() * h * f_ase * bn_array[f_ind], axis=0) + + bc_evolution = ase_bc[f_ind] * np.exp(int_gain_loss) + ase_evolution = np.exp(int_gain_loss) * cumtrapz(new_ase*np.exp(-int_gain_loss), z_array, dx=dx, initial=0) + + power_ase[f_ind, :] = bc_evolution + ase_evolution + + spontaneous_raman_scattering.x = power_ase + spontaneous_raman_scattering.success = True + spontaneous_raman_scattering.message = "Spontaneous Raman Scattering evaluated successfully" + + return spontaneous_raman_scattering + + @property + def stimulated_raman_scattering(self): + """ Return rho fiber gain/loss profile induced by stimulated Raman scattering. + :return: self._stimulated_raman_scattering: the SRS problem solution. + scipy.interpolate.PPoly instance + """ + + if self._stimulated_raman_scattering is None: + fiber_length = self.fiber_information.length + attenuation_coefficient = self.fiber_information.attenuation_coefficient + raman_coefficient = self.fiber_information.raman_coefficient + + spectral_info = self.spectral_information + raman_pump_information = self.raman_pump_information + + z_resolution = self.solver_params.z_resolution + tolerance = self.solver_params.tolerance + verbose = self.solver_params.verbose + + if verbose: + print('Start computing fiber Stimulated Raman Scattering') + + power_spectrum, freq_array, prop_direct, _ = self._compute_power_spectrum(spectral_info, raman_pump_information) + + if len(attenuation_coefficient.alpha_power) >= 2: + interp_alphap = interp1d(attenuation_coefficient.frequency, attenuation_coefficient.alpha_power) + alphap_fiber = interp_alphap(freq_array) + else: + alphap_fiber = attenuation_coefficient.alpha_power * np.ones(freq_array.shape) + + freq_diff = abs(freq_array - np.reshape(freq_array, (len(freq_array), 1))) + if len(raman_coefficient.cr) >= 2: + interp_cr = interp1d(raman_coefficient.frequency, raman_coefficient.cr) + cr = interp_cr(freq_diff) + else: + cr = raman_coefficient.cr * np.ones(freq_diff.shape) + + # z propagation axis + z = np.arange(0, fiber_length+1, z_resolution) + + ode_function = lambda z, p: self._ode_stimulated_raman(z, p, alphap_fiber, freq_array, cr, prop_direct) + boundary_residual = lambda ya, yb: self._residuals_stimulated_raman(ya, yb, power_spectrum, prop_direct) + initial_guess_conditions = self._initial_guess_stimulated_raman(z, power_spectrum, alphap_fiber, prop_direct) + + # ODE SOLVER + stimulated_raman_scattering = solve_bvp(ode_function, boundary_residual, z, initial_guess_conditions, tol=tolerance, verbose=verbose) + + rho = (stimulated_raman_scattering.y.transpose() / power_spectrum).transpose() + rho = np.sqrt(rho) # From power attenuation to field attenuation + + setattr(stimulated_raman_scattering, 'frequency', freq_array) + setattr(stimulated_raman_scattering, 'z', stimulated_raman_scattering.x) + setattr(stimulated_raman_scattering, 'rho', rho) + setattr(stimulated_raman_scattering, 'power', stimulated_raman_scattering.y) + delattr(stimulated_raman_scattering, 'x') + delattr(stimulated_raman_scattering, 'y') + + self._stimulated_raman_scattering = stimulated_raman_scattering + + return self._stimulated_raman_scattering + + def _residuals_stimulated_raman(self, ya, yb, power_spectrum, prop_direct): + + computed_boundary_value = np.zeros(ya.size) + + for index, direction in enumerate(prop_direct): + if direction == +1: + computed_boundary_value[index] = ya[index] + else: + computed_boundary_value[index] = yb[index] + + return power_spectrum - computed_boundary_value + + def _initial_guess_stimulated_raman(self, z, power_spectrum, alphap_fiber, prop_direct): + """ Computes the initial guess knowing the boundary conditions + :param z: patial axis [m]. numpy array + :param power_spectrum: power in each frequency slice [W]. Frequency axis is defined by freq_array. numpy array + :param alphap_fiber: frequency dependent fiber attenuation of signal power [1/m]. Frequency defined by freq_array. numpy array + :param prop_direct: indicates the propagation direction of each power slice in power_spectrum: + +1 for forward propagation and -1 for backward propagation. Frequency defined by freq_array. numpy array + :return: power_guess: guess on the initial conditions [W]. The first ndarray index identifies the frequency slice, + the second ndarray index identifies the step in z. ndarray + """ + + power_guess = np.empty((power_spectrum.size, z.size)) + for f_index, power_slice in enumerate(power_spectrum): + if prop_direct[f_index] == +1: + power_guess[f_index, :] = np.exp(-alphap_fiber[f_index] * z) * power_slice + else: + power_guess[f_index, :] = np.exp(-alphap_fiber[f_index] * z[::-1]) * power_slice + + return power_guess + + def _ode_stimulated_raman(self, z, power_spectrum, alphap_fiber, freq_array, cr_raman_matrix, prop_direct): + """ Aim of ode_raman is to implement the set of ordinary differential equations (ODEs) describing the Raman effect. + :param z: spatial axis (unused). + :param power_spectrum: power in each frequency slice [W]. Frequency axis is defined by freq_array. numpy array. Size n + :param alphap_fiber: frequency dependent fiber attenuation of signal power [1/m]. Frequency defined by freq_array. numpy array. Size n + :param freq_array: reference frequency axis [Hz]. numpy array. Size n + :param cr_raman: Cr(f) Raman gain efficiency variation in frequency [1/W/m]. Frequency defined by freq_array. numpy ndarray. Size nxn + :param prop_direct: indicates the propagation direction of each power slice in power_spectrum: + +1 for forward propagation and -1 for backward propagation. Frequency defined by freq_array. numpy array. Size n + :return: dP/dz: the power variation in dz [W/m]. numpy array. Size n + """ + + dpdz = np.nan * np.ones(power_spectrum.shape) + for f_ind, power in enumerate(power_spectrum): + cr_raman = cr_raman_matrix[f_ind, :] + vibrational_loss = freq_array[f_ind] / freq_array[:f_ind] + + for z_ind, power_sample in enumerate(power): + raman_gain = np.sum(cr_raman[f_ind+1:] * power_spectrum[f_ind+1:, z_ind]) + raman_loss = np.sum(vibrational_loss * cr_raman[:f_ind] * power_spectrum[:f_ind, z_ind]) + + dpdz_element = prop_direct[f_ind] * (-alphap_fiber[f_ind] + raman_gain - raman_loss) * power_sample + dpdz[f_ind][z_ind] = dpdz_element + + return np.vstack(dpdz) \ No newline at end of file From 3aa0a0999b2d55918336f9e510137bb4817655e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 22 May 2019 17:46:01 +0200 Subject: [PATCH 013/117] Update networkx and xlrd ...in an attempt to kill some deprecation warnings under Python 3.7. --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 60f6d9c4a..e33ea915b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ latexcodec==1.0.5 MarkupSafe==1.0 matplotlib==3.0.0 more-itertools==4.3.0 -networkx==2.2 +networkx==2.3 numpy==1.15.2 oset==0.1.3 packaging==18.0 @@ -41,4 +41,4 @@ sphinxcontrib-bibtex==0.4.0 sphinxcontrib-websupport==1.1.0 toml==0.10.0 urllib3==1.23 -xlrd==1.1.0 +xlrd==1.2.0 From 92f11dc07528da4b80175c0ce771e28dc49a7348 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Thu, 23 May 2019 15:05:08 +0200 Subject: [PATCH 014/117] add an example of json including Raman pumps in fiber --- examples/raman_edfa_example_network.json | 97 ++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 examples/raman_edfa_example_network.json diff --git a/examples/raman_edfa_example_network.json b/examples/raman_edfa_example_network.json new file mode 100644 index 000000000..1c27b747c --- /dev/null +++ b/examples/raman_edfa_example_network.json @@ -0,0 +1,97 @@ +{ + "elements": [ + { + "uid": "Site_A", + "type": "Transceiver", + "metadata": { + "location": { + "latitude": 0, + "longitude": 0, + "city": "Site A", + "region": "" + } + } + }, + { + "uid": "Span1", + "type": "Fiber", + "type_variety": "SSMF", + "operational": { + "raman_pumps": [ + { + "power": 200e-3, + "frequency": 200e12, + "propagation_direction": -1 + }, + { + "power": 150e-3, + "frequency": 201e12, + "propagation_direction": -1 + } + ] + }, + "params": { + "type_variety": "SSMF", + "length": 80.0, + "loss_coef": 0.2, + "length_units": "km", + "att_in": 0, + "con_in": 0.5, + "con_out": 0.5 + }, + "metadata": { + "location": { + "latitude": 1, + "longitude": 0, + "city": null, + "region": "" + } + } + }, + { + "uid": "Edfa1", + "type": "Edfa", + "type_variety": "std_low_gain", + "operational": { + "gain_target": 15.0, + "delta_p": -2, + "tilt_target": 0, + "out_voa": 0 + }, + "metadata": { + "location": { + "latitude": 2, + "longitude": 0, + "city": null, + "region": "" + } + } + }, + { + "uid": "Site_B", + "type": "Transceiver", + "metadata": { + "location": { + "latitude": 2, + "longitude": 0, + "city": "Site B", + "region": "" + } + } + } + ], + "connections": [ + { + "from_node": "Site_A", + "to_node": "Span1" + }, + { + "from_node": "Span1", + "to_node": "Edfa1" + }, + { + "from_node": "Edfa1", + "to_node": "Site_B" + } + ] +} \ No newline at end of file From 0e9f3c3576158d5819a516a3498b1965d6510627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 22 May 2019 17:17:43 +0200 Subject: [PATCH 015/117] tests: re-enable warnings For some reason, warnings were disabled in commit 07de78c, but it is not clear to me what the purpose was back then. It passes without warnings locally, and I think that warnings are useful in general, so let's give the CI a try. --- pytest.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 1ceab9429..eea2c1802 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1 @@ [pytest] -addopts = -p no:warnings From cbb61f12400b992a510ee81b9444e7b918d2165f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Fri, 24 May 2019 01:38:12 +0200 Subject: [PATCH 016/117] tests: consult the doctests as well There are no doctests right now, but they will (might?) be added (see PR #230). --- pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/pytest.ini b/pytest.ini index eea2c1802..df3eb518d 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1 +1,2 @@ [pytest] +addopts = --doctest-modules From e519a3bc39be1bbd7734c25cfe6e67d4eb0073e9 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Fri, 24 May 2019 10:36:29 +0200 Subject: [PATCH 017/117] add class RamanFiber --- gnpy/core/elements.py | 209 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index d4ebf6c71..7d194c90a 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -417,6 +417,215 @@ def __call__(self, spectral_info): self.carriers_out = carriers return spectral_info.update(carriers=carriers, pref=pref) +RamanFiberParams = namedtuple('RamanFiberParams', 'type_variety length loss_coef length_units \ + att_in con_in con_out dispersion gamma') + +class RamanFiber(Node): + def __init__(self, *args, params=None, **kwargs): + if params is None: + params = {} + if 'con_in' not in params: + # if not defined in the network json connector loss in/out + # the None value will be updated in network.py[build_network] + # with default values from eqpt_config.json[Spans] + params['con_in'] = None + params['con_out'] = None + if 'att_in' not in params: + #fixed attenuator for padding + params['att_in'] = 0 + + super().__init__(*args, params=RamanFiberParams(**params), **kwargs) + self.type_variety = self.params.type_variety + self.length = self.params.length * UNITS[self.params.length_units] # in m + self.loss_coef = self.params.loss_coef * 1e-3 # lineic loss dB/m + self.lin_loss_coef = self.params.loss_coef / (20 * log10(exp(1))) + self.att_in = self.params.att_in + self.con_in = self.params.con_in + self.con_out = self.params.con_out + self.dispersion = self.params.dispersion # s/m/m + self.gamma = self.params.gamma # 1/W/m + self.pch_out_db = None + self.carriers_in = None + self.carriers_out = None + # TODO|jla: discuss factor 2 in the linear lineic attenuation + + @property + def to_json(self): + return {'uid' : self.uid, + 'type' : type(self).__name__, + 'type_variety' : self.type_variety, + 'params' : { + #have to specify each because namedtupple cannot be updated :( + 'type_variety' : self.type_variety, + 'length' : self.length/UNITS[self.params.length_units], + 'loss_coef' : self.loss_coef*1e3, + 'length_units' : self.params.length_units, + 'att_in' : self.att_in, + 'con_in' : self.con_in, + 'con_out' : self.con_out + }, + 'metadata' : { + 'location': self.metadata['location']._asdict() + } + } + + def __repr__(self): + return f'{type(self).__name__}(uid={self.uid!r}, length={round(self.length*1e-3,1)!r}km, loss={round(self.loss,1)!r}dB)' + + def __str__(self): + return '\n'.join([f'{type(self).__name__} {self.uid}', + f' type_variety: {self.type_variety}', + f' length (km): {round(self.length*1e-3):.2f}', + f' pad att_in (dB): {self.att_in:.2f}', + f' total loss (dB): {self.loss:.2f}', + f' (includes conn loss (dB) in: {self.con_in:.2f} out: {self.con_out:.2f})', + f' (conn loss out includes EOL margin defined in eqpt_config.json)', + f' pch out (dBm): {self.pch_out_db!r}']) + + @property + def fiber_loss(self): + # dB fiber loss, not including padding attenuator + return self.loss_coef * self.length + self.con_in + self.con_out + + @property + def loss(self): + #total loss incluiding padding att_in: useful for polymorphism with roadm loss + return self.loss_coef * self.length + self.con_in + self.con_out + self.att_in + + @property + def passive(self): + return True + + @property + def lin_attenuation(self): + return db2lin(self.length * self.loss_coef) + + @property + def effective_length(self): + _, alpha = self.dbkm_2_lin() + leff = (1 - exp(-2 * alpha * self.length)) / (2 * alpha) + return leff + + @property + def asymptotic_length(self): + _, alpha = self.dbkm_2_lin() + aleff = 1 / (2 * alpha) + return aleff + + def carriers(self, loc, attr): + """retrieve carriers information + loc = (in, out) of the class element + attr = (ase, nli, signal, total) power information""" + if not (loc in ('in', 'out') and attr in ('nli', 'signal', 'total', 'ase')): + yield None + return + power_dict = { + 'nli': 'nonlinear_interference', + 'ase': 'amplified_spontaneous_emission' + } + attr = power_dict.get(attr, attr) + loc_attr = 'carriers_'+loc + for c in getattr(self, loc_attr) : + if attr == 'total': + yield c.power.ase+c.power.nli+c.power.signal + else: + yield c.power._asdict().get(attr, None) + + def beta2(self, ref_wavelength=None): + """ Returns beta2 from dispersion parameter. + Dispersion is entered in ps/nm/km. + Disperion can be a numpy array or a single value. If a + value ref_wavelength is not entered 1550e-9m will be assumed. + ref_wavelength can be a numpy array. + """ + # TODO|jla: discuss beta2 as method or attribute + wl = 1550e-9 if ref_wavelength is None else ref_wavelength + D = abs(self.dispersion) + b2 = (wl ** 2) * D / (2 * pi * c) # 10^21 scales [ps^2/km] + return b2 # s/Hz/m + + def dbkm_2_lin(self): + """ calculates the linear loss coefficient + """ + # alpha_pcoef is linear loss coefficient in dB/km^-1 + # alpha_acoef is linear loss field amplitude coefficient in m^-1 + alpha_pcoef = self.loss_coef + alpha_acoef = alpha_pcoef / (2 * 10 * log10(exp(1))) + return alpha_pcoef, alpha_acoef + + def _psi(self, carrier, interfering_carrier): + """ Calculates eq. 123 from arXiv:1209.0394. + """ + if carrier.num_chan == interfering_carrier.num_chan: # SCI + psi = arcsinh(0.5 * pi**2 * self.asymptotic_length + * abs(self.beta2()) * carrier.baud_rate**2) + else: # XCI + delta_f = carrier.freq - interfering_carrier.freq + psi = arcsinh(pi**2 * self.asymptotic_length * abs(self.beta2()) + * carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate)) + psi -= arcsinh(pi**2 * self.asymptotic_length * abs(self.beta2()) + * carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) + + return psi + + def _gn_analytic(self, carrier, *carriers): + """ Computes the nonlinear interference power on a single carrier. + The method uses eq. 120 from arXiv:1209.0394. + :param carrier: the signal under analysis + :param carriers: the full WDM comb + :return: carrier_nli: the amount of nonlinear interference in W on the under analysis + """ + + g_nli = 0 + for interfering_carrier in carriers: + psi = self._psi(carrier, interfering_carrier) + g_nli += (interfering_carrier.power.signal/interfering_carrier.baud_rate)**2 \ + * (carrier.power.signal/carrier.baud_rate) * psi + + g_nli *= (16 / 27) * (self.gamma * self.effective_length)**2 \ + / (2 * pi * abs(self.beta2()) * self.asymptotic_length) + + carrier_nli = carrier.baud_rate * g_nli + return carrier_nli + + def propagate(self, *carriers): + + # apply connector_att_in on all carriers before computing gn analytics premiere partie pas bonne + attenuation = db2lin(self.con_in + self.att_in) + + chan = [] + for carrier in carriers: + pwr = carrier.power + pwr = pwr._replace(signal=pwr.signal/attenuation, + nonlinear_interference=pwr.nli/attenuation, + amplified_spontaneous_emission=pwr.ase/attenuation) + carrier = carrier._replace(power=pwr) + chan.append(carrier) + + carriers = tuple(f for f in chan) + + # propagate in the fiber and apply attenuation out + attenuation = db2lin(self.con_out) + for carrier in carriers: + pwr = carrier.power + carrier_nli = self._gn_analytic(carrier, *carriers) + pwr = pwr._replace(signal=pwr.signal/self.lin_attenuation/attenuation, + nonlinear_interference=(pwr.nli+carrier_nli)/self.lin_attenuation/attenuation, + amplified_spontaneous_emission=pwr.ase/self.lin_attenuation/attenuation) + yield carrier._replace(power=pwr) + + def update_pref(self, pref): + self.pch_out_db = round(pref.pi - self.loss, 2) + return pref._replace(p_span0=pref.p0, p_spani=self.pch_out_db) + + def __call__(self, spectral_info): + self.carriers_in = spectral_info.carriers + carriers = tuple(self.propagate(*spectral_info.carriers)) + pref = self.update_pref(spectral_info.pref) + self.carriers_out = carriers + return spectral_info.update(carriers=carriers, pref=pref) + + class EdfaParams: def __init__(self, **params): self.update_params(params) From 62fe374e1556e076ca484109832260902a93d4b9 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Fri, 24 May 2019 10:38:24 +0200 Subject: [PATCH 018/117] modified fiber type in raman_edfa_example_network.json to use new class RamanFiber --- examples/raman_edfa_example_network.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/raman_edfa_example_network.json b/examples/raman_edfa_example_network.json index 1c27b747c..8f05fc015 100644 --- a/examples/raman_edfa_example_network.json +++ b/examples/raman_edfa_example_network.json @@ -14,7 +14,7 @@ }, { "uid": "Span1", - "type": "Fiber", + "type": "RamanFiber", "type_variety": "SSMF", "operational": { "raman_pumps": [ From 626211a3203c6bdc471254e4762f1cfff767115b Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Mon, 27 May 2019 17:04:33 +0200 Subject: [PATCH 019/117] Introduce RamanFiber in equipment --- gnpy/core/equipment.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gnpy/core/equipment.py b/gnpy/core/equipment.py index 456e3a041..613c278cb 100644 --- a/gnpy/core/equipment.py +++ b/gnpy/core/equipment.py @@ -105,6 +105,17 @@ class Fiber(common): def __init__(self, **kwargs): self.update_attr(self.default_values, kwargs, 'Fiber') +class RamanFiber(common): + default_values = \ + { + 'type_variety': '', + 'dispersion': None, + 'gamma': 0 + } + + def __init__(self, **kwargs): + self.update_attr(self.default_values, kwargs, 'RamanFiber') + class Amp(common): default_values = \ { From bac20af381621cee12a4b0659ad93ace46086152 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Mon, 27 May 2019 17:08:36 +0200 Subject: [PATCH 020/117] eqpt_with_raman_config.json copied from eqpt_config.json and RamanFiber added into it --- examples/eqpt_with_raman_config.json | 293 +++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 examples/eqpt_with_raman_config.json diff --git a/examples/eqpt_with_raman_config.json b/examples/eqpt_with_raman_config.json new file mode 100644 index 000000000..a10d5e9d5 --- /dev/null +++ b/examples/eqpt_with_raman_config.json @@ -0,0 +1,293 @@ +{ "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": "Juniper_BoosterHG", + "type_def": "advanced_model", + "gain_flatmax": 25, + "gain_min": 10, + "p_max": 21, + "advanced_config_from_json": "Juniper-BoosterHG.json", + "out_voa_auto": false, + "allowed_for_design": false + }, + { + "type_variety": "operator_model_example", + "type_def": "variable_gain", + "gain_flatmax": 26, + "gain_min": 15, + "p_max": 23, + "nf_min": 6, + "nf_max": 10, + "out_voa_auto": false, + "allowed_for_design": false + }, + { + "type_variety": "low_noise", + "type_def": "openroadm", + "gain_flatmax": 27, + "gain_min": 12, + "p_max": 22, + "nf_coef": [-8.104e-4,-6.221e-2,-5.889e-1,37.62], + "allowed_for_design": false + }, + { + "type_variety": "standard", + "type_def": "openroadm", + "gain_flatmax": 27, + "gain_min": 12, + "p_max": 22, + "nf_coef": [-5.952e-4,-6.250e-2,-1.071,28.99], + "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, + "gain_min": 15, + "p_max": 23, + "nf_min": 6, + "nf_max": 10, + "out_voa_auto": false, + "allowed_for_design": true + }, + { + "type_variety": "std_low_gain", + "type_def": "variable_gain", + "gain_flatmax": 16, + "gain_min": 8, + "p_max": 23, + "nf_min": 6.5, + "nf_max": 11, + "out_voa_auto": false, + "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, + "gain_min": 20, + "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", + "dispersion": 1.67e-05, + "gamma": 0.00127 + }, + { + "type_variety": "NZDF", + "dispersion": 0.5e-05, + "gamma": 0.00146 + }, + { + "type_variety": "LOF", + "dispersion": 2.2e-05, + "gamma": 0.000843 + } + ], + "RamanFiber":[{ + "type_variety": "SSMF", + "dispersion": 1.67e-05, + "gamma": 0.00127 + }, + { + "type_variety": "NZDF", + "dispersion": 0.5e-05, + "gamma": 0.00146 + }, + { + "type_variety": "LOF", + "dispersion": 2.2e-05, + "gamma": 0.000843 + } + ], + "Span":[{ + "power_mode":true, + "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, + "padding": 10, + "EOL": 0, + "con_in": 0, + "con_out": 0 + } + ], + "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, + "baud_rate": 32e9, + "f_max":195.1e12, + "spacing": 50e9, + "power_dbm": 0, + "power_range_db": [0,0,1], + "roll_off": 0.15, + "tx_osnr": 40, + "sys_margins": 2 + }], + "Transceiver":[ + { + "type_variety": "vendorA_trx-type1", + "frequency":{ + "min": 191.35e12, + "max": 196.1e12 + }, + "mode":[ + { + + "format": "mode 1", + "baud_rate": 32e9, + "OSNR": 11, + "bit_rate": 100e9, + "roll_off": 0.15, + "tx_osnr": 40, + "min_spacing": 37.5e9, + "cost":1 + }, + { + "format": "mode 2", + "baud_rate": 66e9, + "OSNR": 15, + "bit_rate": 200e9, + "roll_off": 0.15, + "tx_osnr": 40, + "min_spacing": 75e9, + "cost":1 + } + ] + }, + { + "type_variety": "Voyager", + "frequency":{ + "min": 191.35e12, + "max": 196.1e12 + }, + "mode":[ + { + "format": "mode 1", + "baud_rate": 32e9, + "OSNR": 12, + "bit_rate": 100e9, + "roll_off": 0.15, + "tx_osnr": 40, + "min_spacing": 37.5e9, + "cost":1 + }, + { + "format": "mode 3", + "baud_rate": 44e9, + "OSNR": 18, + "bit_rate": 300e9, + "roll_off": 0.15, + "tx_osnr": 40, + "min_spacing": 62.5e9, + "cost":1 + }, + { + "format": "mode 2", + "baud_rate": 66e9, + "OSNR": 21, + "bit_rate": 400e9, + "roll_off": 0.15, + "tx_osnr": 40, + "min_spacing": 75e9, + "cost":1 + }, + { + "format": "mode 4", + "baud_rate": 66e9, + "OSNR": 16, + "bit_rate": 200e9, + "roll_off": 0.15, + "tx_osnr": 40, + "min_spacing": 75e9, + "cost":1 + } + ] + } + ] + +} From c0fda8c3a2ff838d809c0180c39717797a1650e2 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Mon, 27 May 2019 17:09:34 +0200 Subject: [PATCH 021/117] fix to Raman solver --- gnpy/core/science_utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index c6880a46d..47464b6a7 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -34,8 +34,7 @@ def __init__(self, params=None): self.raman_params = RamanParams(params=params['raman_parameters']) self.nli_params = NLIParams(params=params['nli_parameters']) - class RamanSolver: - +class RamanSolver: def __init__(self, fiber_information=None): """ Initialize the fiber object with its physical parameters :param length: fiber length in m. @@ -211,7 +210,7 @@ def _int_spontaneous_raman(self, z_array, raman_matrix, alphap_fiber, freq_array power_ase[f_ind, :] = bc_evolution + ase_evolution - spontaneous_raman_scattering.x = power_ase + spontaneous_raman_scattering.x = 2 * power_ase spontaneous_raman_scattering.success = True spontaneous_raman_scattering.message = "Spontaneous Raman Scattering evaluated successfully" From 9c95fd6b69d81a59869217efff2c51f33d3237ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Mon, 27 May 2019 18:35:14 +0200 Subject: [PATCH 022/117] Do not mix THz and Hz While the `itufl()` function uses THz by default, its values depend on the frequency range that is passed via arguments. In this context, the f_min and f_max come from the default SpectralInformation which is in Hz. Thanks to @ojnas for reporting this. fixes #243 --- gnpy/core/elements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index a845b150f..c22bb39e6 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -573,7 +573,7 @@ def interpol_params(self, frequencies, pin, baud_rates, pref): self.channel_freq, self.nf, self.interpol_dgt and self.interpol_gain_ripple """ # TODO|jla: read amplifier actual frequencies from additional params in json - amplifier_freq = itufl(len(self.params.dgt), self.params.f_min, self.params.f_max) * 1e12 # Hz + amplifier_freq = itufl(len(self.params.dgt), self.params.f_min, self.params.f_max) # Hz self.channel_freq = frequencies self.interpol_dgt = interp(self.channel_freq, amplifier_freq, self.params.dgt) From f0bc2dc62f1e890a1d0d785d7561d7cd6dc94ecf Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 28 May 2019 11:11:40 +0200 Subject: [PATCH 023/117] attribute sim_params added to RamanFiber --- gnpy/core/elements.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index 7d194c90a..f21a27697 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -512,6 +512,14 @@ def asymptotic_length(self): aleff = 1 / (2 * alpha) return aleff + @property + def sim_params(self): + return self._sim_params + + @sim_params.setter + def sim_params(self, sim_params=None): + self._sim_params = sim_params + def carriers(self, loc, attr): """retrieve carriers information loc = (in, out) of the class element From 3f7180c70605a4977b4bfd5fe63477612acac620 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 28 May 2019 11:35:42 +0200 Subject: [PATCH 024/117] configure_network function created to configure the RamanFiber.sim_params in a network --- gnpy/core/science_utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 47464b6a7..54c6c21f3 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -4,12 +4,18 @@ from scipy.integrate import cumtrapz from scipy.interpolate import interp1d from scipy.optimize import OptimizeResult +from gnpy.core.elements import RamanFiber def load_sim_params(path_sim_params): sim_params = load_json(path_sim_params) return SimParams(params=sim_params) +def configure_network(network, sim_params): + for node in network.nodes: + if isinstance(node, RamanFiber): + node.sim_params = sim_params + class RamanParams(): def __init__(self, params=None): if params: From cb8affe9b2f5c9443e9783606f42a492dbbba0d8 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 28 May 2019 12:19:01 +0200 Subject: [PATCH 025/117] transmission with raman integrated with sim_params configuration --- examples/transmission_with_raman_main_example.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index a7ff13e4f..956b31f39 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -23,10 +23,10 @@ from networkx import (draw_networkx_nodes, draw_networkx_edges, draw_networkx_labels, dijkstra_path) from gnpy.core.network import load_network, build_network, save_network -from gnpy.core.elements import Transceiver, Fiber, Edfa, Roadm +from gnpy.core.elements import Transceiver, Fiber, RamanFiber, Edfa, Roadm from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power, Pref from gnpy.core.request import Path_request, RequestParams, compute_constrained_path, propagate2 -from gnpy.core.science_utils import load_sim_params +from gnpy.core.science_utils import load_sim_params, configure_network logger = getLogger(__name__) @@ -118,14 +118,16 @@ def main(network, equipment, source, destination, sim_params, req = None): 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 - Span'])) + f'=> it can be modified in eqpt_with_raman_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) build_network(network, equipment, pref_ch_db, pref_total_db) + configure_network(network, sim_params) path = compute_constrained_path(network, req) - spans = [s.length for s in path if isinstance(s, Fiber)] + spans = [s.length for s in path if isinstance(s, RamanFiber)] + print(f'\nThere are {len(spans)} fiber spans over {sum(spans):.0f}m between {source.uid} and {destination.uid}') print(f'\nNow propagating between {source.uid} and {destination.uid}:') @@ -181,7 +183,7 @@ def main(network, equipment, source, destination, sim_params, req = None): parser = ArgumentParser() parser.add_argument('-e', '--equipment', type=Path, - default=Path(__file__).parent / 'eqpt_config.json') + default=Path(__file__).parent / 'eqpt_with_raman_config.json') parser.add_argument('-sim', '--sim-params', type=Path, default=Path(__file__).parent / 'sim_params.json', help='Path to the json containing simulation parameters') parser.add_argument('-pl', '--plot', action='store_true') From 71b157a8baa8787331cf794ef8d3921fed0040cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 29 May 2019 18:15:55 +0200 Subject: [PATCH 026/117] Infrastructure for reporting configuration errors via exceptions Once the actual config-parsing code start raising these exceptions instead of directly calling sys.exit(), the user experience would deteriorate due to raw exception traces. There's little value in the trace itself, so just wrap the whole config loading with pretty error formatting. We still do not point to a specific place where that error is defined (such as a line/column in a given JSON file) because that information is already lost by the time we perform these checks. Also, these checks are largely open-coded ad-hoc stuff. Some required items are not covered, raising KeyError instead. We should get a formal schema for these... --- examples/path_requests_run.py | 26 +++++++++++++++----------- examples/transmission_main_example.py | 9 +++++++-- gnpy/core/exceptions.py | 13 +++++++++++++ 3 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 gnpy/core/exceptions.py diff --git a/examples/path_requests_run.py b/examples/path_requests_run.py index 5b9c02861..9400a7c09 100755 --- a/examples/path_requests_run.py +++ b/examples/path_requests_run.py @@ -30,6 +30,7 @@ from gnpy.core.request import (Path_request, Result_element, compute_constrained_path, propagate, jsontocsv, Disjunction, compute_path_dsjctn, requests_aggregation, propagate_and_optimize_mode) +from gnpy.core.exceptions import ConfigurationError from copy import copy, deepcopy from textwrap import dedent from math import ceil @@ -306,15 +307,19 @@ def path_result_json(pathresult): print('\x1b[1;34;40m'+f'Computing path requests {args.service_filename} into JSON format'+ '\x1b[0m') # for debug # print( args.eqpt_filename) - data = load_requests(args.service_filename,args.eqpt_filename) - equipment = load_equipment(args.eqpt_filename) - network = load_network(args.network_filename,equipment) + try: + data = load_requests(args.service_filename,args.eqpt_filename) + equipment = load_equipment(args.eqpt_filename) + network = load_network(args.network_filename,equipment) + except ConfigurationError as e: + print('\x1b[1;31;40m' + 'Configuration error:' + '\x1b[0m' + f' {e}') + exit(1) # Build the network once using the default power defined in SI in eqpt config # TODO power density : db2linp(ower_dbm": 0)/power_dbm": 0 * nb channels as defined by - # spacing, f_min and f_max + # spacing, f_min and f_max p_db = equipment['SI']['default'].power_dbm - + p_total_db = p_db + lin2db(automatic_nch(equipment['SI']['default'].f_min,\ equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) build_network(network, equipment, p_db, p_total_db) @@ -322,7 +327,7 @@ def path_result_json(pathresult): rqs = requests_from_json(data, equipment) - # check that request ids are unique. Non unique ids, may + # check that request ids are unique. Non unique ids, may # mess the computation : better to stop the computation all_ids = [r.request_id for r in rqs] if len(all_ids) != len(set(all_ids)): @@ -341,7 +346,7 @@ def path_result_json(pathresult): # need to warn or correct in case of wrong disjunction form # disjunction must not be repeated with same or different ids dsjn = correct_disjn(dsjn) - + # Aggregate demands with same exact constraints print('\x1b[1;34;40m'+f'Aggregating similar requests'+ '\x1b[0m') @@ -350,7 +355,7 @@ def path_result_json(pathresult): print('\x1b[1;34;40m'+'The following services have been requested:'+ '\x1b[0m') print(rqs) - + print('\x1b[1;34;40m'+f'Computing all paths with constraints'+ '\x1b[0m') pths = compute_path_dsjctn(network, equipment, rqs, dsjn) @@ -360,7 +365,7 @@ def path_result_json(pathresult): end = time.time() print(f'computation time {end-start}') print('\x1b[1;34;40m'+f'Result summary'+ '\x1b[0m') - + header = ['req id', ' demand',' snr@bandwidth',' snr@0.1nm',' Receiver minOSNR', ' mode', ' Gbit/s' , ' nb of tsp pairs'] data = [] data.append(header) @@ -377,7 +382,7 @@ def path_result_json(pathresult): firstcol_width = max(len(row[0]) for row in data ) # padding secondcol_width = max(len(row[1]) for row in data ) # padding for row in data: - firstcol = ''.join(row[0].ljust(firstcol_width)) + firstcol = ''.join(row[0].ljust(firstcol_width)) secondcol = ''.join(row[1].ljust(secondcol_width)) remainingcols = ''.join(word.center(col_width,' ') for word in row[2:]) print(f'{firstcol} {secondcol} {remainingcols}') @@ -396,4 +401,3 @@ def path_result_json(pathresult): with open(fnamecsv,"w", encoding='utf-8') as fcsv : jsontocsv(temp,equipment,fcsv) print('\x1b[1;34;40m'+f'saving in {args.output} and {fnamecsv}'+ '\x1b[0m') - diff --git a/examples/transmission_main_example.py b/examples/transmission_main_example.py index c442bb04a..0619bee2f 100755 --- a/examples/transmission_main_example.py +++ b/examples/transmission_main_example.py @@ -26,6 +26,7 @@ from gnpy.core.elements import Transceiver, Fiber, Edfa, Roadm from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power, Pref from gnpy.core.request import Path_request, RequestParams, compute_constrained_path, propagate2 +from gnpy.core.exceptions import ConfigurationError logger = getLogger(__name__) @@ -196,8 +197,12 @@ def main(network, equipment, source, destination, req = None): args = parser.parse_args() basicConfig(level={0: ERROR, 1: INFO, 2: DEBUG}.get(args.verbose, DEBUG)) - equipment = load_equipment(args.equipment) - network = load_network(args.filename, equipment, args.names_matching) + try: + equipment = load_equipment(args.equipment) + network = load_network(args.filename, equipment, args.names_matching) + except ConfigurationError as e: + print('\x1b[1;31;40m' + 'Configuration error:' + '\x1b[0m' + f' {e}') + exit(1) if args.plot: plot_baseline(network) diff --git a/gnpy/core/exceptions.py b/gnpy/core/exceptions.py new file mode 100644 index 000000000..8f1f89652 --- /dev/null +++ b/gnpy/core/exceptions.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +''' +gnpy.core.exceptions +==================== + +Exceptions thrown by other gnpy modules +''' + + +class ConfigurationError(Exception): + '''User-provided configuration contains an error''' From b2e12cd3e0bccf4bb0f5b44a764008380d8cc928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 30 May 2019 11:59:04 +0200 Subject: [PATCH 027/117] Use exceptions instead of direct-exit when parsing equipment JSON --- gnpy/core/equipment.py | 50 +++++++++++++----------------------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/gnpy/core/equipment.py b/gnpy/core/equipment.py index 2aa10d665..56460308a 100644 --- a/gnpy/core/equipment.py +++ b/gnpy/core/equipment.py @@ -9,7 +9,6 @@ ''' from numpy import clip, polyval -from sys import exit from operator import itemgetter from math import isclose from pathlib import Path @@ -17,6 +16,7 @@ from gnpy.core.utils import lin2db, db2lin, load_json from collections import namedtuple from gnpy.core.elements import Edfa +from gnpy.core.exceptions import ConfigurationError import time Model_vg = namedtuple('Model_vg', 'nf1 nf2 delta_p') @@ -142,8 +142,7 @@ def from_json(cls, filename, **kwargs): try: nf0 = kwargs.pop('nf0') except KeyError: #nf0 is expected for a fixed gain amp - print(f'missing nf0 value input for amplifier: {type_variety} in eqpt_config.json') - exit() + raise ConfigurationError(f'missing nf0 value input for amplifier: {type_variety} in equipment config') for k in ('nf_min', 'nf_max'): try: del kwargs[k] @@ -158,8 +157,7 @@ def from_json(cls, filename, **kwargs): nf_min = kwargs.pop('nf_min') nf_max = kwargs.pop('nf_max') except KeyError: - print(f'missing nf_min/max value input for amplifier: {type_variety} in eqpt_config.json') - exit() + raise ConfigurationError(f'missing nf_min or nf_max value input for amplifier: {type_variety} in equipment config') try: #remove all remaining nf inputs del kwargs['nf0'] except KeyError: pass #nf0 is not needed for variable gain amp @@ -169,16 +167,14 @@ def from_json(cls, filename, **kwargs): try: nf_coef = kwargs.pop('nf_coef') except KeyError: #nf_coef is expected for openroadm amp - print(f'missing nf_coef input for amplifier: {type_variety} in eqpt_config.json') - exit() + raise ConfigurationError(f'missing nf_coef input for amplifier: {type_variety} in equipment config') nf_def = Model_openroadm(nf_coef) 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() + raise ConfigurationError(f'missing preamp/booster variety input for amplifier: {type_variety} in equipment config') dual_stage_def = Model_dual_stage(preamp_variety, booster_variety) with open(config, encoding='utf-8') as f: @@ -190,11 +186,9 @@ def from_json(cls, filename, **kwargs): def nf_model(type_variety, gain_min, gain_max, nf_min, nf_max): if nf_min < -10: - print(f'Invalid nf_min value {nf_min!r} for amplifier {type_variety}') - exit() + raise ConfigurationError(f'Invalid nf_min value {nf_min!r} for amplifier {type_variety}') if nf_max < -10: - print(f'Invalid nf_max value {nf_max!r} for amplifier {type_variety}') - exit() + raise ConfigurationError(f'Invalid nf_max value {nf_max!r} for amplifier {type_variety}') # NF estimation model based on nf_min and nf_max # delta_p: max power dB difference between first and second stage coils @@ -209,8 +203,7 @@ def nf_model(type_variety, gain_min, gain_max, nf_min, nf_max): nf1 = lin2db(db2lin(nf_min) - db2lin(nf2)/db2lin(g1a_max)) if nf1 < 4: - print(f'First coil value too low {nf1} for amplifier {type_variety}') - exit() + raise ConfigurationError(f'First coil value too low {nf1} for amplifier {type_variety}') # Check 1 dB < delta_p < 6 dB to ensure nf_min and nf_max values make sense. # There shouldn't be high nf differences between the two coils: @@ -222,20 +215,17 @@ def nf_model(type_variety, gain_min, gain_max, nf_min, nf_max): delta_p = gain_max - g1a_max g1a_min = gain_min - (gain_max-gain_min) - delta_p if not 1 < delta_p < 11: - print(f'Computed \N{greek capital letter delta}P invalid \ + raise ConfigurationError(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 \ \n calculated 1st coil NF = {nf1:.2f}, 2nd coil NF = {nf2:.2f}') - exit() # Check calculated values for nf1 and nf2 calc_nf_min = lin2db(db2lin(nf1) + db2lin(nf2)/db2lin(g1a_max)) if not isclose(nf_min, calc_nf_min, abs_tol=0.01): - print(f'nf_min does not match calc_nf_min, {nf_min} vs {calc_nf_min} for amp {type_variety}') - exit() + raise ConfigurationError(f'nf_min does not match calc_nf_min, {nf_min} vs {calc_nf_min} for amp {type_variety}') calc_nf_max = lin2db(db2lin(nf1) + db2lin(nf2)/db2lin(g1a_min)) if not isclose(nf_max, calc_nf_max, abs_tol=0.01): - print(f'nf_max does not match calc_nf_max, {nf_max} vs {calc_nf_max} for amp {type_variety}') - exit() + raise ConfigurationError(f'nf_max does not match calc_nf_max, {nf_max} vs {calc_nf_max} for amp {type_variety}') return nf1, nf2, delta_p @@ -270,10 +260,8 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F trx_params = {**mode_params} # sanity check: spacing baudrate must be smaller than min spacing if trx_params['baud_rate'] > trx_params['min_spacing'] : - msg = f'Inconsistency in equipment library:\n Transpoder "{trx_type_variety}" mode "{trx_params["format"]}" '+\ - f'has baud rate: {trx_params["baud_rate"]*1e-9} GHz greater than min_spacing {trx_params["min_spacing"]*1e-9}.' - print(msg) - exit() + raise ConfigurationError(f'Inconsistency in equipment library:\n Transpoder "{trx_type_variety}" mode "{trx_params["format"]}" '+\ + 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, @@ -293,9 +281,7 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F # print(f'spacing {temp}') except StopIteration : if error_message: - print(f'could not find tsp : {trx_type_variety} with mode: {trx_mode} in eqpt library') - print('Computation stopped.') - exit() + raise ConfigurationError(f'Computation stoped: could not find tsp : {trx_type_variety} with mode: {trx_mode} in eqpt library') else: # default transponder charcteristics # mainly used with transmission_main_example.py @@ -356,13 +342,7 @@ def update_dual_stage(equipment): 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() + raise ConfigurationError(f'Dual stage {edfa.type_variety} min gain is lower than its preamp min gain') return equipment def equipment_from_json(json_data, filename): From f09789f5ef0e420fd88c8b33485425959d5f5cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 30 May 2019 12:25:53 +0200 Subject: [PATCH 028/117] Refactor color temrinal escaping into a common module I have no idea which one of the existing pypi modules is best for these, and given that we're using just two escape codes, I think it makes sense not to bother with a more capable third-party module just for two magic strings. --- examples/path_requests_run.py | 3 ++- examples/transmission_main_example.py | 3 ++- gnpy/core/ansi_escapes.py | 12 ++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 gnpy/core/ansi_escapes.py diff --git a/examples/path_requests_run.py b/examples/path_requests_run.py index 9400a7c09..e65b358ac 100755 --- a/examples/path_requests_run.py +++ b/examples/path_requests_run.py @@ -31,6 +31,7 @@ propagate, jsontocsv, Disjunction, compute_path_dsjctn, requests_aggregation, propagate_and_optimize_mode) from gnpy.core.exceptions import ConfigurationError +import gnpy.core.ansi_escapes as ansi_escapes from copy import copy, deepcopy from textwrap import dedent from math import ceil @@ -312,7 +313,7 @@ def path_result_json(pathresult): equipment = load_equipment(args.eqpt_filename) network = load_network(args.network_filename,equipment) except ConfigurationError as e: - print('\x1b[1;31;40m' + 'Configuration error:' + '\x1b[0m' + f' {e}') + print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}') exit(1) # Build the network once using the default power defined in SI in eqpt config diff --git a/examples/transmission_main_example.py b/examples/transmission_main_example.py index 0619bee2f..db1ceaca0 100755 --- a/examples/transmission_main_example.py +++ b/examples/transmission_main_example.py @@ -27,6 +27,7 @@ from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power, Pref from gnpy.core.request import Path_request, RequestParams, compute_constrained_path, propagate2 from gnpy.core.exceptions import ConfigurationError +import gnpy.core.ansi_escapes as ansi_escapes logger = getLogger(__name__) @@ -201,7 +202,7 @@ def main(network, equipment, source, destination, req = None): equipment = load_equipment(args.equipment) network = load_network(args.filename, equipment, args.names_matching) except ConfigurationError as e: - print('\x1b[1;31;40m' + 'Configuration error:' + '\x1b[0m' + f' {e}') + print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}') exit(1) if args.plot: diff --git a/gnpy/core/ansi_escapes.py b/gnpy/core/ansi_escapes.py new file mode 100644 index 000000000..16812a382 --- /dev/null +++ b/gnpy/core/ansi_escapes.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +''' +gnpy.core.ansi_escapes +====================== + +A random subset of ANSI terminal escape codes for colored messages +''' + +red = '\x1b[1;31;40m' +reset = '\x1b[0m' From 58c16a59ac36bdd104c62ce482c9cae3e442d8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 30 May 2019 12:28:47 +0200 Subject: [PATCH 029/117] Distinguish between "equipment library errors" and other "config errors" And because no code for these "other config errors" has been merged yet, this is just a placeholder for future work. --- examples/path_requests_run.py | 5 ++++- examples/transmission_main_example.py | 5 ++++- gnpy/core/equipment.py | 28 +++++++++++++-------------- gnpy/core/exceptions.py | 3 +++ 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/examples/path_requests_run.py b/examples/path_requests_run.py index e65b358ac..49025998a 100755 --- a/examples/path_requests_run.py +++ b/examples/path_requests_run.py @@ -30,7 +30,7 @@ from gnpy.core.request import (Path_request, Result_element, compute_constrained_path, propagate, jsontocsv, Disjunction, compute_path_dsjctn, requests_aggregation, propagate_and_optimize_mode) -from gnpy.core.exceptions import ConfigurationError +from gnpy.core.exceptions import ConfigurationError, EquipmentConfigError import gnpy.core.ansi_escapes as ansi_escapes from copy import copy, deepcopy from textwrap import dedent @@ -312,6 +312,9 @@ def path_result_json(pathresult): data = load_requests(args.service_filename,args.eqpt_filename) equipment = load_equipment(args.eqpt_filename) network = load_network(args.network_filename,equipment) + except EquipmentConfigError as e: + print(f'{ansi_escapes.red}Configuration error in the equipment library:{ansi_escapes.reset} {e}') + exit(1) except ConfigurationError as e: print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}') exit(1) diff --git a/examples/transmission_main_example.py b/examples/transmission_main_example.py index db1ceaca0..89221ec89 100755 --- a/examples/transmission_main_example.py +++ b/examples/transmission_main_example.py @@ -26,7 +26,7 @@ from gnpy.core.elements import Transceiver, Fiber, Edfa, Roadm from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power, Pref from gnpy.core.request import Path_request, RequestParams, compute_constrained_path, propagate2 -from gnpy.core.exceptions import ConfigurationError +from gnpy.core.exceptions import ConfigurationError, EquipmentConfigError import gnpy.core.ansi_escapes as ansi_escapes logger = getLogger(__name__) @@ -201,6 +201,9 @@ def main(network, equipment, source, destination, req = None): try: equipment = load_equipment(args.equipment) network = load_network(args.filename, equipment, args.names_matching) + except EquipmentConfigError as e: + print(f'{ansi_escapes.red}Configuration error in the equipment library:{ansi_escapes.reset} {e}') + exit(1) except ConfigurationError as e: print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}') exit(1) diff --git a/gnpy/core/equipment.py b/gnpy/core/equipment.py index 56460308a..6af520238 100644 --- a/gnpy/core/equipment.py +++ b/gnpy/core/equipment.py @@ -16,7 +16,7 @@ from gnpy.core.utils import lin2db, db2lin, load_json from collections import namedtuple from gnpy.core.elements import Edfa -from gnpy.core.exceptions import ConfigurationError +from gnpy.core.exceptions import EquipmentConfigError import time Model_vg = namedtuple('Model_vg', 'nf1 nf2 delta_p') @@ -142,7 +142,7 @@ def from_json(cls, filename, **kwargs): try: nf0 = kwargs.pop('nf0') except KeyError: #nf0 is expected for a fixed gain amp - raise ConfigurationError(f'missing nf0 value input for amplifier: {type_variety} in equipment config') + raise EquipmentConfigError(f'missing nf0 value input for amplifier: {type_variety} in equipment config') for k in ('nf_min', 'nf_max'): try: del kwargs[k] @@ -157,7 +157,7 @@ def from_json(cls, filename, **kwargs): nf_min = kwargs.pop('nf_min') nf_max = kwargs.pop('nf_max') except KeyError: - raise ConfigurationError(f'missing nf_min or nf_max value input for amplifier: {type_variety} in equipment config') + raise EquipmentConfigError(f'missing nf_min or nf_max value input for amplifier: {type_variety} in equipment config') try: #remove all remaining nf inputs del kwargs['nf0'] except KeyError: pass #nf0 is not needed for variable gain amp @@ -167,14 +167,14 @@ def from_json(cls, filename, **kwargs): try: nf_coef = kwargs.pop('nf_coef') except KeyError: #nf_coef is expected for openroadm amp - raise ConfigurationError(f'missing nf_coef input for amplifier: {type_variety} in equipment config') + raise EquipmentConfigError(f'missing nf_coef input for amplifier: {type_variety} in equipment config') nf_def = Model_openroadm(nf_coef) 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: - raise ConfigurationError(f'missing preamp/booster variety input for amplifier: {type_variety} in equipment config') + raise EquipmentConfigError(f'missing preamp/booster variety input for amplifier: {type_variety} in equipment config') dual_stage_def = Model_dual_stage(preamp_variety, booster_variety) with open(config, encoding='utf-8') as f: @@ -186,9 +186,9 @@ def from_json(cls, filename, **kwargs): def nf_model(type_variety, gain_min, gain_max, nf_min, nf_max): if nf_min < -10: - raise ConfigurationError(f'Invalid nf_min value {nf_min!r} for amplifier {type_variety}') + raise EquipmentConfigError(f'Invalid nf_min value {nf_min!r} for amplifier {type_variety}') if nf_max < -10: - raise ConfigurationError(f'Invalid nf_max value {nf_max!r} for amplifier {type_variety}') + raise EquipmentConfigError(f'Invalid nf_max value {nf_max!r} for amplifier {type_variety}') # NF estimation model based on nf_min and nf_max # delta_p: max power dB difference between first and second stage coils @@ -203,7 +203,7 @@ def nf_model(type_variety, gain_min, gain_max, nf_min, nf_max): nf1 = lin2db(db2lin(nf_min) - db2lin(nf2)/db2lin(g1a_max)) if nf1 < 4: - raise ConfigurationError(f'First coil value too low {nf1} for amplifier {type_variety}') + raise EquipmentConfigError(f'First coil value too low {nf1} for amplifier {type_variety}') # Check 1 dB < delta_p < 6 dB to ensure nf_min and nf_max values make sense. # There shouldn't be high nf differences between the two coils: @@ -215,17 +215,17 @@ def nf_model(type_variety, gain_min, gain_max, nf_min, nf_max): delta_p = gain_max - g1a_max g1a_min = gain_min - (gain_max-gain_min) - delta_p if not 1 < delta_p < 11: - raise ConfigurationError(f'Computed \N{greek capital letter delta}P invalid \ + raise EquipmentConfigError(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 \ \n calculated 1st coil NF = {nf1:.2f}, 2nd coil NF = {nf2:.2f}') # Check calculated values for nf1 and nf2 calc_nf_min = lin2db(db2lin(nf1) + db2lin(nf2)/db2lin(g1a_max)) if not isclose(nf_min, calc_nf_min, abs_tol=0.01): - raise ConfigurationError(f'nf_min does not match calc_nf_min, {nf_min} vs {calc_nf_min} for amp {type_variety}') + raise EquipmentConfigError(f'nf_min does not match calc_nf_min, {nf_min} vs {calc_nf_min} for amp {type_variety}') calc_nf_max = lin2db(db2lin(nf1) + db2lin(nf2)/db2lin(g1a_min)) if not isclose(nf_max, calc_nf_max, abs_tol=0.01): - raise ConfigurationError(f'nf_max does not match calc_nf_max, {nf_max} vs {calc_nf_max} for amp {type_variety}') + raise EquipmentConfigError(f'nf_max does not match calc_nf_max, {nf_max} vs {calc_nf_max} for amp {type_variety}') return nf1, nf2, delta_p @@ -260,7 +260,7 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F trx_params = {**mode_params} # sanity check: spacing baudrate must be smaller than min spacing if trx_params['baud_rate'] > trx_params['min_spacing'] : - raise ConfigurationError(f'Inconsistency in equipment library:\n Transpoder "{trx_type_variety}" mode "{trx_params["format"]}" '+\ + raise EquipmentConfigError(f'Inconsistency in equipment library:\n Transpoder "{trx_type_variety}" mode "{trx_params["format"]}" '+\ 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", @@ -281,7 +281,7 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F # print(f'spacing {temp}') except StopIteration : if error_message: - raise ConfigurationError(f'Computation stoped: could not find tsp : {trx_type_variety} with mode: {trx_mode} in eqpt library') + raise EquipmentConfigError(f'Computation stoped: could not find tsp : {trx_type_variety} with mode: {trx_mode} in eqpt library') else: # default transponder charcteristics # mainly used with transmission_main_example.py @@ -342,7 +342,7 @@ def update_dual_stage(equipment): 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 ConfigurationError(f'Dual stage {edfa.type_variety} min gain is lower than its preamp min gain') + raise EquipmentConfigError(f'Dual stage {edfa.type_variety} min gain is lower than its preamp min gain') return equipment def equipment_from_json(json_data, filename): diff --git a/gnpy/core/exceptions.py b/gnpy/core/exceptions.py index 8f1f89652..06b35f073 100644 --- a/gnpy/core/exceptions.py +++ b/gnpy/core/exceptions.py @@ -11,3 +11,6 @@ class ConfigurationError(Exception): '''User-provided configuration contains an error''' + +class EquipmentConfigError(ConfigurationError): + '''Incomplete or wrong configuration within the equipment library''' From b7afb5f9d200d039d3636a00bc7bb298155e0466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 30 May 2019 12:39:10 +0200 Subject: [PATCH 030/117] Exceptions for errors in network topologies --- examples/path_requests_run.py | 5 +++- examples/transmission_main_example.py | 5 +++- gnpy/core/exceptions.py | 3 +++ gnpy/core/network.py | 38 +++++++-------------------- 4 files changed, 21 insertions(+), 30 deletions(-) diff --git a/examples/path_requests_run.py b/examples/path_requests_run.py index 49025998a..32b28180f 100755 --- a/examples/path_requests_run.py +++ b/examples/path_requests_run.py @@ -30,7 +30,7 @@ from gnpy.core.request import (Path_request, Result_element, compute_constrained_path, propagate, jsontocsv, Disjunction, compute_path_dsjctn, requests_aggregation, propagate_and_optimize_mode) -from gnpy.core.exceptions import ConfigurationError, EquipmentConfigError +from gnpy.core.exceptions import ConfigurationError, EquipmentConfigError, NetworkTopologyError import gnpy.core.ansi_escapes as ansi_escapes from copy import copy, deepcopy from textwrap import dedent @@ -315,6 +315,9 @@ def path_result_json(pathresult): except EquipmentConfigError as e: print(f'{ansi_escapes.red}Configuration error in the equipment library:{ansi_escapes.reset} {e}') exit(1) + except NetworkTopologyError as e: + print(f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}') + exit(1) except ConfigurationError as e: print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}') exit(1) diff --git a/examples/transmission_main_example.py b/examples/transmission_main_example.py index 89221ec89..b5c0da59c 100755 --- a/examples/transmission_main_example.py +++ b/examples/transmission_main_example.py @@ -26,7 +26,7 @@ from gnpy.core.elements import Transceiver, Fiber, Edfa, Roadm from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power, Pref from gnpy.core.request import Path_request, RequestParams, compute_constrained_path, propagate2 -from gnpy.core.exceptions import ConfigurationError, EquipmentConfigError +from gnpy.core.exceptions import ConfigurationError, EquipmentConfigError, NetworkTopologyError import gnpy.core.ansi_escapes as ansi_escapes logger = getLogger(__name__) @@ -204,6 +204,9 @@ def main(network, equipment, source, destination, req = None): except EquipmentConfigError as e: print(f'{ansi_escapes.red}Configuration error in the equipment library:{ansi_escapes.reset} {e}') exit(1) + except NetworkTopologyError as e: + print(f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}') + exit(1) except ConfigurationError as e: print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}') exit(1) diff --git a/gnpy/core/exceptions.py b/gnpy/core/exceptions.py index 06b35f073..503d9df96 100644 --- a/gnpy/core/exceptions.py +++ b/gnpy/core/exceptions.py @@ -14,3 +14,6 @@ class ConfigurationError(Exception): class EquipmentConfigError(ConfigurationError): '''Incomplete or wrong configuration within the equipment library''' + +class NetworkTopologyError(ConfigurationError): + '''Topology of user-provided network is wrong''' diff --git a/gnpy/core/network.py b/gnpy/core/network.py index fec0fb0c6..21018dda2 100644 --- a/gnpy/core/network.py +++ b/gnpy/core/network.py @@ -18,9 +18,9 @@ from gnpy.core import elements from gnpy.core.elements import Fiber, Edfa, Transceiver, Roadm, Fused from gnpy.core.equipment import edfa_nf +from gnpy.core.exceptions import ConfigurationError, NetworkTopologyError from gnpy.core.units import UNITS from gnpy.core.utils import load_json, save_json, round2float, db2lin, lin2db -from sys import exit from collections import namedtuple logger = getLogger(__name__) @@ -54,9 +54,8 @@ def network_from_json(json_data, equipment): extra_params = equipment[typ][variety] 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:' + raise ConfigurationError(f'The {typ} of variety type {variety} was not recognized:' '\nplease check it is properly defined in the eqpt_config json file') - exit() cls = getattr(elements, typ) el = cls(**el_config) g.add_node(el) @@ -74,9 +73,7 @@ def network_from_json(json_data, equipment): edge_length = 0.01 g.add_edge(nodes[from_node], nodes[to_node], weight = edge_length) except KeyError: - msg = f'In {__name__} network_from_json function:\n\tcan not find {from_node} or {to_node} defined in {cx}' - print(msg) - exit(1) + raise NetworkTopologyError(f'can not find {from_node} or {to_node} defined in {cx}') return g @@ -156,14 +153,9 @@ def select_edfa(raman_allowed, gain_target, power_target, equipment, uid): #and raman padding at the amplifier input is impossible! if len(edfa_list) < 1: - print( - f'\x1b[1;31;40m'\ - + f'CRITICAL _ ABORT: auto_design could not find any amplifier \ + raise ConfigurationError(f'auto_design could not find any amplifier \ to satisfy min gain requirement in node {uid} \ - please increase span fiber padding'\ - + '\x1b[0m' - ) - exit() + please increase span fiber padding') else: print( f'\x1b[1;31;40m'\ @@ -216,9 +208,8 @@ def target_power(network, node, equipment): #get_fiber_dp 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[Span]' + raise ConfigurationError(f'invalid delta_power_range_db definition in eqpt_config[Span]' f'delta_power_range_db: [lower_bound, upper_bound, step]') - exit() if isinstance(node, Roadm): dp = 0 @@ -231,10 +222,7 @@ def prev_node_generator(network, node): try: prev_node = next(n for n in network.predecessors(node)) except StopIteration: - msg = f'In {__name__} prev_node_generator function:\n\t{node.uid} is not properly connected, please check network topology' - print(msg) - logger.critical(msg) - exit(1) + raise NetworkTopologyError(f'Node {node.uid} is not properly connected, please check network topology') # yield and re-iterate if isinstance(prev_node, Fused) or isinstance(node, Fused): yield prev_node @@ -248,8 +236,7 @@ def next_node_generator(network, node): try: next_node = next(n for n in network.successors(node)) except StopIteration: - print(f'In {__name__} next_node_generator function:\n\t{node.uid} is not properly connected, please check network topology') - exit(1) + raise NetworkTopologyError('Node {node.uid} is not properly connected, please check network topology') # yield and re-iterate if isinstance(next_node, Fused) or isinstance(node, Fused): yield next_node @@ -438,9 +425,7 @@ def split_fiber(network, fiber, bounds, target_length, equipment): next_node = next(network.successors(fiber)) prev_node = next(network.predecessors(fiber)) except StopIteration: - - print(f'In {__name__} split_fiber function:\n\t{fiber.uid} is not properly connected, please check network topology') - exit() + raise NetworkTopologyError(f'Fiber {fiber.uid} is not properly connected, please check network topology') network.remove_node(fiber) @@ -493,10 +478,7 @@ def add_fiber_padding(network, fibers, padding): try: next_node = next(network.successors(fiber)) except StopIteration: - msg = f'In {__name__} add_fiber_padding function:\n\t{fiber.uid} is not properly connected, please check network topology' - print(msg) - logger.critical(msg) - exit(1) + raise NetworkTopologyError(f'Fiber {fiber.uid} is not properly connected, please check network topology') if this_span_loss < padding and not (isinstance(next_node, Fused)): #add a padding att_in at the input of the 1st fiber: #address the case when several fibers are spliced together From 2d66b6266bb51177f9d32fd4211e62a4df588193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 30 May 2019 12:39:42 +0200 Subject: [PATCH 031/117] Add a missing FIXME for direct-printing of diagnostics --- gnpy/core/network.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gnpy/core/network.py b/gnpy/core/network.py index 21018dda2..4f0684b30 100644 --- a/gnpy/core/network.py +++ b/gnpy/core/network.py @@ -157,6 +157,7 @@ def select_edfa(raman_allowed, gain_target, power_target, equipment, uid): to satisfy min gain requirement in node {uid} \ please increase span fiber padding') else: + # TODO: convert to logging print( f'\x1b[1;31;40m'\ + f'WARNING: target gain in node {uid} is below all available amplifiers min gain: \ From ecfc4a8cb290c29b90bbfd727c527d474c600d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 30 May 2019 13:58:33 +0200 Subject: [PATCH 032/117] One fewer exit() in a library scope --- gnpy/core/elements.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index a845b150f..36fbe405d 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -624,14 +624,8 @@ def _nf(self, type_def, nf_model, nf_fit_coeff, gain_min, gain_flatmax, gain_tar 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() + else: + assert False, "Unrecognized amplifier type, this should have been checked by the JSON loader" return nf_avg+pad, pad def _calc_nf(self, avg = False): From 1d4a8998e1d830d5d17b68a743adecfb6e8b667c Mon Sep 17 00:00:00 2001 From: EstherLerouzic Date: Thu, 23 May 2019 17:13:29 +0100 Subject: [PATCH 033/117] Bug fix on create_eqpt_sheet.py program was not correctly listing nodes when links duplicate EDFA entries. I refactored it to make it simpler to understand. I added coordinates on ../tests/data/testTopology.xls for plot purpose Signed-off-by: EstherLerouzic --- examples/create_eqpt_sheet.py | 128 +++++++++++++++++----------------- tests/data/testTopology.xls | Bin 15872 -> 15872 bytes 2 files changed, 63 insertions(+), 65 deletions(-) diff --git a/examples/create_eqpt_sheet.py b/examples/create_eqpt_sheet.py index b0f7b4ca7..704f04825 100644 --- a/examples/create_eqpt_sheet.py +++ b/examples/create_eqpt_sheet.py @@ -11,64 +11,72 @@ determined based on the topology. """ -from sys import exit try: from xlrd import open_workbook except ModuleNotFoundError: exit('Required: `pip install xlrd`') from argparse import ArgumentParser -from collections import namedtuple, defaultdict +PARSER = ArgumentParser() +PARSER.add_argument('workbook', nargs='?', default='meshTopologyExampleV2.xls', + help='create the mandatory columns in Eqpt sheet') +ALL_ROWS = lambda sh, start=0: (sh.row(x) for x in range(start, sh.nrows)) -Shortlink = namedtuple('Link', 'src dest') +class Node: + """ Node element contains uid, list of connected nodes and eqpt type + """ + def __init__(self, uid, to_node): + self.uid = uid + self.to_node = to_node + self.eqpt = None -Shortnode = namedtuple('Node', 'nodename eqt') + def __repr__(self): + return f'uid {self.uid} \nto_node {[node for node in self.to_node]}\neqpt {self.eqpt}\n' -parser = ArgumentParser() -parser.add_argument('workbook', nargs='?', default='meshTopologyExampleV2.xls', - help = 'create the mandatory columns in Eqpt sheet ') -all_rows = lambda sh, start=0: (sh.row(x) for x in range(start, sh.nrows)) + def __str__(self): + return f'uid {self.uid} \nto_node {[node for node in self.to_node]}\neqpt {self.eqpt}\n' def read_excel(input_filename): - with open_workbook(input_filename) as wb: + """ read excel Nodes and Links sheets and create a dict of nodes with + their to_nodes and type of eqpt + """ + with open_workbook(input_filename) as wobo: # reading Links sheet - links_sheet = wb.sheet_by_name('Links') - links = [] - nodeoccuranceinlinks = [] - links_by_src = defaultdict(list) - links_by_dest = defaultdict(list) - for row in all_rows(links_sheet, start=5): - links.append(Shortlink(row[0].value,row[1].value)) - links_by_src[row[0].value].append(Shortnode(row[1].value,'')) - links_by_dest[row[1].value].append(Shortnode(row[0].value,'')) - #print(f'source {links[len(links)-1].src} dest {links[len(links)-1].dest}') - nodeoccuranceinlinks.append(row[0].value) - nodeoccuranceinlinks.append(row[1].value) + links_sheet = wobo.sheet_by_name('Links') + nodes = {} + for row in ALL_ROWS(links_sheet, start=5): + try: + nodes[row[0].value].to_node.append(row[1].value) + except KeyError: + nodes[row[0].value] = Node(row[0].value, [row[1].value]) + try: + nodes[row[1].value].to_node.append(row[0].value) + except KeyError: + nodes[row[1].value] = Node(row[1].value, [row[0].value]) - # reading Nodes sheet - nodes_sheet = wb.sheet_by_name('Nodes') - nodes = [] - node_degree = [] - for row in all_rows(nodes_sheet, start=5) : + nodes_sheet = wobo.sheet_by_name('Nodes') + for row in ALL_ROWS(nodes_sheet, start=5): + node = row[0].value + eqpt = row[6].value + try: + if eqpt == 'ILA' and len(nodes[node].to_node) != 2: + print(f'Inconsistancy ILA node with degree > 2: {node} ') + exit() + if eqpt == '' and len(nodes[node].to_node) == 2: + nodes[node].eqpt = 'ILA' + elif eqpt == '' and len(nodes[node].to_node) != 2: + nodes[node].eqpt = 'ROADM' + else: + nodes[node].eqpt = eqpt + except KeyError: + print(f'inconsistancy between nodes and links sheet: {node} is not listed in links') + exit() + return nodes - temp_eqt = row[6].value - # verify node degree to confirm eqt type - node_degree.append(nodeoccuranceinlinks.count(row[0].value)) - if temp_eqt.lower() == 'ila' and nodeoccuranceinlinks.count(row[0].value) !=2 : - print(f'Inconsistancy: node {nodes[len(nodes)-1]} has degree \ - {node_degree[len(nodes)-1]} and can not be an ILA ... replaced by ROADM') - temp_eqt = 'ROADM' - if temp_eqt == '' and nodeoccuranceinlinks.count(row[0].value) == 2 : - temp_eqt = 'ILA' - if temp_eqt == '' and nodeoccuranceinlinks.count(row[0].value) != 2 : - temp_eqt = 'ROADM' - # print(f'node {nodes[len(nodes)-1]} eqt {temp_eqt}') - nodes.append(Shortnode(row[0].value,temp_eqt)) - # print(len(nodes)-1) - print(f'reading: node {nodes[len(nodes)-1].nodename} eqpt {temp_eqt}') - return links,nodes, links_by_src , links_by_dest - -def create_eqt_template(links,nodes, links_by_src , links_by_dest, input_filename): +def create_eqt_template(nodes, input_filename): + """ writes list of node A node Z corresponding to Nodes and Links sheets in order + to help user populating Eqpt + """ output_filename = f'{input_filename[:-4]}_eqpt_sheet.txt' with open(output_filename, 'w', encoding='utf-8') as my_file: # print header similar to excel @@ -77,27 +85,17 @@ def create_eqt_template(links,nodes, links_by_src , links_by_dest, input_filenam \nNode A \tNode Z \tamp type \tatt_in \tamp gain \ttilt \tatt_out\ amp type \tatt_in \tamp gain \ttilt \tatt_out\n') - tab = [] - temp = [] - i = 0 - for lk in links: - if [e for n,e in nodes if n==lk.src][0] != 'FUSED' : - temp = [lk.src , lk.dest] - tab.append(temp) - my_file.write(f'{temp[0]}\t{temp[1]}\n') - for n in nodes : - if n.eqt.lower() == 'roadm' : - for src in links_by_dest[n.nodename] : - temp = [n.nodename , src.nodename] - tab.append(temp) - # print(temp) - my_file.write(f'{temp[0]}\t{temp[1]}\n') - i = i + 1 + + for node in nodes.values(): + if node.eqpt == 'ILA': + my_file.write(f'{node.uid}\t{node.to_node[0]}\n') + if node.eqpt == 'ROADM': + for to_node in node.to_node: + my_file.write(f'{node.uid}\t{to_node}\n') + print(f'File {output_filename} successfully created with Node A - Node Z ' + - ' entries for Eqpt sheet in excel file.') + ' entries for Eqpt sheet in excel file.') if __name__ == '__main__': - args = parser.parse_args() - input_filename = args.workbook - links,nodes,links_by_src, links_by_dest = read_excel(input_filename) - create_eqt_template(links,nodes, links_by_src , links_by_dest , input_filename) + ARGS = PARSER.parse_args() + create_eqt_template(read_excel(ARGS.workbook), ARGS.workbook) diff --git a/tests/data/testTopology.xls b/tests/data/testTopology.xls index 92cc4a991fbf4737d21e911da82eb74d1b5ff32a..85576f21441ac7f836447d56636e5d5ccc4ae0b6 100644 GIT binary patch delta 975 zcmY*YJ!=$E6uoagc4l|Jb|*V=XV=kH2sWmO2}G?_2qMJ7!fLA^TLj7CqL?O{6%vxh z>R};5Itx2-Yy-wZa2hKW|AC<8HWq^Sz8QIUXBe1s&%Ari{g}zlWasVm;QY^dz{`g9 z&C&gb`{0A@Q^10?@q4LX&exy@Ltpa_ zfGG0gc4~=Ge(wxKESJ-P{MKp8qjX&u@>e==NGyU--tOLYV)Y)&H{El|F~rbBe-U(5 zl)kg&=WceBn3iIwrc%y^ztF&F4Laaqlu((CPSc5)<*KP%qL(R^ImXZ#TV@s0W-29f zrB~`Fj8WN?Vlty&x;sv%=6x6-^90T`24>ztket?5)eQjPZkd4 z(doxa!%%meCdMad(1h5;&?-)3EgQR@U8Sr0N zR+}1XOn#H5#^m?CuQ4_O`VEa~lMRN+{~`Mm(EklcSVOP?7`1f+8n}PF0UmDUHcQ~E x`hF~K+wWF3On+s$ob`*R2cP8D>REU6<>kk{)>k2)Sr_vUtCvkN&Hr3=%zwZWd7A(L delta 815 zcmZWnJxIeq82v7nCTWt|A4_T4lu`w85XB#;h=_^~q7DvDor*&hQLG9gC|1!yM{jp6 zZpGjzh@;U>6a+zVbnYM`_+1e1#5)N0@-E-^-pg0psqHkzB5i{yz!id-rPbw)Be3Bu z&tIr8SzX&af+!4o3z4usSBXbKL1GuL5Zr!5UnF4#wxGa%%i(gqu=U1qy38*vxgk$_SD_*psZN)@083HrhlW%pg5y_FVWfCnH4 zqHx1f0H_@eHgi?OiU(y0#3AlgJEq89N(zG4Igl`KSC5K56lM5^z$*S1KPGSp{mZ-# zI#eVb1{i<%FoYA(lnR)E(CN;yTA=?^urO4ff`!{Oc?x??!$FN=44M&vgE43kn5$34 z1m@~5ZGl6WTEP^UkBKT_?&d@qGjuZ!DJdSb18YeUhlzUSIJoX@sem20J?`ELo%S~_ q2Fld#D+aD>FWrZiY^9htZl>3J?rt9l+gH21>*63K6W+&=PQL-*Ty2K{ From 279d08a0e874e555b5883ea01f2e494756f279b9 Mon Sep 17 00:00:00 2001 From: EstherLerouzic Date: Fri, 24 May 2019 12:40:21 +0100 Subject: [PATCH 034/117] Correct testTopology file testTopology listed corlay twice in Eqpt although it is a ILA. should appear only once in node A column update expected parser results for this change in tests/data Signed-off-by: EstherLerouzic --- tests/data/testTopology.xls | Bin 15872 -> 16896 bytes .../testTopology_auto_design_expected.json | 434 +++++++++--------- tests/data/testTopology_expected.json | 245 +++++----- 3 files changed, 328 insertions(+), 351 deletions(-) diff --git a/tests/data/testTopology.xls b/tests/data/testTopology.xls index 85576f21441ac7f836447d56636e5d5ccc4ae0b6..a7885c6b3aa0c687fc93968120177afb4440925e 100644 GIT binary patch delta 2182 zcmZuyOKTHR6h3n&&8tmrl1Wl)n#Q!YO>8L^9|cjgD_0g=h_8Z_xDiFDP#032itmLW z+@(u@fsI>1cL8xB=)$Et!Ck?Hg4Xlh$<3XN$uRAl?>p~%X3o~;)tMc z>l0IAY>a`ueE;3u-Njw)KG)jo;)$~z`z##M{t|y47YlRhYUZV=IBh3;J$|;8mn2yE zE{%}GS0`g-!Axh^$}SD{@o|b%YsB&iBB#@STl}7=lgmC&@oj!plH^J)Is7Qq`pi?c zQoX&{%323VAe@juz)%8l9pGnmJ((@*2o9C`a$WUt{*n4M^WOJD>A;&Gl2OhpX`{L9 zqLZ|8jAGQuS$T-d3-bv|P%D&8l9GLVic*ub6LOG7y4?UIvby*gb-A&o4mWc{+$~C~ zPYv&cpyN)^8A(ZzqZA8KjD7uw4Fgm4rk9UuXVkZbr!F*eRro{OqZ&LpYo>!g)m zoAsl7X@E_605&=R+cN;efJS3rV7u5QZdFix#ezL-d;o?Ck2=JA5yfili_Ml(MYsP@ znvWFYm+wh}rBmv|fy!vHizV0vV=TR;0wvUyxl$>?c9K5yG!okC>uBXP`Ut-`Gal=n zgeW+1a5y{5rF91AB{-a+q?~#`SMo=K=PrZyQ4;zKy^Q8e-Wkn#Jq{T!vvf~DGse;n z?iuWmgCwJw?W-QyT2A_X0a2JFi~-!_G-q0bG%T0boGB>ZYai?iTGMa^t%kq@V)UQerykBCzH5_W6X4B%@hj&bO)LPD0C>QxFmBqj-U2NLYM$6O|4XvygypgJ?ZTIbC}|L-$Q zH-Dsfk_%>^E$<}9xNCNy?qsa27i!kZ*g#&+%Gf~LDz#8sqqIe_cOHk! zL=*-O>e)Y_-5xEd2M_6?2=)&UJP3*x<3Yj3H=9jn(jAuJoA=Fo?|buJbDcErKcDf( zA^Sprk}F}=<@>py{`-$3_nE#eDFnn_F%Ri zsLL|Ao(z@2wBT0i{CD{d^UTbR2`0MP5PzoLuSHrO(p6K#QpX#vLK$E{iz%Fg9JE4( zP1L2^ZLp!PrNV#>*d1^x(AFHdGS@5s&>&5hi=Y6kcRF6tpz|4^KW$1WAJGaIYI z(>Ia~hNm2QHkR^>8Iz^VB}A)KGIPlMJ}zEWMu$ro#pyfSfg|9En{hPhU|0^jeTJ=t zld3pGVlR%?-2Ppeh}6bmD7)pQiS%T6_z>NcWg;Hdi^G+SnNRNx_faaik94WB3pw)Jv4e{&Dkk@@m0s#cFftT8B1d~_O)AkikrzcQYc3l)IEW2)7 zT%J2tqpmuYy|$svpzO6s>s{&MatH~MmaNX1HV_9$}cjr!<;_BIpTjy$q vf9hm+GGDAd{zye*{wQngz5K9V*eBv)E>7OfZ&NZV?k+x}?Kt_nXc_+jE>xNt diff --git a/tests/data/testTopology_auto_design_expected.json b/tests/data/testTopology_auto_design_expected.json index 47678832e..bb5273d9b 100644 --- a/tests/data/testTopology_auto_design_expected.json +++ b/tests/data/testTopology_auto_design_expected.json @@ -41,7 +41,7 @@ "type": "Transceiver", "metadata": { "location": { - "latitude": 0.0, + "latitude": 4.0, "longitude": 0.0, "city": "Rennes_STA", "region": "RLD" @@ -53,7 +53,7 @@ "type": "Transceiver", "metadata": { "location": { - "latitude": 4.0, + "latitude": 0.0, "longitude": 0.0, "city": "Brest_KLA", "region": "RLD" @@ -65,8 +65,8 @@ "type": "Transceiver", "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 6.0, + "longitude": 0.0, "city": "a", "region": "" } @@ -77,8 +77,8 @@ "type": "Transceiver", "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 5.0, + "longitude": 0.0, "city": "b", "region": "" } @@ -89,8 +89,8 @@ "type": "Transceiver", "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 6.0, + "longitude": 1.0, "city": "c", "region": "" } @@ -101,8 +101,8 @@ "type": "Transceiver", "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 6.0, + "longitude": 4.0, "city": "d", "region": "" } @@ -113,8 +113,8 @@ "type": "Transceiver", "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 5.0, + "longitude": 4.0, "city": "e", "region": "" } @@ -125,8 +125,8 @@ "type": "Transceiver", "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 5.0, + "longitude": 1.0, "city": "f", "region": "" } @@ -137,8 +137,8 @@ "type": "Transceiver", "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 5.0, + "longitude": 3.0, "city": "g", "region": "" } @@ -149,8 +149,8 @@ "type": "Transceiver", "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 5.0, + "longitude": 2.0, "city": "h", "region": "" } @@ -209,7 +209,7 @@ }, "metadata": { "location": { - "latitude": 0.0, + "latitude": 4.0, "longitude": 0.0, "city": "Rennes_STA", "region": "RLD" @@ -224,7 +224,7 @@ }, "metadata": { "location": { - "latitude": 4.0, + "latitude": 0.0, "longitude": 0.0, "city": "Brest_KLA", "region": "RLD" @@ -239,8 +239,8 @@ }, "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 6.0, + "longitude": 0.0, "city": "a", "region": "" } @@ -254,8 +254,8 @@ }, "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 5.0, + "longitude": 0.0, "city": "b", "region": "" } @@ -269,8 +269,8 @@ }, "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 6.0, + "longitude": 1.0, "city": "c", "region": "" } @@ -284,8 +284,8 @@ }, "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 6.0, + "longitude": 4.0, "city": "d", "region": "" } @@ -299,8 +299,8 @@ }, "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 5.0, + "longitude": 4.0, "city": "e", "region": "" } @@ -314,8 +314,8 @@ }, "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 5.0, + "longitude": 1.0, "city": "f", "region": "" } @@ -329,8 +329,8 @@ }, "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 5.0, + "longitude": 3.0, "city": "g", "region": "" } @@ -344,8 +344,8 @@ }, "metadata": { "location": { - "latitude": 0, - "longitude": 0, + "latitude": 5.0, + "longitude": 2.0, "city": "h", "region": "" } @@ -380,7 +380,7 @@ "type": "Fused", "metadata": { "location": { - "latitude": 3.0, + "latitude": 1.0, "longitude": 0.0, "city": "Morlaix", "region": "RLD" @@ -416,7 +416,7 @@ "type": "Fused", "metadata": { "location": { - "latitude": 3.0, + "latitude": 1.0, "longitude": 0.0, "city": "Morlaix", "region": "RLD" @@ -526,7 +526,7 @@ }, "metadata": { "location": { - "latitude": 1.5, + "latitude": 2.5, "longitude": 0.0, "city": null, "region": null @@ -548,7 +548,7 @@ }, "metadata": { "location": { - "latitude": 0.5, + "latitude": 3.5, "longitude": 0.0, "city": null, "region": null @@ -570,7 +570,7 @@ }, "metadata": { "location": { - "latitude": 2.5, + "latitude": 1.5, "longitude": 0.0, "city": null, "region": null @@ -592,7 +592,7 @@ }, "metadata": { "location": { - "latitude": 3.5, + "latitude": 0.5, "longitude": 0.0, "city": null, "region": null @@ -614,8 +614,8 @@ }, "metadata": { "location": { - "latitude": 2.5, - "longitude": 0.5, + "latitude": 0.0, + "longitude": 1.5, "city": null, "region": null } @@ -636,8 +636,8 @@ }, "metadata": { "location": { - "latitude": 1.5, - "longitude": 2.0, + "latitude": 1.0, + "longitude": 3.0, "city": null, "region": null } @@ -658,8 +658,8 @@ }, "metadata": { "location": { - "latitude": 1.5, - "longitude": 3.0, + "latitude": 3.0, + "longitude": 4.0, "city": null, "region": null } @@ -680,8 +680,8 @@ }, "metadata": { "location": { - "latitude": 0.5, - "longitude": 1.0, + "latitude": 4.0, + "longitude": 2.0, "city": null, "region": null } @@ -702,7 +702,7 @@ }, "metadata": { "location": { - "latitude": 0.0, + "latitude": 5.5, "longitude": 0.0, "city": null, "region": null @@ -724,8 +724,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 0.5, "city": null, "region": null } @@ -746,8 +746,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 2.5, "city": null, "region": null } @@ -768,8 +768,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.5, + "longitude": 1.0, "city": null, "region": null } @@ -790,8 +790,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 0.5, "city": null, "region": null } @@ -812,8 +812,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.5, + "longitude": 4.0, "city": null, "region": null } @@ -834,8 +834,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 3.5, "city": null, "region": null } @@ -856,8 +856,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 1.5, "city": null, "region": null } @@ -878,8 +878,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 2.5, "city": null, "region": null } @@ -988,7 +988,7 @@ }, "metadata": { "location": { - "latitude": 1.5, + "latitude": 2.5, "longitude": 0.0, "city": null, "region": null @@ -1010,7 +1010,7 @@ }, "metadata": { "location": { - "latitude": 0.5, + "latitude": 3.5, "longitude": 0.0, "city": null, "region": null @@ -1032,7 +1032,7 @@ }, "metadata": { "location": { - "latitude": 2.5, + "latitude": 1.5, "longitude": 0.0, "city": null, "region": null @@ -1054,7 +1054,7 @@ }, "metadata": { "location": { - "latitude": 3.5, + "latitude": 0.5, "longitude": 0.0, "city": null, "region": null @@ -1076,8 +1076,8 @@ }, "metadata": { "location": { - "latitude": 2.5, - "longitude": 0.5, + "latitude": 0.0, + "longitude": 1.5, "city": null, "region": null } @@ -1098,8 +1098,8 @@ }, "metadata": { "location": { - "latitude": 1.5, - "longitude": 2.0, + "latitude": 1.0, + "longitude": 3.0, "city": null, "region": null } @@ -1120,8 +1120,8 @@ }, "metadata": { "location": { - "latitude": 1.5, - "longitude": 3.0, + "latitude": 3.0, + "longitude": 4.0, "city": null, "region": null } @@ -1142,8 +1142,8 @@ }, "metadata": { "location": { - "latitude": 0.5, - "longitude": 1.0, + "latitude": 4.0, + "longitude": 2.0, "city": null, "region": null } @@ -1164,7 +1164,7 @@ }, "metadata": { "location": { - "latitude": 0.0, + "latitude": 5.5, "longitude": 0.0, "city": null, "region": null @@ -1186,8 +1186,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 0.5, "city": null, "region": null } @@ -1208,8 +1208,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 2.5, "city": null, "region": null } @@ -1230,8 +1230,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.5, + "longitude": 1.0, "city": null, "region": null } @@ -1252,8 +1252,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 0.5, "city": null, "region": null } @@ -1274,8 +1274,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.5, + "longitude": 4.0, "city": null, "region": null } @@ -1296,8 +1296,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 3.5, "city": null, "region": null } @@ -1318,8 +1318,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 1.5, "city": null, "region": null } @@ -1340,8 +1340,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 2.5, "city": null, "region": null } @@ -1397,7 +1397,7 @@ }, "metadata": { "location": { - "latitude": 1.0, + "latitude": 3.0, "longitude": 0.0, "city": "Stbrieuc", "region": "RLD" @@ -1435,7 +1435,7 @@ }, "metadata": { "location": { - "latitude": 4.0, + "latitude": 0.0, "longitude": 0.0, "city": "Brest_KLA", "region": "RLD" @@ -1454,8 +1454,8 @@ }, "metadata": { "location": { - "latitude": 1.0, - "longitude": 2.0, + "latitude": 4.0, + "longitude": 4.0, "city": "Ploermel", "region": "RLD" } @@ -1499,25 +1499,6 @@ } } }, - { - "uid": "west edfa in Quimper to Lorient_KMA", - "type": "Edfa", - "type_variety": "std_low_gain", - "operational": { - "gain_target": 19.0, - "delta_p": null, - "tilt_target": 0, - "out_voa": 0 - }, - "metadata": { - "location": { - "latitude": 1.0, - "longitude": 1.0, - "city": "Quimper", - "region": "RLD" - } - } - }, { "uid": "Edfa0_roadm Lorient_KMA", "type": "Edfa", @@ -1568,8 +1549,8 @@ }, "metadata": { "location": { - "latitude": 1.75, - "longitude": 2.5, + "latitude": 1.5, + "longitude": 3.0, "city": "Lorient_KMA", "region": "RLD" } @@ -1606,8 +1587,8 @@ }, "metadata": { "location": { - "latitude": 1.75, - "longitude": 3.5, + "latitude": 2.5, + "longitude": 4.0, "city": "Vannes_KBE", "region": "RLD" } @@ -1625,7 +1606,7 @@ }, "metadata": { "location": { - "latitude": 0.25, + "latitude": 3.75, "longitude": 0.0, "city": "Rennes_STA", "region": "RLD" @@ -1644,8 +1625,8 @@ }, "metadata": { "location": { - "latitude": 0.25, - "longitude": 0.5, + "latitude": 4.0, + "longitude": 1.0, "city": "Rennes_STA", "region": "RLD" } @@ -1663,7 +1644,7 @@ }, "metadata": { "location": { - "latitude": 3.75, + "latitude": 0.25, "longitude": 0.0, "city": "Brest_KLA", "region": "RLD" @@ -1682,7 +1663,7 @@ }, "metadata": { "location": { - "latitude": 0.0, + "latitude": 5.75, "longitude": 0.0, "city": "a", "region": "" @@ -1701,8 +1682,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 0.25, "city": "a", "region": "" } @@ -1720,7 +1701,7 @@ }, "metadata": { "location": { - "latitude": 0.0, + "latitude": 5.25, "longitude": 0.0, "city": "b", "region": "" @@ -1739,8 +1720,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 0.25, "city": "b", "region": "" } @@ -1758,8 +1739,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 0.75, "city": "c", "region": "" } @@ -1777,8 +1758,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 1.75, "city": "c", "region": "" } @@ -1796,8 +1777,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.75, + "longitude": 1.0, "city": "c", "region": "" } @@ -1815,8 +1796,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 3.25, "city": "d", "region": "" } @@ -1834,8 +1815,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.75, + "longitude": 4.0, "city": "d", "region": "" } @@ -1853,8 +1834,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.25, + "longitude": 4.0, "city": "e", "region": "" } @@ -1872,8 +1853,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 3.75, "city": "e", "region": "" } @@ -1891,8 +1872,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.25, + "longitude": 1.0, "city": "f", "region": "" } @@ -1910,8 +1891,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 0.75, "city": "f", "region": "" } @@ -1929,8 +1910,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 1.25, "city": "f", "region": "" } @@ -1948,8 +1929,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 3.25, "city": "g", "region": "" } @@ -1967,8 +1948,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 2.75, "city": "g", "region": "" } @@ -1986,8 +1967,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 1.75, "city": "h", "region": "" } @@ -2005,8 +1986,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 2.25, "city": "h", "region": "" } @@ -2062,7 +2043,7 @@ }, "metadata": { "location": { - "latitude": 0.25, + "latitude": 3.75, "longitude": 0.0, "city": null, "region": null @@ -2081,7 +2062,7 @@ }, "metadata": { "location": { - "latitude": 3.75, + "latitude": 0.25, "longitude": 0.0, "city": null, "region": null @@ -2100,8 +2081,8 @@ }, "metadata": { "location": { - "latitude": 2.0, - "longitude": 1.25, + "latitude": 0.5, + "longitude": 2.25, "city": null, "region": null } @@ -2119,8 +2100,8 @@ }, "metadata": { "location": { - "latitude": 1.75, - "longitude": 2.5, + "latitude": 1.5, + "longitude": 3.0, "city": null, "region": null } @@ -2138,8 +2119,8 @@ }, "metadata": { "location": { - "latitude": 1.75, - "longitude": 3.5, + "latitude": 2.5, + "longitude": 4.0, "city": null, "region": null } @@ -2157,8 +2138,8 @@ }, "metadata": { "location": { - "latitude": 0.25, - "longitude": 0.5, + "latitude": 4.0, + "longitude": 1.0, "city": null, "region": null } @@ -2176,7 +2157,7 @@ }, "metadata": { "location": { - "latitude": 0.0, + "latitude": 5.25, "longitude": 0.0, "city": null, "region": null @@ -2195,8 +2176,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 0.75, "city": null, "region": null } @@ -2214,8 +2195,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 3.25, "city": null, "region": null } @@ -2233,8 +2214,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.25, + "longitude": 1.0, "city": null, "region": null } @@ -2252,8 +2233,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 0.75, "city": null, "region": null } @@ -2271,8 +2252,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.75, + "longitude": 4.0, "city": null, "region": null } @@ -2290,8 +2271,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 3.25, "city": null, "region": null } @@ -2309,8 +2290,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 1.75, "city": null, "region": null } @@ -2328,8 +2309,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 2.75, "city": null, "region": null } @@ -2366,7 +2347,7 @@ }, "metadata": { "location": { - "latitude": 1.75, + "latitude": 2.25, "longitude": 0.0, "city": null, "region": null @@ -2385,7 +2366,7 @@ }, "metadata": { "location": { - "latitude": 1.0, + "latitude": 3.0, "longitude": 0.0, "city": null, "region": null @@ -2397,15 +2378,34 @@ "type": "Edfa", "type_variety": "std_low_gain", "operational": { - "gain_target": 10.0, + "gain_target": 15.0, "delta_p": null, "tilt_target": 0, "out_voa": 0 }, "metadata": { "location": { - "latitude": 3.25, - "longitude": 0.25, + "latitude": 0.0, + "longitude": 0.75, + "city": null, + "region": null + } + } + }, + { + "uid": "Edfa0_fiber (Lorient_KMA → Quimper)-", + "type": "Edfa", + "type_variety": "std_low_gain", + "operational": { + "gain_target": 14.0, + "delta_p": null, + "tilt_target": 0, + "out_voa": 0 + }, + "metadata": { + "location": { + "latitude": 0.5, + "longitude": 2.25, "city": null, "region": null } @@ -2423,8 +2423,8 @@ }, "metadata": { "location": { - "latitude": 1.0, - "longitude": 2.0, + "latitude": 3.5, + "longitude": 3.0, "city": null, "region": null } @@ -2442,7 +2442,7 @@ }, "metadata": { "location": { - "latitude": 0.0, + "latitude": 5.75, "longitude": 0.0, "city": null, "region": null @@ -2461,8 +2461,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 0.25, "city": null, "region": null } @@ -2480,8 +2480,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 6.0, + "longitude": 1.75, "city": null, "region": null } @@ -2499,8 +2499,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.75, + "longitude": 1.0, "city": null, "region": null } @@ -2518,8 +2518,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 0.25, "city": null, "region": null } @@ -2537,8 +2537,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.25, + "longitude": 4.0, "city": null, "region": null } @@ -2556,8 +2556,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 3.75, "city": null, "region": null } @@ -2575,8 +2575,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 1.25, "city": null, "region": null } @@ -2594,8 +2594,8 @@ }, "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0, + "latitude": 5.0, + "longitude": 2.25, "city": null, "region": null } @@ -2973,7 +2973,7 @@ }, { "from_node": "fiber (Lorient_KMA → Quimper)-", - "to_node": "west edfa in Quimper to Lorient_KMA" + "to_node": "Edfa0_fiber (Lorient_KMA → Quimper)-" }, { "from_node": "fiber (Vannes_KBE → Ploermel)-", @@ -3051,10 +3051,6 @@ "from_node": "west edfa in Lannion_CAS to Morlaix", "to_node": "roadm Lannion_CAS" }, - { - "from_node": "west edfa in Quimper to Lorient_KMA", - "to_node": "fiber (Quimper → Brest_KLA)-" - }, { "from_node": "Edfa0_roadm Lorient_KMA", "to_node": "fiber (Lorient_KMA → Loudeac)-F054" @@ -3243,6 +3239,10 @@ "from_node": "Edfa0_fiber (Quimper → Brest_KLA)-", "to_node": "roadm Brest_KLA" }, + { + "from_node": "Edfa0_fiber (Lorient_KMA → Quimper)-", + "to_node": "fiber (Quimper → Brest_KLA)-" + }, { "from_node": "Edfa0_fiber (Vannes_KBE → Ploermel)-", "to_node": "fiber (Ploermel → Rennes_STA)-" diff --git a/tests/data/testTopology_expected.json b/tests/data/testTopology_expected.json index 133a1fbe9..3e54d2a0a 100644 --- a/tests/data/testTopology_expected.json +++ b/tests/data/testTopology_expected.json @@ -42,7 +42,7 @@ "location": { "city": "Rennes_STA", "region": "RLD", - "latitude": 0.0, + "latitude": 4.0, "longitude": 0.0 } }, @@ -54,7 +54,7 @@ "location": { "city": "Brest_KLA", "region": "RLD", - "latitude": 4.0, + "latitude": 0.0, "longitude": 0.0 } }, @@ -66,8 +66,8 @@ "location": { "city": "a", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 6.0, + "longitude": 0.0 } }, "type": "Transceiver" @@ -78,8 +78,8 @@ "location": { "city": "b", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 5.0, + "longitude": 0.0 } }, "type": "Transceiver" @@ -90,8 +90,8 @@ "location": { "city": "c", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 6.0, + "longitude": 1.0 } }, "type": "Transceiver" @@ -102,8 +102,8 @@ "location": { "city": "d", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 6.0, + "longitude": 4.0 } }, "type": "Transceiver" @@ -114,8 +114,8 @@ "location": { "city": "e", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 5.0, + "longitude": 4.0 } }, "type": "Transceiver" @@ -126,8 +126,8 @@ "location": { "city": "f", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 5.0, + "longitude": 1.0 } }, "type": "Transceiver" @@ -138,8 +138,8 @@ "location": { "city": "g", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 5.0, + "longitude": 3.0 } }, "type": "Transceiver" @@ -150,8 +150,8 @@ "location": { "city": "h", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 5.0, + "longitude": 2.0 } }, "type": "Transceiver" @@ -198,7 +198,7 @@ "location": { "city": "Rennes_STA", "region": "RLD", - "latitude": 0.0, + "latitude": 4.0, "longitude": 0.0 } }, @@ -210,7 +210,7 @@ "location": { "city": "Brest_KLA", "region": "RLD", - "latitude": 4.0, + "latitude": 0.0, "longitude": 0.0 } }, @@ -222,8 +222,8 @@ "location": { "city": "a", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 6.0, + "longitude": 0.0 } }, "type": "Roadm" @@ -234,8 +234,8 @@ "location": { "city": "b", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 5.0, + "longitude": 0.0 } }, "type": "Roadm" @@ -246,8 +246,8 @@ "location": { "city": "c", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 6.0, + "longitude": 1.0 } }, "type": "Roadm" @@ -258,8 +258,8 @@ "location": { "city": "d", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 6.0, + "longitude": 4.0 } }, "type": "Roadm" @@ -270,8 +270,8 @@ "location": { "city": "e", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 5.0, + "longitude": 4.0 } }, "type": "Roadm" @@ -282,8 +282,8 @@ "location": { "city": "f", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 5.0, + "longitude": 1.0 } }, "type": "Roadm" @@ -294,8 +294,8 @@ "location": { "city": "g", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 5.0, + "longitude": 3.0 } }, "type": "Roadm" @@ -306,8 +306,8 @@ "location": { "city": "h", "region": "", - "latitude": 0, - "longitude": 0 + "latitude": 5.0, + "longitude": 2.0 } }, "type": "Roadm" @@ -342,7 +342,7 @@ "location": { "city": "Morlaix", "region": "RLD", - "latitude": 3.0, + "latitude": 1.0, "longitude": 0.0 } }, @@ -378,7 +378,7 @@ "location": { "city": "Morlaix", "region": "RLD", - "latitude": 3.0, + "latitude": 1.0, "longitude": 0.0 } }, @@ -460,7 +460,7 @@ "uid": "fiber (Lannion_CAS → Stbrieuc)-F056", "metadata": { "location": { - "latitude": 1.5, + "latitude": 2.5, "longitude": 0.0 } }, @@ -478,7 +478,7 @@ "uid": "fiber (Stbrieuc → Rennes_STA)-F057", "metadata": { "location": { - "latitude": 0.5, + "latitude": 3.5, "longitude": 0.0 } }, @@ -496,7 +496,7 @@ "uid": "fiber (Lannion_CAS → Morlaix)-F059", "metadata": { "location": { - "latitude": 2.5, + "latitude": 1.5, "longitude": 0.0 } }, @@ -514,7 +514,7 @@ "uid": "fiber (Morlaix → Brest_KLA)-F060", "metadata": { "location": { - "latitude": 3.5, + "latitude": 0.5, "longitude": 0.0 } }, @@ -532,8 +532,8 @@ "uid": "fiber (Brest_KLA → Quimper)-", "metadata": { "location": { - "latitude": 2.5, - "longitude": 0.5 + "latitude": 0.0, + "longitude": 1.5 } }, "type": "Fiber", @@ -550,8 +550,8 @@ "uid": "fiber (Quimper → Lorient_KMA)-", "metadata": { "location": { - "latitude": 1.5, - "longitude": 2.0 + "latitude": 1.0, + "longitude": 3.0 } }, "type": "Fiber", @@ -568,8 +568,8 @@ "uid": "fiber (Ploermel → Vannes_KBE)-", "metadata": { "location": { - "latitude": 1.5, - "longitude": 3.0 + "latitude": 3.0, + "longitude": 4.0 } }, "type": "Fiber", @@ -586,8 +586,8 @@ "uid": "fiber (Ploermel → Rennes_STA)-", "metadata": { "location": { - "latitude": 0.5, - "longitude": 1.0 + "latitude": 4.0, + "longitude": 2.0 } }, "type": "Fiber", @@ -604,7 +604,7 @@ "uid": "fiber (a → b)-", "metadata": { "location": { - "latitude": 0.0, + "latitude": 5.5, "longitude": 0.0 } }, @@ -622,8 +622,8 @@ "uid": "fiber (a → c)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 6.0, + "longitude": 0.5 } }, "type": "Fiber", @@ -640,8 +640,8 @@ "uid": "fiber (c → d)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 6.0, + "longitude": 2.5 } }, "type": "Fiber", @@ -658,8 +658,8 @@ "uid": "fiber (c → f)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.5, + "longitude": 1.0 } }, "type": "Fiber", @@ -676,8 +676,8 @@ "uid": "fiber (b → f)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.0, + "longitude": 0.5 } }, "type": "Fiber", @@ -694,8 +694,8 @@ "uid": "fiber (e → d)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.5, + "longitude": 4.0 } }, "type": "Fiber", @@ -712,8 +712,8 @@ "uid": "fiber (e → g)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.0, + "longitude": 3.5 } }, "type": "Fiber", @@ -730,8 +730,8 @@ "uid": "fiber (f → h)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.0, + "longitude": 1.5 } }, "type": "Fiber", @@ -748,8 +748,8 @@ "uid": "fiber (h → g)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.0, + "longitude": 2.5 } }, "type": "Fiber", @@ -838,7 +838,7 @@ "uid": "fiber (Stbrieuc → Lannion_CAS)-F056", "metadata": { "location": { - "latitude": 1.5, + "latitude": 2.5, "longitude": 0.0 } }, @@ -856,7 +856,7 @@ "uid": "fiber (Rennes_STA → Stbrieuc)-F057", "metadata": { "location": { - "latitude": 0.5, + "latitude": 3.5, "longitude": 0.0 } }, @@ -874,7 +874,7 @@ "uid": "fiber (Morlaix → Lannion_CAS)-F059", "metadata": { "location": { - "latitude": 2.5, + "latitude": 1.5, "longitude": 0.0 } }, @@ -892,7 +892,7 @@ "uid": "fiber (Brest_KLA → Morlaix)-F060", "metadata": { "location": { - "latitude": 3.5, + "latitude": 0.5, "longitude": 0.0 } }, @@ -910,8 +910,8 @@ "uid": "fiber (Quimper → Brest_KLA)-", "metadata": { "location": { - "latitude": 2.5, - "longitude": 0.5 + "latitude": 0.0, + "longitude": 1.5 } }, "type": "Fiber", @@ -928,8 +928,8 @@ "uid": "fiber (Lorient_KMA → Quimper)-", "metadata": { "location": { - "latitude": 1.5, - "longitude": 2.0 + "latitude": 1.0, + "longitude": 3.0 } }, "type": "Fiber", @@ -946,8 +946,8 @@ "uid": "fiber (Vannes_KBE → Ploermel)-", "metadata": { "location": { - "latitude": 1.5, - "longitude": 3.0 + "latitude": 3.0, + "longitude": 4.0 } }, "type": "Fiber", @@ -964,8 +964,8 @@ "uid": "fiber (Rennes_STA → Ploermel)-", "metadata": { "location": { - "latitude": 0.5, - "longitude": 1.0 + "latitude": 4.0, + "longitude": 2.0 } }, "type": "Fiber", @@ -982,7 +982,7 @@ "uid": "fiber (b → a)-", "metadata": { "location": { - "latitude": 0.0, + "latitude": 5.5, "longitude": 0.0 } }, @@ -1000,8 +1000,8 @@ "uid": "fiber (c → a)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 6.0, + "longitude": 0.5 } }, "type": "Fiber", @@ -1018,8 +1018,8 @@ "uid": "fiber (d → c)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 6.0, + "longitude": 2.5 } }, "type": "Fiber", @@ -1036,8 +1036,8 @@ "uid": "fiber (f → c)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.5, + "longitude": 1.0 } }, "type": "Fiber", @@ -1054,8 +1054,8 @@ "uid": "fiber (f → b)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.0, + "longitude": 0.5 } }, "type": "Fiber", @@ -1072,8 +1072,8 @@ "uid": "fiber (d → e)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.5, + "longitude": 4.0 } }, "type": "Fiber", @@ -1090,8 +1090,8 @@ "uid": "fiber (g → e)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.0, + "longitude": 3.5 } }, "type": "Fiber", @@ -1108,8 +1108,8 @@ "uid": "fiber (h → f)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.0, + "longitude": 1.5 } }, "type": "Fiber", @@ -1126,8 +1126,8 @@ "uid": "fiber (g → h)-", "metadata": { "location": { - "latitude": 0.0, - "longitude": 0.0 + "latitude": 5.0, + "longitude": 2.5 } }, "type": "Fiber", @@ -1179,41 +1179,41 @@ } }, { - "uid": "east edfa in Stbrieuc to Rennes_STA", + "uid": "east edfa in Lannion_CAS to Morlaix", "metadata": { "location": { - "city": "Stbrieuc", + "city": "Lannion_CAS", "region": "RLD", - "latitude": 1.0, + "latitude": 2.0, "longitude": 0.0 } }, "type": "Edfa", - "type_variety": "std_medium_gain", + "type_variety": "std_low_gain", "operational": { - "gain_target": 18.5, + "gain_target": 18.0, "delta_p": null, "tilt_target": 0, - "out_voa": null + "out_voa": 0.5 } }, { - "uid": "east edfa in Lannion_CAS to Morlaix", + "uid": "east edfa in Stbrieuc to Rennes_STA", "metadata": { "location": { - "city": "Lannion_CAS", + "city": "Stbrieuc", "region": "RLD", - "latitude": 2.0, + "latitude": 3.0, "longitude": 0.0 } }, "type": "Edfa", - "type_variety": "std_low_gain", + "type_variety": "std_medium_gain", "operational": { - "gain_target": 18.0, + "gain_target": 18.5, "delta_p": null, "tilt_target": 0, - "out_voa": 0.5 + "out_voa": null } }, { @@ -1222,7 +1222,7 @@ "location": { "city": "Brest_KLA", "region": "RLD", - "latitude": 4.0, + "latitude": 0.0, "longitude": 0.0 } }, @@ -1241,8 +1241,8 @@ "location": { "city": "Ploermel", "region": "RLD", - "latitude": 1.0, - "longitude": 2.0 + "latitude": 4.0, + "longitude": 4.0 } }, "type": "Edfa", @@ -1291,25 +1291,6 @@ "tilt_target": 0, "out_voa": null } - }, - { - "uid": "west edfa in Quimper to Lorient_KMA", - "metadata": { - "location": { - "city": "Quimper", - "region": "RLD", - "latitude": 1.0, - "longitude": 1.0 - } - }, - "type": "Edfa", - "type_variety": "std_low_gain", - "operational": { - "gain_target": 19.0, - "delta_p": null, - "tilt_target": 0, - "out_voa": null - } } ], "connections": [ @@ -1499,10 +1480,6 @@ }, { "from_node": "fiber (Lorient_KMA → Quimper)-", - "to_node": "west edfa in Quimper to Lorient_KMA" - }, - { - "from_node": "west edfa in Quimper to Lorient_KMA", "to_node": "fiber (Quimper → Brest_KLA)-" }, { From 16134b5cafeb5a180d529f420e8355dbc13ba4b7 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Mon, 3 Jun 2019 16:27:58 +0200 Subject: [PATCH 035/117] +1 and -1 replaced with coporop and counterprop strings for Raman pumps description --- examples/raman_edfa_example_network.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/raman_edfa_example_network.json b/examples/raman_edfa_example_network.json index 8f05fc015..489aa8875 100644 --- a/examples/raman_edfa_example_network.json +++ b/examples/raman_edfa_example_network.json @@ -21,12 +21,12 @@ { "power": 200e-3, "frequency": 200e12, - "propagation_direction": -1 + "propagation_direction": "counterprop" }, { "power": 150e-3, "frequency": 201e12, - "propagation_direction": -1 + "propagation_direction": "counterprop" } ] }, From fd406c106bb608284f440d692ceea645246a10e6 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Mon, 3 Jun 2019 16:33:26 +0200 Subject: [PATCH 036/117] gn model introduced in the science utils module --- gnpy/core/science_utils.py | 116 ++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 54c6c21f3..a100ac13b 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -1,3 +1,8 @@ +import progressbar +import numpy as np +from operator import attrgetter +from scipy.interpolate import interp1d + from gnpy.core.utils import load_json import scipy.constants as ph from scipy.integrate import solve_bvp @@ -339,4 +344,113 @@ def _ode_stimulated_raman(self, z, power_spectrum, alphap_fiber, freq_array, cr_ dpdz_element = prop_direct[f_ind] * (-alphap_fiber[f_ind] + raman_gain - raman_loss) * power_sample dpdz[f_ind][z_ind] = dpdz_element - return np.vstack(dpdz) \ No newline at end of file + return np.vstack(dpdz) + +class NLI: + """ This class implements the NLI models. + Model and method can be specified in `self.model_parameters.method`. + List of implemented methods: + 'gn_analytic': brute force triple integral solution + 'GGN_spectrally_separated_xpm_spm': XPM plus SPM + """ + + def __init__(self, fiber_information=None): + """ Initialize the fiber object with its physical parameters + """ + self.fiber_information = fiber_information + self.srs_profile = None + self.model_parameters = None + + @property + def fiber_information(self): + return self.__fiber_information + + @fiber_information.setter + def fiber_information(self, fiber_information): + self.__fiber_information = fiber_information + + @property + def srs_profile(self): + return self.__srs_profile + + @srs_profile.setter + def srs_profile(self, srs_profile): + self.__srs_profile = srs_profile + + @property + def model_parameters(self): + return self.__model_parameters + + @model_parameters.setter + def model_parameters(self, model_params): + """ + :param model_params: namedtuple containing the parameters used to compute the NLI. + """ + self.__model_parameters = model_params + + def alpha0(self, f_eval=193.5e12): + if len(self.fiber_information.attenuation_coefficient.alpha_power) == 1: + alpha0 = self.fiber_information.attenuation_coefficient.alpha_power[0] + else: + alpha_interp = interp1d(self.fiber_information.attenuation_coefficient.frequency, + self.fiber_information.attenuation_coefficient.alpha_power) + alpha0 = alpha_interp(f_eval) + return alpha0 + + def compute_nli(self, carrier, *carriers): + """ Compute NLI power generated by the WDM comb `*carriers` on the channel under test `carrier` + at the end of the fiber span. + """ + if 'gn_model_analytic' == self.model_parameters.method.lower(): + carrier_nli = self._gn_analytic(carrier, *carriers) + else: + raise ValueError(f'Method {self.model_parameters.method_nli} not implemented.') + + return carrier_nli + + # Methods for computing spectrally separated GN + def _gn_analytic(self, carrier, *carriers): + """ Computes the nonlinear interference power on a single carrier. + The method uses eq. 120 from arXiv:1209.0394. + :param carrier: the signal under analysis + :param carriers: the full WDM comb + :return: carrier_nli: the amount of nonlinear interference in W on the under analysis + """ + + alpha = self.alpha0() / 2 + length = self.fiber_information.length + effective_length = (1 - np.exp(-2 * alpha * length)) / (2 * alpha) + asymptotic_length = 1 / (2 * alpha) + + beta2 = self.fiber_information.beta2 + gamma = self.fiber_information.gamma + + g_nli = 0 + for interfering_carrier in carriers: + g_interfearing = interfering_carrier.power.signal / interfering_carrier.baud_rate + g_signal = carrier.power.signal / carrier.baud_rate + g_nli += g_interfearing**2 * g_signal * self._psi(carrier, interfering_carrier) + g_nli *= (16.0 / 27.0) * (gamma * effective_length)**2 /\ + (2 * np.pi * abs(beta2) * asymptotic_length) + + carrier_nli = carrier.baud_rate * g_nli + return carrier_nli + + def _psi(self, carrier, interfering_carrier): + """ Calculates eq. 123 from arXiv:1209.0394. + """ + alpha = self.alpha0() / 2 + beta2 = self.fiber_information.beta2 + + asymptotic_length = 1 / (2 * alpha) + if carrier.channel_number == interfering_carrier.channel_number: # SPM + psi = np.arcsinh(0.5 * np.pi**2 * asymptotic_length + * abs(beta2) * carrier.baud_rate**2) + else: # XPM + delta_f = carrier.frequency - interfering_carrier.frequency + psi = np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * + carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate)) + psi -= np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * + carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) + + return psi From 22acd88d44dafaae33502cf430acdbceca8202d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Fri, 31 May 2019 15:51:20 +0200 Subject: [PATCH 037/117] Utility functions for pruning and merging Co-authored-by: Esther Le Rouzic --- gnpy/core/utils.py | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/gnpy/core/utils.py b/gnpy/core/utils.py index 5d54e3db2..1a1951712 100644 --- a/gnpy/core/utils.py +++ b/gnpy/core/utils.py @@ -11,8 +11,8 @@ import json -import numpy as np from csv import writer +import numpy as np from numpy import pi, cos, sqrt, log10 from scipy import constants @@ -199,3 +199,43 @@ def rrc(ffs, baud_rate, alpha): p_inds = np.where(np.logical_and(np.abs(ffs) > 0, np.abs(ffs) < l_lim)) hf[p_inds] = 1 return sqrt(hf) + +def merge_amplifier_restrictions(dict1, dict2): + """Updates contents of dicts recursively + + >>> d1 = {'params': {'restrictions': {'preamp_variety_list': [], 'booster_variety_list': []}}} + >>> d2 = {'params': {'target_pch_out_db': -20}} + >>> merge_amplifier_restrictions(d1, d2) + {'params': {'restrictions': {'preamp_variety_list': [], 'booster_variety_list': []}, 'target_pch_out_db': -20}} + + >>> d3 = {'params': {'restrictions': {'preamp_variety_list': ['foo'], 'booster_variety_list': ['bar']}}} + >>> merge_amplifier_restrictions(d1, d3) + {'params': {'restrictions': {'preamp_variety_list': [], 'booster_variety_list': []}}} + """ + + copy_dict1 = dict1.copy() + for key in dict2: + if key in dict1: + if isinstance(dict1[key], dict): + copy_dict1[key] = merge_amplifier_restrictions(copy_dict1[key], dict2[key]) + else: + copy_dict1[key] = dict2[key] + return copy_dict1 + +def silent_remove(this_list, elem): + """Remove matching elements from a list without raising ValueError + + >>> li = [0, 1] + >>> li = silent_remove(li, 1) + >>> li + [0] + >>> li = silent_remove(li, 1) + >>> li + [0] + """ + + try: + this_list.remove(elem) + except ValueError: + pass + return this_list From d94dc51d88abb56a1bc9af5a1f3b01f1dce2e274 Mon Sep 17 00:00:00 2001 From: Esther Le Rouzic Date: Fri, 31 May 2019 15:54:07 +0200 Subject: [PATCH 038/117] Restrictions on auto-adding amplifiers into ROADMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-authored-by: Jan Kundrát --- Excel_userguide.rst | 8 +- README.rst | 13 +- examples/eqpt_config.json | 4 +- examples/meshTopologyExampleV2.json | 34 +- examples/meshTopologyExampleV2.xls | Bin 15872 -> 15872 bytes gnpy/core/convert.py | 63 +- gnpy/core/elements.py | 13 +- gnpy/core/equipment.py | 85 ++- gnpy/core/network.py | 73 ++- tests/LinkforTest.json | 38 ++ ..._Global_Topology_auto_design_expected.json | 600 +++++++++++++++--- tests/data/eqpt_config.json | 15 +- tests/data/testTopology.xls | Bin 16896 -> 16896 bytes .../testTopology_auto_design_expected.json | 174 +++-- tests/data/testTopology_expected.json | 41 +- tests/data/test_network.json | 21 +- tests/test_propagation.py | 2 + tests/test_roadm_restrictions.py | 203 ++++++ 18 files changed, 1128 insertions(+), 259 deletions(-) create mode 100644 tests/test_roadm_restrictions.py diff --git a/Excel_userguide.rst b/Excel_userguide.rst index ce146b24a..79ca34989 100644 --- a/Excel_userguide.rst +++ b/Excel_userguide.rst @@ -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 @@ -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** @@ -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 `_ 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. diff --git a/README.rst b/README.rst index a93f19bec..31bb8c4ed 100644 --- a/README.rst +++ b/README.rst @@ -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 diff --git a/examples/eqpt_config.json b/examples/eqpt_config.json index 6dbf8d2cb..070509069 100644 --- a/examples/eqpt_config.json +++ b/examples/eqpt_config.json @@ -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":[{ diff --git a/examples/meshTopologyExampleV2.json b/examples/meshTopologyExampleV2.json index 89a7d62b6..fe302ae04 100644 --- a/examples/meshTopologyExampleV2.json +++ b/examples/meshTopologyExampleV2.json @@ -681,25 +681,6 @@ "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": { @@ -1041,6 +1022,21 @@ "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": "Fused", + "params": { + "loss": 0 + } } ], "connections": [ diff --git a/examples/meshTopologyExampleV2.xls b/examples/meshTopologyExampleV2.xls index f73a7738a574b53c17fd3b85c9f661f280c3f271..fa9fe5fc341d6439b448a977d2ee9b77fd1443d3 100644 GIT binary patch delta 4315 zcmZ`+O>9(E6h3c$-n{vF^ID*V=}>3r^uJIjAf-?dDhMj_n_$9*Kp9LCT4;-C1O`or zu3UIGx*@VLL=!aBg$rYdF|%>)hM2fD(Zp(^iKvO+dH3%geXJjSO)ZE;{VtxM9e0^bYer9@cW^Q%`(UJN3>5G@5jZ6EBpC`RE z(YY%N^|MQVwSVb2$Co}^nQ$7@?Exhand=CjgHO}9IDwvK-r^*JAMJO^rRjq0&k#9H zPhv_GDG9r6_!4FD^%8Ap$&g2rPT1%KI_|# zC;`M1NR$L(I})X6HC)vw4dfc+0>M!=G6Upkv6v z!p8~wlIZKAiukU(=2XSc-L=L##Y$mBC-oSVpl+%elmzNEC=XqcJB1Hqrzis1DR|*}idOo-`*RM&}N^xUdInYYb7AirfxV!F7k+%-13g7riJnXuatWj0`)YI91 zXm0-E=?gvmW1IW;bl|x5ajPp^MQ`n`Mi6;RvmRWJU{2^efz^v7l3KsTxhjCJP_xbU zr}1u!_GfVXrXza7{zdzbw9-Y=` z0mvHlBSxPj6})sQ6_c2rk5C%H3{16>MkoWCwF5>g9iiUhR#td7;H-l zhW0KSW3znl!sWWNG0)lB;}wRk7^Hk?9)FEB@$m&B^QL_4&6w5;#g~IU7Vh6I5b*KZML;^bWPh;6Dg^l_W zvFuwMULuMXv%Cat7PGwED_ERH4y?^t%$E#`V3wPAB9{~#v?&TA`USnA^Q)|0@mnOS zM|o87ixC5OFE~wcV*Q_uU25l(uu!6B4ax!SR){YdeYD3Q2WZNm1kiH^C4u%DltPWM z&!9AFjOPtAo`N&CLG9Gi1TQ*-GESq1Em~H8ev188tt!I*jE#Yao`N3D^ kW=718Kjr;C<-NPM>jy`?om#*2V0?QradpGeqaAMYe^9*pumAu6 delta 3998 zcmZ`+ON>-S6uqxse_wyz&^>&bX1+Z%bT^-wab)HJ4h}k~h(ki+CoD`PFvh5;sF*0y z;{xM?5M!-$VT5ShsiBRD8-qKC1fz*@WsoJhFh)Y6pu~G#{od1t&XAm{I``hHy7#R6Q%9fXfdU<0hk+!`zwrD^r^u%-$Hd{{ zySa~3zwq#WB5|oS*A4tn-R|$@&mCuf_oM!V(^*{MQ-T+~&K4a0Ef+0L@XT`FVwe3# z=hKv?#e$vB5IM_Pi78RyScv-gkBDa|o9GKiGPKhP2OXcXV*9F}J6N|e9QubmUgOXz z{v(no!3YsbXqX zAg{(?#^(c)L8N2!bc~%=?8!Ct05=AA*t}0AgQCteF?L3=&qUY=)90VGm5uq%#@JcK z{*~D}a_q_vF9DI{V(eUuom1>RRm=85_6p_Oi?O{J+f(dw%r2|-h{30dLD7BZW9+I;9}t%=2wEiaU3v|JM? z$4Gf{mO9Jb*@0J__8R4!LU&b#oDi*52-WRVO3=h>igKA#Z;gCTI z8lYi=Tt*`XB^iwxlwwpjD9vchpbVq+24xvF49d|U4R;Lm7+M#3M%G1vk#$jIWL=aP zSrVo>A|>Gu@z_YQ-8L)0 z*2v>gpW>2G5pR#wI-AsbFN|3uDFWJCK1^*CHM~g?IH!2|Fy_q$!K+5S!~kCmSdn6I zMzltY4{uTILKuTay~F^;4Ym}6E&RE`Y4j?5Uz{GT1@Us$r!LF+qVkGSTljt9UMu3t z=-GUklM7{Crn0!ye#;%B5ivWqs(j$clP^E>#Ie!F#AIV`B{ivm;fxv<2ghFPR3aZ( zD8Nz#ql7>7twOl0C;9`74|AoMic~T6P?ddu1wb=2Ke?_Viwo$ za0a%jO-qRxSIFgvhG{~4)fjRn#pT9OOitGDmCA!o^13?RVUWvc%Ah2pI}J)P+GbFi z(X>G*kQsxrtOC|%j?vU1GwCt3M)QoU(E=lDv=}jZJIRn50m%r&cQ=u(O z9SOwA7Ea;4RxQwtk8B_GnaK7*`z*!wQBtSy8?&Kq@FLp>-3Cd$(QGsecBg2MaE80v zOQmwy6^D95$)xzMeY0~dTMb1>H~QC#7Ke>~$zte8rO#sM$oYcBDgHbTc^2a#;6^Z# z^v%daf^}s#RU$&g!a~pXTVe8er%~a_^L-zW7~orUmc{AK*E`$QxwvehN;?el80}OD z?}_zvmq8ArS%VUcb{XU{y4#>6w+eFxrTF{5+n_X~du(@-VR)~Z%rcrcD931zK^~*~ z49YXQpAn=0;?fLn1y_w{NsF&gUOf7dmg(G6d?QVIfJA5OLR;qsdJVe-PY*27x{~C% zz2eWUo1F(mW4zhcbp<_XnYJ8HjwbC>tPEc?Sg(%H59s`bR({L!{v z0R^xt@Q|<;bD_nD#N!k5CIToy)oc_`hpLGi?>|KFZDc)a->%1F!LHA%^|O=H!Gekr zRbDj_qslECj|I!dCz8E&Kbbt>bU&Ec=A558Ry baud_rate), default=baud_rate*1.2) def automatic_nch(f_min, f_max, spacing): @@ -333,18 +337,28 @@ 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: + if amp_name not in equipment['Edfa']: + 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 @@ -359,11 +373,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 diff --git a/gnpy/core/network.py b/gnpy/core/network.py index 4f0684b30..8ea813252 100644 --- a/gnpy/core/network.py +++ b/gnpy/core/network.py @@ -20,7 +20,8 @@ from gnpy.core.equipment import edfa_nf from gnpy.core.exceptions import ConfigurationError, NetworkTopologyError from gnpy.core.units import UNITS -from gnpy.core.utils import load_json, save_json, round2float, db2lin, lin2db +from gnpy.core.utils import (load_json, save_json, round2float, db2lin, + merge_amplifier_restrictions) from collections import namedtuple logger = getLogger(__name__) @@ -50,10 +51,12 @@ 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.__dict__) - elif typ in ['Edfa', 'Fiber']: #catch it now because the code will crash later! + temp = el_config.setdefault('params', {}) + temp = merge_amplifier_restrictions(temp, extra_params.__dict__) + el_config['params'] = temp + elif typ in ['Edfa', 'Fiber']: # catch it now because the code will crash later! raise ConfigurationError(f'The {typ} of variety type {variety} was not recognized:' '\nplease check it is properly defined in the eqpt_config json file') cls = getattr(elements, typ) @@ -65,10 +68,8 @@ def network_from_json(json_data, equipment): for cx in json_data['connections']: from_node, to_node = cx['from_node'], cx['to_node'] try: - if isinstance(nodes[from_node], Fiber): + if isinstance(nodes[from_node], Fiber): edge_length = nodes[from_node].params.length - # print(from_node) - # print(edge_length) else: edge_length = 0.01 g.add_edge(nodes[from_node], nodes[to_node], weight = edge_length) @@ -90,21 +91,27 @@ def network_to_json(network): data.update(connections) return data -def select_edfa(raman_allowed, gain_target, power_target, equipment, uid): +def select_edfa(raman_allowed, gain_target, power_target, equipment, uid, restrictions=None): """amplifer selection algorithm @Orange Jean-Luc Augé """ Edfa_list = namedtuple('Edfa_list', 'variety power gain_min nf') TARGET_EXTENDED_GAIN = equipment['Span']['default'].target_extended_gain - edfa_dict = equipment['Edfa'] + + # for roadm restriction only: create a dict including not allowed for design amps + # because main use case is to have specific radm amp which are not allowed for ILA + # with the auto design + edfa_dict = {name: amp for (name, amp) in equipment['Edfa'].items() + if restrictions is None or name in restrictions} + pin = power_target - gain_target - #create 2 list of available amplifiers with relevant attributs for their selection + # create 2 list of available amplifiers with relevant attributes for their selection - #edfa list with : - #extended gain min allowance of 3dB: could be parametrized, but a bit complex - #extended gain max allowance TARGET_EXTENDED_GAIN is coming from eqpt_config.json - #power attribut include power AND gain limitations + # edfa list with: + # extended gain min allowance of 3dB: could be parametrized, but a bit complex + # extended gain max allowance TARGET_EXTENDED_GAIN is coming from eqpt_config.json + # power attribut include power AND gain limitations edfa_list = [Edfa_list( variety=edfa_variety, power=min( @@ -119,7 +126,7 @@ def select_edfa(raman_allowed, gain_target, power_target, equipment, uid): -edfa.gain_min, nf=edfa_nf(gain_target, edfa_variety, equipment)) \ for edfa_variety, edfa in edfa_dict.items() - if (edfa.allowed_for_design and not edfa.raman)] + if ((edfa.allowed_for_design or restrictions is not None) and not edfa.raman)] #consider a Raman list because of different gain_min requirement: #do not allow extended gain min for Raman @@ -326,7 +333,7 @@ def set_egress_amplifier(network, roadm, equipment, pref_total_db): 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 raman_allowed = False @@ -335,9 +342,24 @@ def set_egress_amplifier(network, roadm, equipment, pref_total_db): equipment['Span']['default'].max_fiber_lineic_loss_for_raman raman_allowed = prev_node.params.loss_coef < max_fiber_lineic_loss_for_raman - if node.params.type_variety == '' : + # implementation of restrictions on roadm boosters + if isinstance(prev_node,Roadm): + if prev_node.restrictions['booster_variety_list']: + restrictions = prev_node.restrictions['booster_variety_list'] + else: + restrictions = None + elif isinstance(next_node,Roadm): + # implementation of restrictions on roadm preamp + if next_node.restrictions['preamp_variety_list']: + restrictions = next_node.restrictions['preamp_variety_list'] + else: + restrictions = None + else: + restrictions = None + + if node.params.type_variety == '': edfa_variety, power_reduction = select_edfa(raman_allowed, - gain_target, power_target, equipment, node.uid) + gain_target, power_target, equipment, node.uid, restrictions) extra_params = equipment['Edfa'][edfa_variety] node.params.update_params(extra_params.__dict__) dp += power_reduction @@ -351,7 +373,7 @@ def set_egress_amplifier(network, roadm, equipment, pref_total_db): ) node.delta_p = dp if power_mode else None - node.effective_gain = gain_target + node.effective_gain = gain_target set_amplifier_voa(node, power_target, power_mode) if isinstance(next_node, Roadm) or isinstance(next_node, Transceiver): break @@ -484,10 +506,13 @@ def add_fiber_padding(network, fibers, padding): #add a padding att_in at the input of the 1st fiber: #address the case when several fibers are spliced together first_fiber = find_first_node(network, fiber) - if first_fiber.att_in is None: - first_fiber.att_in = padding - this_span_loss - else : - first_fiber.att_in = first_fiber.att_in + padding - this_span_loss + # in order to support no booster , fused might be placed + # just after a roadm: need to check that first_fiber is really a fiber + if isinstance(first_fiber,Fiber): + if first_fiber.att_in is None: + first_fiber.att_in = padding - this_span_loss + else: + 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['Span']['default'] @@ -510,6 +535,7 @@ def build_network(network, equipment, pref_ch_db, pref_total_db): amplified_nodes = [n for n in network.nodes() if isinstance(n, Fiber) or isinstance(n, Roadm)] + for node in amplified_nodes: add_egress_amplifier(network, node) @@ -522,4 +548,3 @@ def build_network(network, equipment, pref_ch_db, pref_total_db): trx = [t for t in network.nodes() if isinstance(t, Transceiver)] for t in trx: set_egress_amplifier(network, t, equipment, pref_total_db) - diff --git a/tests/LinkforTest.json b/tests/LinkforTest.json index 8edb226bc..1b63c8f68 100644 --- a/tests/LinkforTest.json +++ b/tests/LinkforTest.json @@ -205,6 +205,36 @@ "longitude": 0 } } + }, + { + "uid": "Att_B", + "type": "Fused", + "params":{ + "loss":16 + }, + "metadata": { + "location": { + "latitude": 2.0, + "longitude": 1.0, + "city": "Corlay", + "region": "RLD" + } + } + }, + { + "uid": "Att_F", + "type": "Fused", + "params":{ + "loss":16 + }, + "metadata": { + "location": { + "latitude": 2.0, + "longitude": 1.0, + "city": "Corlay", + "region": "RLD" + } + } } ], @@ -247,6 +277,10 @@ }, { "from_node": "Edfa5", + "to_node": "Att_F" + }, + { + "from_node": "Att_F", "to_node": "trx F" }, { @@ -255,6 +289,10 @@ }, { "from_node": "Edfa1", + "to_node": "Att_B" + }, + { + "from_node": "Att_B", "to_node": "trx B" } ] diff --git a/tests/data/CORONET_Global_Topology_auto_design_expected.json b/tests/data/CORONET_Global_Topology_auto_design_expected.json index 73878c56a..7006b6af5 100644 --- a/tests/data/CORONET_Global_Topology_auto_design_expected.json +++ b/tests/data/CORONET_Global_Topology_auto_design_expected.json @@ -1204,7 +1204,11 @@ "uid": "roadm Abilene", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1219,7 +1223,11 @@ "uid": "roadm Albany", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1234,7 +1242,11 @@ "uid": "roadm Albuquerque", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1249,7 +1261,11 @@ "uid": "roadm Atlanta", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1264,7 +1280,11 @@ "uid": "roadm Austin", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1279,7 +1299,11 @@ "uid": "roadm Baltimore", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1294,7 +1318,11 @@ "uid": "roadm Baton_Rouge", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1309,7 +1337,11 @@ "uid": "roadm Billings", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1324,7 +1356,11 @@ "uid": "roadm Birmingham", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1339,7 +1375,11 @@ "uid": "roadm Bismarck", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1354,7 +1394,11 @@ "uid": "roadm Boston", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1369,7 +1413,11 @@ "uid": "roadm Buffalo", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1384,7 +1432,11 @@ "uid": "roadm Charleston", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1399,7 +1451,11 @@ "uid": "roadm Charlotte", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1414,7 +1470,11 @@ "uid": "roadm Chicago", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1429,7 +1489,11 @@ "uid": "roadm Cincinnati", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1444,7 +1508,11 @@ "uid": "roadm Cleveland", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1459,7 +1527,11 @@ "uid": "roadm Columbus", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1474,7 +1546,11 @@ "uid": "roadm Dallas", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1489,7 +1565,11 @@ "uid": "roadm Denver", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1504,7 +1584,11 @@ "uid": "roadm Detroit", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1519,7 +1603,11 @@ "uid": "roadm El_Paso", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1534,7 +1622,11 @@ "uid": "roadm Fresno", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1549,7 +1641,11 @@ "uid": "roadm Greensboro", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1564,7 +1660,11 @@ "uid": "roadm Hartford", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1579,7 +1679,11 @@ "uid": "roadm Houston", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1594,7 +1698,11 @@ "uid": "roadm Jacksonville", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1609,7 +1717,11 @@ "uid": "roadm Kansas_City", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1624,7 +1736,11 @@ "uid": "roadm Las_Vegas", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1639,7 +1755,11 @@ "uid": "roadm Little_Rock", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1654,7 +1774,11 @@ "uid": "roadm Long_Island", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1669,7 +1793,11 @@ "uid": "roadm Los_Angeles", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1684,7 +1812,11 @@ "uid": "roadm Louisville", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1699,7 +1831,11 @@ "uid": "roadm Memphis", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1714,7 +1850,11 @@ "uid": "roadm Miami", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1729,7 +1869,11 @@ "uid": "roadm Milwaukee", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1744,7 +1888,11 @@ "uid": "roadm Minneapolis", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1759,7 +1907,11 @@ "uid": "roadm Nashville", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1774,7 +1926,11 @@ "uid": "roadm New_Orleans", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1789,7 +1945,11 @@ "uid": "roadm New_York", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1804,7 +1964,11 @@ "uid": "roadm Newark", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1819,7 +1983,11 @@ "uid": "roadm Norfolk", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1834,7 +2002,11 @@ "uid": "roadm Oakland", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1849,7 +2021,11 @@ "uid": "roadm Oklahoma_City", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1864,7 +2040,11 @@ "uid": "roadm Omaha", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1879,7 +2059,11 @@ "uid": "roadm Orlando", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1894,7 +2078,11 @@ "uid": "roadm Philadelphia", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1909,7 +2097,11 @@ "uid": "roadm Phoenix", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1924,7 +2116,11 @@ "uid": "roadm Pittsburgh", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1939,7 +2135,11 @@ "uid": "roadm Portland", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1954,7 +2154,11 @@ "uid": "roadm Providence", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1969,7 +2173,11 @@ "uid": "roadm Raleigh", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1984,7 +2192,11 @@ "uid": "roadm Richmond", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -1999,7 +2211,11 @@ "uid": "roadm Rochester", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2014,7 +2230,11 @@ "uid": "roadm Sacramento", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2029,7 +2249,11 @@ "uid": "roadm Salt_Lake_City", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2044,7 +2268,11 @@ "uid": "roadm San_Antonio", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2059,7 +2287,11 @@ "uid": "roadm San_Diego", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2074,7 +2306,11 @@ "uid": "roadm San_Francisco", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2089,7 +2325,11 @@ "uid": "roadm San_Jose", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2104,7 +2344,11 @@ "uid": "roadm Santa_Barbara", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2119,7 +2363,11 @@ "uid": "roadm Scranton", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2134,7 +2382,11 @@ "uid": "roadm Seattle", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2149,7 +2401,11 @@ "uid": "roadm Spokane", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2164,7 +2420,11 @@ "uid": "roadm Springfield", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2179,7 +2439,11 @@ "uid": "roadm St_Louis", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2194,7 +2458,11 @@ "uid": "roadm Syracuse", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2209,7 +2477,11 @@ "uid": "roadm Tallahassee", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2224,7 +2496,11 @@ "uid": "roadm Tampa", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2239,7 +2515,11 @@ "uid": "roadm Toledo", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2254,7 +2534,11 @@ "uid": "roadm Tucson", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2269,7 +2553,11 @@ "uid": "roadm Tulsa", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2284,7 +2572,11 @@ "uid": "roadm Washington_DC", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2299,7 +2591,11 @@ "uid": "roadm West_Palm_Beach", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2314,7 +2610,11 @@ "uid": "roadm Wilmington", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2329,7 +2629,11 @@ "uid": "roadm Amsterdam", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2344,7 +2648,11 @@ "uid": "roadm Berlin", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2359,7 +2667,11 @@ "uid": "roadm Brussels", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2374,7 +2686,11 @@ "uid": "roadm Bucharest", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2389,7 +2705,11 @@ "uid": "roadm Frankfurt", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2404,7 +2724,11 @@ "uid": "roadm Istanbul", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2419,7 +2743,11 @@ "uid": "roadm London", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2434,7 +2762,11 @@ "uid": "roadm Madrid", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2449,7 +2781,11 @@ "uid": "roadm Paris", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2464,7 +2800,11 @@ "uid": "roadm Rome", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2479,7 +2819,11 @@ "uid": "roadm Vienna", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2494,7 +2838,11 @@ "uid": "roadm Warsaw", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2509,7 +2857,11 @@ "uid": "roadm Zurich", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2524,7 +2876,11 @@ "uid": "roadm Bangkok", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2539,7 +2895,11 @@ "uid": "roadm Beijing", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2554,7 +2914,11 @@ "uid": "roadm Delhi", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2569,7 +2933,11 @@ "uid": "roadm Hong_Kong", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2584,7 +2952,11 @@ "uid": "roadm Honolulu", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2599,7 +2971,11 @@ "uid": "roadm Mumbai", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2614,7 +2990,11 @@ "uid": "roadm Seoul", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2629,7 +3009,11 @@ "uid": "roadm Shanghai", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2644,7 +3028,11 @@ "uid": "roadm Singapore", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2659,7 +3047,11 @@ "uid": "roadm Sydney", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2674,7 +3066,11 @@ "uid": "roadm Taipei", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -2689,7 +3085,11 @@ "uid": "roadm Tokyo", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { diff --git a/tests/data/eqpt_config.json b/tests/data/eqpt_config.json index 0163158c1..47b5cfbea 100644 --- a/tests/data/eqpt_config.json +++ b/tests/data/eqpt_config.json @@ -49,7 +49,16 @@ "p_max": 21, "nf0": 5, "allowed_for_design": true - } + }, + { + "type_variety": "std_booster", + "type_def": "fixed_gain", + "gain_flatmax": 21, + "gain_min": 20, + "p_max": 21, + "nf0": 5, + "allowed_for_design": false + } ], "Fiber":[{ "type_variety": "SSMF", @@ -75,8 +84,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":[{ diff --git a/tests/data/testTopology.xls b/tests/data/testTopology.xls index a7885c6b3aa0c687fc93968120177afb4440925e..0005997b5919ca64c7c1f53680fff17b8d4b8d22 100644 GIT binary patch delta 4556 zcmZ`+OKg-?6h8mF-?!5j?M$aL)4$WwPI(k4w1T`5vAo0wLSo3!R#K!C+MA})+EArKaXpq_J||8K@N&GftHeCM8f?>XPx zd+CaE>56l6ZF>FBX~3T_9Gst?S-c8qsI8C#`)AL-jsq!3m1!UZd(NL*x(W<#@u~DD z(a*vq2jDmlWp-8D0CvvLFD}h2oLHDyTw0i&TAH0d+X`@aVWu{B&O6BWW*X%rz~a*M ziIb9pVNRyx$eH;!Pn@dFo^AY{xgN+dIXE{nJ$qqJjE4bUyRbMj-S|5Dh2tD=oNfy^ z^;(ue05iOZxjOi}oVGZCmgS_yL0qZV9)SqdQq~>?aF$~dLm-2z2I!*YLmdBNffg|m zhb@>i{x}R`{Ll8g9Wn0+dS%fV=XX2)I1qv;?it2~Cy+pP3{gxWTn-|NOtvuQ&hS&C z03zHeWeXxo@h6?`1RWUpNAQzD#vm^{g6p>I2uSv^748IXs$f$_3|b8GvZFqBRIzV) z*t7yBR!Nz}e3{35?3iL_yKLsPVg^_rB9pj}9rv;0iaqCH(+)AfniK4VkDc(b6N>!< zvfGtE?I?o|gS_ib`q)Xuu5{bXu}+cQsn}F+_QX;?c1p1?BU|q|og4$|FWR-VkDd0h z(~A9@XKp%aCaRIx86P|2V`mh5B4^i4IWXu}b2A^C>fKfgQ|ym&F4Mb4b;2OWzt6b^ zU4Jd2vhyk;RXl?p{>bBz71dr)9(3LedU-YPG980XkwKBa>KUUs8I<@n&lsH-gEGJE z8KYBR(8nKm#%OODxV+NiI%&SIXCXuVhmDGzhyO)68+|-l%h-k|!SD)5fCR!L7}#Xa za^qp48mRVT9NKvhyH!!h5okam(i;YYe6o~xR`OFN*BRpPm)!cWvWIZGPN*6bfC7vd z6hySjpb(W+FzV`gVXMjkeA0WQ8N{zY9qQ-u^$yLg4I-n4F*vZjhd+$ zRU}1`YIdJ*>PuJ06q_`;2pTn0vsOOem#=SB{-oMP&`9c+)@`X@RLL}()UW%B7Z*8i zGKivR)J(^y&MAu2u?Ua5>FT&*Cqxb!NgdNUBt<`W@*QrzKB4@R!e66i%0=Bmvq|Op zK=D!{=cf#!oHe4p{jY-2_ajA-3bva6>ZT`X%&L?=38_*!{O%-MGev6U<^DX=D%B!Z z)xjtG^AkB0F)PM;)SOvSU~eg#R2c2qDm;7nr~Ub!P{Uk*={QhfLDJ8ZLBN)U4?!4`gJxad61 z9}X9N^4R5*M;Ep#RtPh8VVglAMB5DtBidn51kp}|qR{V&5<_I8#1U^*iz;6x1>}u?HLuG`o1BR ztiX?sj816(rrM<Hd@|LVZJ-NEM-OB_tI?+vHtGcc#<w2Y>gVh&i%1G7@b`%nPHQkdm^t9$_mfPufzv zWEn)4Tg{_SYwHy}y+E3YY*-YOX;BzmKELK_y`+Mb#F$QrrobDXvXzCcPa*omTb)6a zL`fmiLThv7JjzD5Graj3M7*-jkS=T1SdkO*Yik#hbkQa$O+%WWA9$A^EWEMfUW_)| zJ}B>{Efx>TjK!p**%pgQM{n}Ub;a60aXU)tdByWACH2gD)ELzhPPbe2r3-*pU6a_F zOCCP9rnAg{TK835OIHvhbi-b?{Gce9XBDDHYz6iiFJg|2#1Yp4AJuj#StAbD1qn&gOZ3|!Bqk1F#wEpzgwf+(1A0Jz__bB?4GLym%nRhcO>@aSgj717N zY+i@-@*RR#j7`pcact7)8ohNbo8G$CKPKir#vhCwGX8WswEpzEwf=GG-|Nb67v$+&YjI8|laqf=8FNa1N-)~+9sQ>@~ delta 4450 zcmZ`+OKg-?6h8mF-?tB1+WFf~%SY-If}kiY3<*L3L#zc6G$HDs0bQ98 zZsGzHqS2iT4U?GYf)L}PVTB7_5)(H*qKn22F(#hxKL0-{oki!n=X~d!`}*eG%U6QS zSAwtCm#43liT~1c;Oxm$Z(OA^{no1o_McgJl?O^BhcS?%-LId!aFqm2%DM85?CEqE z5FL@J$}V}n(oQir(cU9INsN?;-nek`*!GPk=VR=AjGZ^^yM6VbD7UN&2krV4VtxhVx4OT+xy!^wHbLDEMQ2!y zv5SWNKC^9&SXV(S3>&A%?@cMjE*bWHX4}=pe?dKlU5>HKF?QLow^V&@?1~`V6zo(b z#;(NJ6~q3R*>>L86+tTvTg2Enz5ZMX!(OV^g=J$`1og>ASQmCJaAF1Z%af5Y_zSAa zFCt@DYeAv>IWmS76f_`n13n(sMbMz!5*fp61r5ph$Qb4zs3va?VE4ZtIA6is_-gSe zzR8;#YqB(UT859lIW@<|=v9wi{RFOd4f)G(Js6d#k$P*jVWrrkm&P1Q&>*dGD9LE8Ln%h%4y742 z9m+78a45@YokKZBlMdx+h}N_mEHLybii~`U5+k3Y%*dyxF!CvQQ(TJW1Pd4C-2~(d zi*$%K>h(;slGW%DQ%sso9xbO_6?t@3*jjJ=Gs@qh<+KX>kF;8=mJBbevQ9aKtQIY& zSvc*8qBU!LlOg6*m`5E#7>l%cp(Gp|Enb~+vL1?QHwOP{$kkq42_(>RmEoTTu#m7u zj~V}hnyf{*bpA)7s1ExI`CUD1ZZQ5uqY=vDWTj~*+o6uaeUo3S2Yx9r^-eXRe))GJoU>zHYOHFi=u+%U z#$Rg+Y+!dQ&(L^wE6=;erj^Eeu27zXa(*-%>Ng%edd#qU6uTP8A1A9pDDRAh^1);! z7?OXChMNZsrB6|6hSH}f*UbVUEOuI#20?&;ZD0LarB{gru0MZELZ;fKv9X}l9cd@f zjJ&D@0d==&*8iz9+wdX0{MN5$Yta&VgKdkvH z2omzv_(y_ou3BVB+AwXB2b#5Dvs`G_Vv^Pule9W*F{~8VTc@oKr5J5PNfSkKw1tu?5}`sTAnMEi1S2g4SfoD(v`tHa_0$cC zje-(=o2A>0m2_MaQKRpJI%2D(b;0&XS{LjLJJm&-Wj0FFy@s`EwGi^<$*^T7rgg!t zh}H$m*1BM|M(cuC1k`73S|6-}ARl)@%~3~SSx z0Xf4oG3t5MSWNhEYh^q`WvdeB2a$IvVjNILjKPMYxYu$BXT_!;irIz~&(60Nv(=Oh zV|eEb^Zx|iIWn{UV#{V4Rz9}G5yiK*5oJR@)P%kYZG}as$$Fy8^0vCm3tmInP=!X| z4O-%8%s8qO%HFB-6&xQlja}*>MVrRfQm&t8;@^i$WslYSu;MWUxx2$-2=c9x$7xoU z;ey9_KT-rkpg)MbKUtu6Qg=l0S)plug;#ipKvcnteDNPqqN(hfYBa@5{&jblw|ajLn&HKPdk)m{eQ-x45NKMOqSt(XUs8r z)}cHjtGX#Jc8m@>TanRo^4?=%(<&Y`d)&Pd(>%4c^t|zhf{)W7*}LIj)9MFQ+WX_h z>-{sv|JsIi!3*;ChOp`N8!RhqcF(f7B2d5q956k(j!?`t=-ab0r^;00< TOHH)zY~7R$X4((7XOsT}?GyQY diff --git a/tests/data/testTopology_auto_design_expected.json b/tests/data/testTopology_auto_design_expected.json index bb5273d9b..6db256e30 100644 --- a/tests/data/testTopology_auto_design_expected.json +++ b/tests/data/testTopology_auto_design_expected.json @@ -160,7 +160,11 @@ "uid": "roadm Lannion_CAS", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -175,7 +179,11 @@ "uid": "roadm Lorient_KMA", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -190,7 +198,11 @@ "uid": "roadm Vannes_KBE", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -205,7 +217,11 @@ "uid": "roadm Rennes_STA", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -220,7 +236,11 @@ "uid": "roadm Brest_KLA", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -235,7 +255,13 @@ "uid": "roadm a", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [ + "std_booster" + ] + } }, "metadata": { "location": { @@ -250,7 +276,13 @@ "uid": "roadm b", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [ + "std_low_gain" + ], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -265,7 +297,11 @@ "uid": "roadm c", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -280,7 +316,11 @@ "uid": "roadm d", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -295,7 +335,11 @@ "uid": "roadm e", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -310,7 +354,11 @@ "uid": "roadm f", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -325,7 +373,11 @@ "uid": "roadm g", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -340,7 +392,11 @@ "uid": "roadm h", "type": "Roadm", "params": { - "target_pch_out_db": -20 + "target_pch_out_db": -20, + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [] + } }, "metadata": { "location": { @@ -354,6 +410,9 @@ { "uid": "west fused spans in Corlay", "type": "Fused", + "params": { + "loss": 1 + }, "metadata": { "location": { "latitude": 2.0, @@ -366,6 +425,9 @@ { "uid": "west fused spans in Loudeac", "type": "Fused", + "params": { + "loss": 1 + }, "metadata": { "location": { "latitude": 2.0, @@ -378,6 +440,9 @@ { "uid": "west fused spans in Morlaix", "type": "Fused", + "params": { + "loss": 1 + }, "metadata": { "location": { "latitude": 1.0, @@ -390,6 +455,9 @@ { "uid": "east fused spans in Corlay", "type": "Fused", + "params": { + "loss": 1 + }, "metadata": { "location": { "latitude": 2.0, @@ -402,6 +470,9 @@ { "uid": "east fused spans in Loudeac", "type": "Fused", + "params": { + "loss": 1 + }, "metadata": { "location": { "latitude": 2.0, @@ -414,6 +485,9 @@ { "uid": "east fused spans in Morlaix", "type": "Fused", + "params": { + "loss": 1 + }, "metadata": { "location": { "latitude": 1.0, @@ -737,7 +811,7 @@ "type_variety": "SSMF", "params": { "type_variety": "SSMF", - "length": 50.0, + "length": 10.0, "loss_coef": 0.2, "length_units": "km", "att_in": 0, @@ -1199,10 +1273,10 @@ "type_variety": "SSMF", "params": { "type_variety": "SSMF", - "length": 50.0, + "length": 10.0, "loss_coef": 0.2, "length_units": "km", - "att_in": 0, + "att_in": 8.0, "con_in": 0, "con_out": 0 }, @@ -1499,12 +1573,27 @@ } } }, + { + "uid": "east edfa in c to d", + "type": "Fused", + "params": { + "loss": 0 + }, + "metadata": { + "location": { + "latitude": 6.0, + "longitude": 1.0, + "city": "c", + "region": "" + } + } + }, { "uid": "Edfa0_roadm Lorient_KMA", "type": "Edfa", "type_variety": "test_fixed_gain", "operational": { - "gain_target": 20, + "gain_target": 20.0, "delta_p": null, "tilt_target": 0, "out_voa": 0 @@ -1523,7 +1612,7 @@ "type": "Edfa", "type_variety": "test_fixed_gain", "operational": { - "gain_target": 20, + "gain_target": 20.0, "delta_p": null, "tilt_target": 0, "out_voa": 0 @@ -1654,7 +1743,7 @@ { "uid": "Edfa0_roadm a", "type": "Edfa", - "type_variety": "test_fixed_gain", + "type_variety": "std_booster", "operational": { "gain_target": 20, "delta_p": null, @@ -1673,7 +1762,7 @@ { "uid": "Edfa1_roadm a", "type": "Edfa", - "type_variety": "test_fixed_gain", + "type_variety": "std_booster", "operational": { "gain_target": 20, "delta_p": null, @@ -1732,7 +1821,7 @@ "type": "Edfa", "type_variety": "test_fixed_gain", "operational": { - "gain_target": 20, + "gain_target": 22, "delta_p": null, "tilt_target": 0, "out_voa": 0 @@ -1751,26 +1840,7 @@ "type": "Edfa", "type_variety": "test_fixed_gain", "operational": { - "gain_target": 20, - "delta_p": null, - "tilt_target": 0, - "out_voa": 0 - }, - "metadata": { - "location": { - "latitude": 6.0, - "longitude": 1.75, - "city": "c", - "region": "" - } - } - }, - { - "uid": "Edfa2_roadm c", - "type": "Edfa", - "type_variety": "test_fixed_gain", - "operational": { - "gain_target": 20, + "gain_target": 22, "delta_p": null, "tilt_target": 0, "out_voa": 0 @@ -2186,9 +2256,9 @@ { "uid": "Edfa0_fiber (c → d)-", "type": "Edfa", - "type_variety": "std_low_gain", + "type_variety": "test_fixed_gain", "operational": { - "gain_target": 10.0, + "gain_target": 22.0, "delta_p": null, "tilt_target": 0, "out_voa": 0 @@ -2723,6 +2793,10 @@ "from_node": "roadm Brest_KLA", "to_node": "Edfa0_roadm Brest_KLA" }, + { + "from_node": "roadm c", + "to_node": "east edfa in c to d" + }, { "from_node": "roadm a", "to_node": "trx a" @@ -2759,10 +2833,6 @@ "from_node": "roadm c", "to_node": "Edfa1_roadm c" }, - { - "from_node": "roadm c", - "to_node": "Edfa2_roadm c" - }, { "from_node": "roadm d", "to_node": "trx d" @@ -3051,6 +3121,10 @@ "from_node": "west edfa in Lannion_CAS to Morlaix", "to_node": "roadm Lannion_CAS" }, + { + "from_node": "east edfa in c to d", + "to_node": "fiber (c → d)-" + }, { "from_node": "Edfa0_roadm Lorient_KMA", "to_node": "fiber (Lorient_KMA → Loudeac)-F054" @@ -3105,10 +3179,6 @@ }, { "from_node": "Edfa1_roadm c", - "to_node": "fiber (c → d)-" - }, - { - "from_node": "Edfa2_roadm c", "to_node": "fiber (c → f)-" }, { @@ -3284,4 +3354,4 @@ "to_node": "roadm h" } ] -} \ No newline at end of file +} diff --git a/tests/data/testTopology_expected.json b/tests/data/testTopology_expected.json index 3e54d2a0a..798beaa5f 100644 --- a/tests/data/testTopology_expected.json +++ b/tests/data/testTopology_expected.json @@ -218,6 +218,14 @@ }, { "uid": "roadm a", + "params": { + "restrictions": { + "preamp_variety_list": [], + "booster_variety_list": [ + "std_booster" + ] + } + }, "metadata": { "location": { "city": "a", @@ -230,6 +238,14 @@ }, { "uid": "roadm b", + "params": { + "restrictions": { + "preamp_variety_list": [ + "std_low_gain" + ], + "booster_variety_list": [] + } + }, "metadata": { "location": { "city": "b", @@ -647,7 +663,7 @@ "type": "Fiber", "type_variety": "SSMF", "params": { - "length": 50.0, + "length": 10.0, "length_units": "km", "loss_coef": 0.2, "con_in": null, @@ -1025,7 +1041,7 @@ "type": "Fiber", "type_variety": "SSMF", "params": { - "length": 50.0, + "length": 10.0, "length_units": "km", "loss_coef": 0.2, "con_in": null, @@ -1291,6 +1307,21 @@ "tilt_target": 0, "out_voa": null } + }, + { + "uid": "east edfa in c to d", + "metadata": { + "location": { + "city": "c", + "region": "", + "latitude": 6.0, + "longitude": 1.0 + } + }, + "type": "Fused", + "params": { + "loss": 0 + } } ], "connections": [ @@ -1536,6 +1567,10 @@ }, { "from_node": "roadm c", + "to_node": "east edfa in c to d" + }, + { + "from_node": "east edfa in c to d", "to_node": "fiber (c → d)-" }, { @@ -1743,4 +1778,4 @@ "to_node": "trx h" } ] -} \ No newline at end of file +} diff --git a/tests/data/test_network.json b/tests/data/test_network.json index 0b33502a5..6565829bd 100644 --- a/tests/data/test_network.json +++ b/tests/data/test_network.json @@ -77,7 +77,22 @@ "longitude": 0 } } - }, + }, + { + "uid": "Att_B", + "type": "Fused", + "params":{ + "loss":16 + }, + "metadata": { + "location": { + "latitude": 2.0, + "longitude": 1.0, + "city": "Corlay", + "region": "RLD" + } + } + }, { "uid": "Site_B", "type": "Transceiver", @@ -110,6 +125,10 @@ }, { "from_node": "Edfa2", + "to_node": "Att_B" + }, + { + "from_node": "Att_B", "to_node": "Site_B" } diff --git a/tests/test_propagation.py b/tests/test_propagation.py index a85866c87..b0bc87a6b 100644 --- a/tests/test_propagation.py +++ b/tests/test_propagation.py @@ -18,6 +18,8 @@ #network_file_name = 'tests/test_network.json' network_file_name = Path(__file__).parent.parent / 'tests/LinkforTest.json' +#TODO: note that this json entries has a weird topology since EDfa1 has a possible branch on a receiver B +# this might not pass future tests/ code updates #network_file_name = Path(__file__).parent.parent / 'examples/edfa_example_network.json' eqpt_library_name = Path(__file__).parent.parent / 'tests/data/eqpt_config.json' diff --git a/tests/test_roadm_restrictions.py b/tests/test_roadm_restrictions.py new file mode 100644 index 000000000..61f7a02d2 --- /dev/null +++ b/tests/test_roadm_restrictions.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# @Author: Esther Le Rouzic +# @Date: 2019-05-22 +""" +@author: esther.lerouzic +checks that fused placed in amp type is correctly converted to a fused element instead of an edfa +and that no additional amp is added. +checks that restrictions in roadms are correctly applied during autodesign + +""" + +from pathlib import Path +import pytest +from gnpy.core.utils import lin2db, load_json +from gnpy.core.elements import Fused, Roadm, Edfa +from gnpy.core.equipment import load_equipment, Amp, automatic_nch +from gnpy.core.network import network_from_json, build_network + + +TEST_DIR = Path(__file__).parent +EQPT_LIBRARY_NAME = TEST_DIR / 'data/eqpt_config.json' +NETWORK_FILE_NAME = TEST_DIR / 'data/testTopology_expected.json' +# adding tests to check the roadm restrictions + +# mark node_uid amps as fused for testing purpose +@pytest.mark.parametrize("node_uid", ['east edfa in Lannion_CAS to Stbrieuc']) +def test_no_amp_feature(node_uid): + ''' Check that booster is not placed on a roadm if fused is specified + test_parser covers partly this behaviour. This test should guaranty that the + feature is preserved even if convert is changed + ''' + equipment = load_equipment(EQPT_LIBRARY_NAME) + json_network = load_json(NETWORK_FILE_NAME) + + for elem in json_network['elements']: + if elem['uid'] == node_uid: + #replace edfa node by a fused node in the topology + elem['type'] = 'Fused' + elem.pop('type_variety') + elem.pop('operational') + elem['params'] = {'loss': 0} + + next_node_uid = next(conn['to_node'] for conn in json_network['connections'] \ + if conn['from_node'] == node_uid) + previous_node_uid = next(conn['from_node'] for conn in json_network['connections'] \ + if conn['to_node'] == node_uid) + + network = network_from_json(json_network, equipment) + # Build the network once using the default power defined in SI in eqpt config + # power density : db2linp(ower_dbm": 0)/power_dbm": 0 * nb channels as defined by + # spacing, f_min and f_max + p_db = equipment['SI']['default'].power_dbm + p_total_db = p_db + lin2db(automatic_nch(equipment['SI']['default'].f_min,\ + equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) + + build_network(network, equipment, p_db, p_total_db) + + node = next(nd for nd in network.nodes() if nd.uid == node_uid) + next_node = next(network.successors(node)) + previous_node = next(network.predecessors(node)) + + if not isinstance(node, Fused): + raise AssertionError() + if not node.params.loss == 0.0: + raise AssertionError() + if not next_node_uid == next_node.uid: + raise AssertionError() + if not previous_node_uid == previous_node.uid: + raise AssertionError() + +@pytest.fixture() +def equipment(): + """init transceiver class to access snr and osnr calculations""" + equipment = load_equipment(EQPT_LIBRARY_NAME) + # define some booster and preamps + restrictions_list = [ + { + 'type_variety': 'booster_medium_gain', + 'type_def': 'variable_gain', + 'gain_flatmax': 25, + 'gain_min': 15, + 'p_max': 21, + 'nf_min': 5.8, + 'nf_max': 10, + 'out_voa_auto': False, + 'allowed_for_design': False + }, + { + 'type_variety': 'preamp_medium_gain', + 'type_def': 'variable_gain', + 'gain_flatmax': 26, + 'gain_min': 15, + 'p_max': 23, + 'nf_min': 6, + 'nf_max': 10, + 'out_voa_auto': False, + 'allowed_for_design': False + }, + { + 'type_variety': 'preamp_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': False + }, + { + 'type_variety': 'preamp_low_gain', + 'type_def': 'variable_gain', + 'gain_flatmax': 16, + 'gain_min': 8, + 'p_max': 23, + 'nf_min': 6.5, + 'nf_max': 11, + 'out_voa_auto': False, + 'allowed_for_design': False + }] + # add them to the library + for entry in restrictions_list: + equipment['Edfa'][entry['type_variety']] = Amp.from_json(EQPT_LIBRARY_NAME, **entry) + return equipment + + +@pytest.mark.parametrize("restrictions", [ + { + 'preamp_variety_list':[], + 'booster_variety_list':[] + }, + { + 'preamp_variety_list':[], + 'booster_variety_list':['booster_medium_gain'] + }, + { + 'preamp_variety_list':['preamp_medium_gain', 'preamp_high_gain', 'preamp_low_gain'], + 'booster_variety_list':[] + }]) +def test_restrictions(restrictions, equipment): + ''' test that restriction is correctly applied if provided in eqpt_config and if no Edfa type + were provided in the network json + ''' + # add restrictions + equipment['Roadm']['default'].restrictions = restrictions + # build network + json_network = load_json(NETWORK_FILE_NAME) + network = network_from_json(json_network, equipment) + + amp_nodes_nobuild_uid = [nd.uid for nd in network.nodes() \ + if isinstance(nd, Edfa) and isinstance(next(network.predecessors(nd)), Roadm)] + preamp_nodes_nobuild_uid = [nd.uid for nd in network.nodes() \ + if isinstance(nd, Edfa) and isinstance(next(network.successors(nd)), Roadm)] + amp_nodes_nobuild = {nd.uid : nd for nd in network.nodes() \ + if isinstance(nd, Edfa) and isinstance(next(network.predecessors(nd)), Roadm)} + preamp_nodes_nobuild = {nd.uid : nd for nd in network.nodes() \ + if isinstance(nd, Edfa) and isinstance(next(network.successors(nd)), Roadm)} + # roadm dict with restrictions before build + roadms = {nd.uid: nd for nd in network.nodes() if isinstance(nd, Roadm)} + # Build the network once using the default power defined in SI in eqpt config + # power density : db2linp(ower_dbm": 0)/power_dbm": 0 * nb channels as defined by + # spacing, f_min and f_max + p_db = equipment['SI']['default'].power_dbm + p_total_db = p_db + lin2db(automatic_nch(equipment['SI']['default'].f_min,\ + equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) + + build_network(network, equipment, p_db, p_total_db) + + amp_nodes = [nd for nd in network.nodes() \ + if isinstance(nd, Edfa) and isinstance(next(network.predecessors(nd)), Roadm)\ + and next(network.predecessors(nd)).restrictions['booster_variety_list']] + + preamp_nodes = [nd for nd in network.nodes() \ + if isinstance(nd, Edfa) and isinstance(next(network.successors(nd)), Roadm)\ + and next(network.successors(nd)).restrictions['preamp_variety_list']] + + # check that previously existing amp are not changed + for amp in amp_nodes: + if amp.uid in amp_nodes_nobuild_uid: + print(amp.uid, amp.params.type_variety) + if not amp.params.type_variety == amp_nodes_nobuild[amp.uid].params.type_variety: + raise AssertionError() + for amp in preamp_nodes: + if amp.uid in preamp_nodes_nobuild_uid: + if not amp.params.type_variety == preamp_nodes_nobuild[amp.uid].params.type_variety: + raise AssertionError() + # check that restrictions are correctly applied + for amp in amp_nodes: + if amp.uid not in amp_nodes_nobuild_uid: + # and if roadm had no restrictions before build: + if restrictions['booster_variety_list'] and \ + not roadms[next(network.predecessors(amp)).uid]\ + .restrictions['booster_variety_list']: + if not amp.params.type_variety in restrictions['booster_variety_list']: + + raise AssertionError() + for amp in preamp_nodes: + if amp.uid not in preamp_nodes_nobuild_uid: + if restrictions['preamp_variety_list'] and\ + not roadms[next(network.successors(amp)).uid].restrictions['preamp_variety_list']: + if not amp.params.type_variety in restrictions['preamp_variety_list']: + raise AssertionError() From 862845b4ac640e4483d4f93d238be0a08f760989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 6 Jun 2019 21:51:48 +0200 Subject: [PATCH 039/117] docs: minor grammar fixes --- gnpy/core/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnpy/core/node.py b/gnpy/core/node.py index 875b6d946..746fbee13 100644 --- a/gnpy/core/node.py +++ b/gnpy/core/node.py @@ -9,12 +9,12 @@ Strictly, a network element is any callable which accepts an immutable .info.SpectralInformation object and returns a .info.SpectralInformation object -(a copy.) +(a copy). Network elements MUST implement two attributes .uid and .name representing a unique identifier and a printable name. -This base class provides a mode convenient way to define a network element +This base class provides a more convenient way to define a network element via subclassing. ''' From 168f1891cfa51e160bbd44007a6b254cc4c98a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 6 Jun 2019 22:53:54 +0200 Subject: [PATCH 040/117] docs: ensure that all modules are documented --- docs/source/gnpy.core.rst | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/source/gnpy.core.rst b/docs/source/gnpy.core.rst index 284a3f396..ff2c850bc 100644 --- a/docs/source/gnpy.core.rst +++ b/docs/source/gnpy.core.rst @@ -4,6 +4,22 @@ gnpy\.core package Submodules ---------- +gnpy\.core\.ansi_escapes module +------------------------------- + +.. automodule:: gnpy.core.ansi_escapes + :members: + :undoc-members: + :show-inheritance: + +gnpy\.core\.convert module +-------------------------- + +.. automodule:: gnpy.core.convert + :members: + :undoc-members: + :show-inheritance: + gnpy\.core\.elements module --------------------------- @@ -12,6 +28,22 @@ gnpy\.core\.elements module :undoc-members: :show-inheritance: +gnpy\.core\.equipment module +---------------------------- + +.. automodule:: gnpy.core.equipment + :members: + :undoc-members: + :show-inheritance: + +gnpy\.core\.exceptions module +----------------------------- + +.. automodule:: gnpy.core.exceptions + :members: + :undoc-members: + :show-inheritance: + gnpy\.core\.execute module -------------------------- @@ -44,6 +76,22 @@ gnpy\.core\.node module :undoc-members: :show-inheritance: +gnpy\.core\.request module +-------------------------- + +.. automodule:: gnpy.core.request + :members: + :undoc-members: + :show-inheritance: + +gnpy\.core\.service_sheet module +-------------------------------- + +.. automodule:: gnpy.core.service_sheet + :members: + :undoc-members: + :show-inheritance: + gnpy\.core\.units module ------------------------ From bfecff0412d03a04e8c5641727853c2492800eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 6 Jun 2019 23:04:06 +0200 Subject: [PATCH 041/117] docs: use a default preset here to prevent extra repetitions --- docs/conf.py | 3 +-- docs/source/gnpy.core.rst | 24 ------------------------ docs/source/gnpy.rst | 3 --- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6a1a244b1..4f7c30966 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -173,5 +173,4 @@ 'Miscellaneous'), ] - - +autodoc_default_flags = ['members', 'undoc-members', 'show-inheritance'] diff --git a/docs/source/gnpy.core.rst b/docs/source/gnpy.core.rst index ff2c850bc..970ab2400 100644 --- a/docs/source/gnpy.core.rst +++ b/docs/source/gnpy.core.rst @@ -24,9 +24,6 @@ gnpy\.core\.elements module --------------------------- .. automodule:: gnpy.core.elements - :members: - :undoc-members: - :show-inheritance: gnpy\.core\.equipment module ---------------------------- @@ -48,33 +45,21 @@ gnpy\.core\.execute module -------------------------- .. automodule:: gnpy.core.execute - :members: - :undoc-members: - :show-inheritance: gnpy\.core\.info module ----------------------- .. automodule:: gnpy.core.info - :members: - :undoc-members: - :show-inheritance: gnpy\.core\.network module -------------------------- .. automodule:: gnpy.core.network - :members: - :undoc-members: - :show-inheritance: gnpy\.core\.node module ----------------------- .. automodule:: gnpy.core.node - :members: - :undoc-members: - :show-inheritance: gnpy\.core\.request module -------------------------- @@ -96,23 +81,14 @@ gnpy\.core\.units module ------------------------ .. automodule:: gnpy.core.units - :members: - :undoc-members: - :show-inheritance: gnpy\.core\.utils module ------------------------ .. automodule:: gnpy.core.utils - :members: - :undoc-members: - :show-inheritance: Module contents --------------- .. automodule:: gnpy.core - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/gnpy.rst b/docs/source/gnpy.rst index c3bea3fe5..cf732cf29 100644 --- a/docs/source/gnpy.rst +++ b/docs/source/gnpy.rst @@ -12,6 +12,3 @@ Module contents --------------- .. automodule:: gnpy - :members: - :undoc-members: - :show-inheritance: From f46134fda550b6521dc35bcb6e315f96403dae22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 6 Jun 2019 23:05:42 +0200 Subject: [PATCH 042/117] docs: document the _private methods This is a reference documentation, so it makes sense for it to be reasonably complete. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 4f7c30966..16a872a92 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -173,4 +173,4 @@ 'Miscellaneous'), ] -autodoc_default_flags = ['members', 'undoc-members', 'show-inheritance'] +autodoc_default_flags = ['members', 'undoc-members', 'private-members', 'show-inheritance'] From 16173355f33e177b43e178e39e7b1b47de486289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 6 Jun 2019 23:26:12 +0200 Subject: [PATCH 043/117] docs: random improvements and Sphinxiation --- gnpy/core/elements.py | 45 +++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index e979c169c..a59a41145 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -290,12 +290,12 @@ def __str__(self): @property def fiber_loss(self): - # dB fiber loss, not including padding attenuator + """Fiber loss in dB, not including padding attenuator""" return self.loss_coef * self.length + self.con_in + self.con_out @property def loss(self): - #total loss incluiding padding att_in: useful for polymorphism with roadm loss + """total loss including padding att_in: useful for polymorphism with roadm loss""" return self.loss_coef * self.length + self.con_in + self.con_out + self.att_in @property @@ -320,8 +320,10 @@ def asymptotic_length(self): def carriers(self, loc, attr): """retrieve carriers information - loc = (in, out) of the class element - attr = (ase, nli, signal, total) power information""" + + :param loc: (in, out) of the class element + :param attr: (ase, nli, signal, total) power information + """ if not (loc in ('in', 'out') and attr in ('nli', 'signal', 'total', 'ase')): yield None return @@ -338,11 +340,11 @@ def carriers(self, loc, attr): yield c.power._asdict().get(attr, None) def beta2(self, ref_wavelength=None): - """ Returns beta2 from dispersion parameter. + """Returns beta2 from dispersion parameter. Dispersion is entered in ps/nm/km. - Disperion can be a numpy array or a single value. If a - value ref_wavelength is not entered 1550e-9m will be assumed. - ref_wavelength can be a numpy array. + Disperion can be a numpy array or a single value. + + :param ref_wavelength: can be a numpy array; default: 1550e-9m """ # TODO|jla: discuss beta2 as method or attribute wl = 1550e-9 if ref_wavelength is None else ref_wavelength @@ -351,17 +353,15 @@ def beta2(self, ref_wavelength=None): return b2 # s/Hz/m def dbkm_2_lin(self): - """ calculates the linear loss coefficient - """ - # alpha_pcoef is linear loss coefficient in dB/km^-1 - # alpha_acoef is linear loss field amplitude coefficient in m^-1 + """calculates the linear loss coefficient""" + # linear loss coefficient in dB/km^-1 alpha_pcoef = self.loss_coef + # linear loss field amplitude coefficient in m^-1 alpha_acoef = alpha_pcoef / (2 * 10 * log10(exp(1))) return alpha_pcoef, alpha_acoef def _psi(self, carrier, interfering_carrier): - """ Calculates eq. 123 from arXiv:1209.0394. - """ + """Calculates eq. 123 from `arXiv:1209.0394 `__""" if carrier.num_chan == interfering_carrier.num_chan: # SCI psi = arcsinh(0.5 * pi**2 * self.asymptotic_length * abs(self.beta2()) * carrier.baud_rate**2) @@ -375,8 +375,9 @@ def _psi(self, carrier, interfering_carrier): return psi def _gn_analytic(self, carrier, *carriers): - """ Computes the nonlinear interference power on a single carrier. - The method uses eq. 120 from arXiv:1209.0394. + """Computes the nonlinear interference power on a single carrier. + The method uses eq. 120 from `arXiv:1209.0394 `__. + :param carrier: the signal under analysis :param carriers: the full WDM comb :return: carrier_nli: the amount of nonlinear interference in W on the under analysis @@ -557,8 +558,10 @@ def __str__(self): def carriers(self, loc, attr): """retrieve carriers information - loc = (in, out) of the class element - attr = (ase, nli, signal, total) power information""" + + :param loc: (in, out) of the class element + :param attr: (ase, nli, signal, total) power information + """ if not (loc in ('in', 'out') and attr in ('nli', 'signal', 'total', 'ase')): yield None return @@ -575,7 +578,7 @@ def carriers(self, loc, attr): yield c.power._asdict().get(attr, None) def interpol_params(self, frequencies, pin, baud_rates, pref): - """interpolate SI channel frequencies with the edfa dgt and gain_ripple frquencies from json + """interpolate SI channel frequencies with the edfa dgt and gain_ripple frquencies from JSON set the edfa class __init__ None parameters : self.channel_freq, self.nf, self.interpol_dgt and self.interpol_gain_ripple """ @@ -674,7 +677,7 @@ def _calc_nf(self, avg = False): 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) + """noise_profile(bw) computes amplifier ase (W) in signal bw (Hz) noise is calculated at amplifier input :bw: signal bandwidth = baud rate in Hz @@ -829,7 +832,7 @@ def _gain_profile(self, pin, err_tolerance=1.0e-11, simple_opt=True): return g1st - voa + array(self.interpol_dgt) * dgts3 def propagate(self, pref, *carriers): - """add ase noise to the propagating carriers of SpectralInformation""" + """add ASE noise to the propagating carriers of SpectralInformation""" pin = array([c.power.signal+c.power.nli+c.power.ase for c in carriers]) # pin in W freq = array([c.frequency for c in carriers]) brate = array([c.baud_rate for c in carriers]) From d8feccc7157389e8f05a79393b292ec23e3819fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 6 Jun 2019 22:15:05 +0200 Subject: [PATCH 044/117] Python: do not use a mutable default value Because default arguments are evaluated *once*, not every time they are called, a mutable default value is not "reset", and this happens: >>> from gnpy.core.node import Node >>> x=Node('123') >>> y=Node('456') >>> print(x.metadata) {'location': Location(latitude=0, longitude=0, city=None, region=None)} >>> print(y.metadata) {'location': Location(latitude=0, longitude=0, city=None, region=None)} >>> y.metadata['foo']=123 >>> print(x.metadata) {'location': Location(latitude=0, longitude=0, city=None, region=None), 'foo': 123} This is easily fixable by using an immutable value as a placeholder here. --- gnpy/core/elements.py | 11 +++++------ gnpy/core/node.py | 4 +++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index e979c169c..c94c57300 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -476,12 +476,11 @@ def __repr__(self): f'tilt_target={self.tilt_target!r})') class Edfa(Node): - def __init__(self, *args, params={}, operational={}, **kwargs): - #TBC is this useful? put in comment for now: - #if params is None: - # params = {} - #if operational is None: - # operational = {} + def __init__(self, *args, params=None, operational=None, **kwargs): + if params is None: + params = {} + if operational is None: + operational = {} super().__init__( *args, params=EdfaParams(**params), diff --git a/gnpy/core/node.py b/gnpy/core/node.py index 875b6d946..fcc5a6ed9 100644 --- a/gnpy/core/node.py +++ b/gnpy/core/node.py @@ -26,10 +26,12 @@ def __new__(cls, latitude=0, longitude=0, city=None, region=None): return super().__new__(cls, latitude, longitude, city, region) class Node: - def __init__(self, uid, name=None, params=None, metadata={'location':{}}, operational=None): + def __init__(self, uid, name=None, params=None, metadata=None, operational=None): if name is None: name = uid self.uid, self.name = uid, name + if metadata is None: + metadata = {'location': {}} if metadata and not isinstance(metadata.get('location'), Location): metadata['location'] = Location(**metadata.pop('location', {})) self.params, self.metadata, self.operational = params, metadata, operational From c3499142b0dbdc68945352a820a16e3a7af965d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 6 Jun 2019 23:47:52 +0200 Subject: [PATCH 045/117] doc: hyperlinks++ --- gnpy/core/elements.py | 6 +++--- gnpy/core/info.py | 2 +- gnpy/core/node.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index a59a41145..70dfe1e91 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -7,11 +7,11 @@ This module contains standard network elements. -A network element is a Python callable. It takes a .info.SpectralInformation +A network element is a Python callable. It takes a :class:`.info.SpectralInformation` object and returns a copy with appropriate fields affected. This structure represents spectral information that is "propogated" by this network element. Network elements must have only a local "view" of the network and propogate -SpectralInformation using only this information. They should be independent and +:class:`.info.SpectralInformation` using only this information. They should be independent and self-contained. Network elements MUST implement two attributes .uid and .name representing a @@ -832,7 +832,7 @@ def _gain_profile(self, pin, err_tolerance=1.0e-11, simple_opt=True): return g1st - voa + array(self.interpol_dgt) * dgts3 def propagate(self, pref, *carriers): - """add ASE noise to the propagating carriers of SpectralInformation""" + """add ASE noise to the propagating carriers of :class:`.info.SpectralInformation`""" pin = array([c.power.signal+c.power.nli+c.power.ase for c in carriers]) # pin in W freq = array([c.frequency for c in carriers]) brate = array([c.baud_rate for c in carriers]) diff --git a/gnpy/core/info.py b/gnpy/core/info.py index 5ae60ea93..ceb6f4049 100644 --- a/gnpy/core/info.py +++ b/gnpy/core/info.py @@ -5,7 +5,7 @@ gnpy.core.info ============== -This module contains classes for modelling SpectralInformation. +This module contains classes for modelling :class:`SpectralInformation`. ''' diff --git a/gnpy/core/node.py b/gnpy/core/node.py index 746fbee13..65eb99340 100644 --- a/gnpy/core/node.py +++ b/gnpy/core/node.py @@ -8,7 +8,7 @@ This module contains the base class for a network element. Strictly, a network element is any callable which accepts an immutable -.info.SpectralInformation object and returns a .info.SpectralInformation object +:class:`.info.SpectralInformation` object and returns an :class:`.info.SpectralInformation` object (a copy). Network elements MUST implement two attributes .uid and .name representing a From 89d666948e7125a6e3f33236f02e09083697c326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 6 Jun 2019 23:55:16 +0200 Subject: [PATCH 046/117] Fiber: beta2: use a default value directly Rather than do the None-dance and document the default value within a docstring, let's use the default value directly. This is safe because numbers are immutable. --- gnpy/core/elements.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index 70dfe1e91..f35e0a586 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -339,17 +339,16 @@ def carriers(self, loc, attr): else: yield c.power._asdict().get(attr, None) - def beta2(self, ref_wavelength=None): + def beta2(self, ref_wavelength=1550e-9): """Returns beta2 from dispersion parameter. Dispersion is entered in ps/nm/km. Disperion can be a numpy array or a single value. - :param ref_wavelength: can be a numpy array; default: 1550e-9m + :param ref_wavelength: can be a numpy array; default: 1550nm """ # TODO|jla: discuss beta2 as method or attribute - wl = 1550e-9 if ref_wavelength is None else ref_wavelength D = abs(self.dispersion) - b2 = (wl ** 2) * D / (2 * pi * c) # 10^21 scales [ps^2/km] + b2 = (ref_wavelength ** 2) * D / (2 * pi * c) # 10^21 scales [ps^2/km] return b2 # s/Hz/m def dbkm_2_lin(self): From 2dff9346127894d7060429beef3c91f16254b1c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Fri, 7 Jun 2019 00:02:53 +0200 Subject: [PATCH 047/117] CI: run the examples as well We are not checking their results or anything, but it is nice to be able to say "hey, these still work". --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 27aec6ae4..3504c4a8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,5 +11,7 @@ install: script: - pytest --cov-report=xml --cov=gnpy - rstcheck --ignore-roles cite --ignore-directives automodule --recursive --ignore-messages '(Duplicate explicit target name.*)' . + - cd examples; ./transmission_main_example.py + - cd examples; ./path_requests_run.py after_success: - bash <(curl -s https://codecov.io/bash) From fb9915d301fb9a7e2d29517b4583e4ba5fdf3b73 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 11 Jun 2019 13:23:38 +0200 Subject: [PATCH 048/117] add raman efficiency in SSMF equipment --- examples/eqpt_with_raman_config.json | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/examples/eqpt_with_raman_config.json b/examples/eqpt_with_raman_config.json index a10d5e9d5..e9737c1fa 100644 --- a/examples/eqpt_with_raman_config.json +++ b/examples/eqpt_with_raman_config.json @@ -143,23 +143,17 @@ "allowed_for_design": false } ], - "Fiber":[{ + "RamanFiber":[{ "type_variety": "SSMF", "dispersion": 1.67e-05, - "gamma": 0.00127 - }, - { - "type_variety": "NZDF", - "dispersion": 0.5e-05, - "gamma": 0.00146 - }, - { - "type_variety": "LOF", - "dispersion": 2.2e-05, - "gamma": 0.000843 + "gamma": 0.00127, + "raman_efficiency": { + "cr": [0, 9.4E-06, 2.92E-05, 4.88E-05, 6.82E-05, 8.31E-05, 9.4E-05, 0.0001014, 0.0001069, 0.0001119, 0.0001217, 0.0001268, 0.0001365, 0.000149, 0.000165, 0.000181, 0.0001977, 0.0002192, 0.0002469, 0.0002749, 0.0002999, 0.0003206, 0.0003405, 0.0003592, 0.000374, 0.0003826, 0.0003841, 0.0003826, 0.0003802, 0.0003756, 0.0003549, 0.0003795, 0.000344, 0.0002933, 0.0002024, 0.0001158, 8.46E-05, 7.14E-05, 6.86E-05, 8.5E-05, 8.93E-05, 9.01E-05, 8.15E-05, 6.67E-05, 4.37E-05, 3.28E-05, 2.96E-05, 2.65E-05, 2.57E-05, 2.81E-05, 3.08E-05, 3.67E-05, 5.85E-05, 6.63E-05, 6.36E-05, 5.5E-05, 4.06E-05, 2.77E-05, 2.42E-05, 1.87E-05, 1.6E-05, 1.4E-05, 1.13E-05, 1.05E-05, 9.8E-06, 9.8E-06, 1.13E-05, 1.64E-05, 1.95E-05, 2.38E-05, 2.26E-05, 2.03E-05, 1.48E-05, 1.09E-05, 9.8E-06, 1.05E-05, 1.17E-05, 1.25E-05, 1.21E-05, 1.09E-05, 9.8E-06, 8.2E-06, 6.6E-06, 4.7E-06, 2.7E-06, 1.9E-06, 1.2E-06, 4E-07, 2E-07, 1E-07], + "frequency_offset": [0, 0.5e12, 1e12, 1.5e12, 2e12, 2.5e12, 3e12, 3.5e12, 4e12, 4.5e12, 5e12, 5.5e12, 6e12, 6.5e12, 7e12, 7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 1e120, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12750000000000, 13e12, 13250000000000, 13.5e12, 14e12, 14.5e12, 14750000000000, 15e12, 15.5e12, 16e12, 16.5e12, 17e12, 17.5e12, 18e12, 18250000000000, 18.5e12, 18750000000000, 19e12, 19.5e12, 2e120, 20.5e12, 21e12, 21.5e12, 22e12, 22.5e12, 23e12, 23.5e12, 24e12, 24.5e12, 25e12, 25.5e12, 26e12, 26.5e12, 27e12, 27.5e12, 28e12, 28.5e12, 29e12, 29.5e12, 3e120, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12, 35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 4e120, 40.5e12, 41e12, 41.5e12, 42e12] + } } ], - "RamanFiber":[{ + "Fiber":[{ "type_variety": "SSMF", "dispersion": 1.67e-05, "gamma": 0.00127 From 5cf5dd223438818d9d53a3a408b0f3597d6e685b Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 11 Jun 2019 13:24:12 +0200 Subject: [PATCH 049/117] add temperature parameter in fiber --- examples/raman_edfa_example_network.json | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/raman_edfa_example_network.json b/examples/raman_edfa_example_network.json index 489aa8875..6ead4c526 100644 --- a/examples/raman_edfa_example_network.json +++ b/examples/raman_edfa_example_network.json @@ -17,6 +17,7 @@ "type": "RamanFiber", "type_variety": "SSMF", "operational": { + "temperature": 283, "raman_pumps": [ { "power": 200e-3, From 561c8aff851d7ef16fa796eb41286c682cddeddb Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 11 Jun 2019 13:27:40 +0200 Subject: [PATCH 050/117] modified sim_params for transmission_with_raman_main_example.py --- examples/sim_params.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/sim_params.json b/examples/sim_params.json index dadad9913..e011f8ae6 100644 --- a/examples/sim_params.json +++ b/examples/sim_params.json @@ -1,16 +1,16 @@ { - "list_of_channels_under_test": [1, 2, 3, 5, 40, 90], + "list_of_channels_under_test": [1, 2, 3, 4, 37, 38, 39, 74, 75], "raman_parameters": { "flag_raman": true, - "space_resolution": 1e3, - "tolerance": 1e-8, + "space_resolution": 20e3, + "tolerance": 1e-7, "verbose": 2 }, "nli_parameters": { - "nli_method_name": "GGN", + "nli_method_name": "gn_model_analytic", "wdm_grid_size": 50e9, "dispersion_tolerance": 1, "phase_shift_tollerance": 0.1, "verbose": 1 } -} +} \ No newline at end of file From 6ad011d12d0336f6337420b574db39d04a19815e Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 11 Jun 2019 13:28:39 +0200 Subject: [PATCH 051/117] raman_efficiency included in RamanFiber equipment --- gnpy/core/equipment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gnpy/core/equipment.py b/gnpy/core/equipment.py index 613c278cb..232714588 100644 --- a/gnpy/core/equipment.py +++ b/gnpy/core/equipment.py @@ -110,7 +110,8 @@ class RamanFiber(common): { 'type_variety': '', 'dispersion': None, - 'gamma': 0 + 'gamma': 0, + 'raman_efficiency': None } def __init__(self, **kwargs): From 471eab126ea8a1fa9fdd2145528a1c84465c5530 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 11 Jun 2019 13:38:05 +0200 Subject: [PATCH 052/117] fix raman solver parameters --- gnpy/core/science_utils.py | 302 ++++++++++++------------------------- 1 file changed, 94 insertions(+), 208 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index a100ac13b..e015444ac 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -1,15 +1,13 @@ -import progressbar import numpy as np from operator import attrgetter -from scipy.interpolate import interp1d - -from gnpy.core.utils import load_json +from collections import namedtuple import scipy.constants as ph from scipy.integrate import solve_bvp from scipy.integrate import cumtrapz from scipy.interpolate import interp1d from scipy.optimize import OptimizeResult -from gnpy.core.elements import RamanFiber + +from gnpy.core.utils import db2lin, load_json def load_sim_params(path_sim_params): @@ -17,6 +15,7 @@ def load_sim_params(path_sim_params): return SimParams(params=sim_params) def configure_network(network, sim_params): + from gnpy.core.elements import RamanFiber for node in network.nodes: if isinstance(node, RamanFiber): node.sim_params = sim_params @@ -46,104 +45,102 @@ def __init__(self, params=None): self.nli_params = NLIParams(params=params['nli_parameters']) class RamanSolver: - def __init__(self, fiber_information=None): + def __init__(self, raman_params=None, fiber_params=None): """ Initialize the fiber object with its physical parameters :param length: fiber length in m. :param alphap: fiber power attenuation coefficient vs frequency in 1/m. numpy array :param freq_alpha: frequency axis of alphap in Hz. numpy array :param cr_raman: Raman efficiency vs frequency offset in 1/W/m. numpy array :param freq_cr: reference frequency offset axis for cr_raman. numpy array - :param solver_params: namedtuple containing the solver parameters (optional). + :param raman_params: namedtuple containing the solver parameters (optional). """ - self._fiber_information = fiber_information - self._solver_params = None - self._spectral_information = None - self._raman_pump_information = None - self._stimulated_raman_scattering = None - self._spontaneous_raman_scattering = None + self.fiber_params = fiber_params + self.raman_params = raman_params + self.__carriers = None + self.__stimulated_raman_scattering = None + self.__spontaneous_raman_scattering = None @property - def fiber_information(self): - return self._fiber_information + def fiber_params(self): + return self.__fiber_params - @fiber_information.setter - def fiber_information(self, fiber_information): - self._fiber_information = fiber_information - self._stimulated_raman_scattering = None + @fiber_params.setter + def fiber_params(self, fiber_params): + self.__stimulated_raman_scattering = None + self.__fiber_params = fiber_params @property - def spectral_information(self): - return self._spectral_information + def carriers(self): + return self.__carriers - @spectral_information.setter - def spectral_information(self, spectral_information): + @carriers.setter + def carriers(self, carriers): """ - :param spectral_information: namedtuple containing all the spectral information about carriers and eventual Raman pumps + :param carriers: tuple of namedtuples containing information about carriers :return: """ - self._spectral_information = spectral_information - self._stimulated_raman_scattering = None + self.__carriers = carriers + self.__stimulated_raman_scattering = None @property - def raman_pump_information(self): - return self._raman_pump_information + def raman_pumps(self): + return self._raman_pumps - @raman_pump_information.setter - def raman_pump_information(self, raman_pump_information): - self._raman_pump_information = raman_pump_information + @raman_pumps.setter + def raman_pumps(self, raman_pumps): + self._raman_pumps = raman_pumps + self.__stimulated_raman_scattering = None @property - def solver_params(self): - return self._solver_params + def raman_params(self): + return self.__raman_params - @solver_params.setter - def solver_params(self, solver_params): + @raman_params.setter + def raman_params(self, raman_params): """ - :param solver_params: namedtuple containing the solver parameters (optional). + :param raman_params: namedtuple containing the solver parameters (optional). :return: """ - self._solver_params = solver_params - self._stimulated_raman_scattering = None - self._spontaneous_raman_scattering = None + self.__raman_params = raman_params + self.__stimulated_raman_scattering = None + self.__spontaneous_raman_scattering = None @property def spontaneous_raman_scattering(self): - if self._spontaneous_raman_scattering is None: + if self.__spontaneous_raman_scattering is None: # SET STUFF - attenuation_coefficient = self.fiber_information.attenuation_coefficient - raman_coefficient = self.fiber_information.raman_coefficient - temp_k = self.fiber_information.temperature + loss_coef = self.fiber_params.loss_coef + raman_efficiency = self.fiber_params.raman_efficiency + temperature = self.fiber_params.temperature - spectral_info = self.spectral_information - raman_pump_information = self.raman_pump_information + carriers = self.carriers + raman_pumps = self.raman_pumps - verbose = self.solver_params.verbose + verbose = self.raman_params.verbose if verbose: print('Start computing fiber Spontaneous Raman Scattering') - power_spectrum, freq_array, prop_direct, bn_array = self._compute_power_spectrum(spectral_info, raman_pump_information) - temperature = temp_k.temperature + power_spectrum, freq_array, prop_direct, bn_array = self._compute_power_spectrum(carriers, raman_pumps) - if len(attenuation_coefficient.alpha_power) >= 2: - interp_alphap = interp1d(attenuation_coefficient.frequency, attenuation_coefficient.alpha_power) - alphap_fiber = interp_alphap(freq_array) + if not hasattr(loss_coef, 'alpha_power'): + alphap_fiber = loss_coef * np.ones(freq_array.shape) else: - alphap_fiber = attenuation_coefficient.alpha_power * np.ones(freq_array.shape) + interp_alphap = interp1d(loss_coef['frequency'], loss_coef['alpha_power']) + alphap_fiber = interp_alphap(freq_array) freq_diff = abs(freq_array - np.reshape(freq_array, (len(freq_array), 1))) - if len(raman_coefficient.cr) >= 2: - interp_cr = interp1d(raman_coefficient.frequency, raman_coefficient.cr) - cr = interp_cr(freq_diff) - else: - cr = raman_coefficient.cr * np.ones(freq_diff.shape) + interp_cr = interp1d(raman_efficiency['frequency_offset'], raman_efficiency['cr']) + cr = interp_cr(freq_diff) # z propagation axis - z_array = self.stimulated_raman_scattering.z + z_array = self.__stimulated_raman_scattering.z ase_bc = np.zeros(freq_array.shape) # calculate ase power - spontaneous_raman_scattering = self._int_spontaneous_raman(z_array, self.stimulated_raman_scattering.power, alphap_fiber, freq_array, cr, freq_diff, ase_bc, bn_array, temperature) + spontaneous_raman_scattering = self._int_spontaneous_raman(z_array, self.__stimulated_raman_scattering.power, + alphap_fiber, freq_array, cr, freq_diff, ase_bc, + bn_array, temperature) setattr(spontaneous_raman_scattering, 'frequency', freq_array) setattr(spontaneous_raman_scattering, 'z', z_array) @@ -153,16 +150,16 @@ def spontaneous_raman_scattering(self): if verbose: print(spontaneous_raman_scattering.message) - self._spontaneous_raman_scattering = spontaneous_raman_scattering + self.__spontaneous_raman_scattering = spontaneous_raman_scattering - return self._spontaneous_raman_scattering + return self.__spontaneous_raman_scattering @staticmethod - def _compute_power_spectrum(spectral_information, raman_pump_information): + def _compute_power_spectrum(carriers, raman_pumps=None): """ Rearrangement of spectral and Raman pump information to make them compatible with Raman solver - :param spectral_information: a namedtuple describing the transmitted channels - :param raman_pump_information: a namedtuple describing the Raman pumps + :param carriers: a tuple of namedtuples describing the transmitted channels + :param raman_pumps: a namedtuple describing the Raman pumps :return: """ @@ -170,20 +167,22 @@ def _compute_power_spectrum(spectral_information, raman_pump_information): pow_array = np.array([]) f_array = np.array([]) noise_bandwidth_array = np.array([]) - for carrier in sorted(spectral_information.carriers, key=attrgetter('frequency')): + for carrier in sorted(carriers, key=attrgetter('frequency')): f_array = np.append(f_array, carrier.frequency) pow_array = np.append(pow_array, carrier.power.signal) - noise_bandwidth_array = np.append(noise_bandwidth_array, carrier.baud_rate) + ref_bw = carrier.baud_rate + noise_bandwidth_array = np.append(noise_bandwidth_array, ref_bw) propagation_direction = np.ones(len(f_array)) # Raman pump power spectrum - if raman_pump_information: - for pump in raman_pump_information.raman_pumps: + if raman_pumps: + for pump in raman_pumps: pow_array = np.append(pow_array, pump.power) f_array = np.append(f_array, pump.frequency) - propagation_direction = np.append(propagation_direction, pump.propagation_direction) - noise_bandwidth_array = np.append(noise_bandwidth_array, pump.pump_bandwidth) + direction = +1 if pump.propagation_direction.lower() == 'coprop' else -1 + propagation_direction = np.append(propagation_direction, direction) + noise_bandwidth_array = np.append(noise_bandwidth_array, ref_bw) # Final sorting ind = np.argsort(f_array) @@ -196,7 +195,7 @@ def _compute_power_spectrum(spectral_information, raman_pump_information): def _int_spontaneous_raman(self, z_array, raman_matrix, alphap_fiber, freq_array, cr_raman_matrix, freq_diff, ase_bc, bn_array, temperature): spontaneous_raman_scattering = OptimizeResult() - dx = self.solver_params.z_resolution + dx = self.raman_params.space_resolution h = ph.value('Planck constant') kb = ph.value('Boltzmann constant') @@ -227,42 +226,37 @@ def _int_spontaneous_raman(self, z_array, raman_matrix, alphap_fiber, freq_array return spontaneous_raman_scattering - @property - def stimulated_raman_scattering(self): - """ Return rho fiber gain/loss profile induced by stimulated Raman scattering. - :return: self._stimulated_raman_scattering: the SRS problem solution. + def stimulated_raman_scattering(self, carriers, raman_pumps=None): + """ Returns stimulated Raman scattering solution including + fiber gain/loss profile. + :return: self.__stimulated_raman_scattering: the SRS problem solution. scipy.interpolate.PPoly instance """ - if self._stimulated_raman_scattering is None: - fiber_length = self.fiber_information.length - attenuation_coefficient = self.fiber_information.attenuation_coefficient - raman_coefficient = self.fiber_information.raman_coefficient - - spectral_info = self.spectral_information - raman_pump_information = self.raman_pump_information - - z_resolution = self.solver_params.z_resolution - tolerance = self.solver_params.tolerance - verbose = self.solver_params.verbose + if self.__stimulated_raman_scattering is None: + # fiber parameters + fiber_length = self.fiber_params.length + loss_coef = self.fiber_params.loss_coef + raman_efficiency = self.fiber_params.raman_efficiency + # raman solver parameters + z_resolution = self.raman_params.space_resolution + tolerance = self.raman_params.tolerance + verbose = self.raman_params.verbose if verbose: print('Start computing fiber Stimulated Raman Scattering') - power_spectrum, freq_array, prop_direct, _ = self._compute_power_spectrum(spectral_info, raman_pump_information) + power_spectrum, freq_array, prop_direct, _ = self._compute_power_spectrum(carriers, raman_pumps) - if len(attenuation_coefficient.alpha_power) >= 2: - interp_alphap = interp1d(attenuation_coefficient.frequency, attenuation_coefficient.alpha_power) - alphap_fiber = interp_alphap(freq_array) + if not hasattr(loss_coef, 'alpha_power'): + alphap_fiber = loss_coef * np.ones(freq_array.shape) else: - alphap_fiber = attenuation_coefficient.alpha_power * np.ones(freq_array.shape) + interp_alphap = interp1d(loss_coef['frequency'], loss_coef['alpha_power']) + alphap_fiber = interp_alphap(freq_array) freq_diff = abs(freq_array - np.reshape(freq_array, (len(freq_array), 1))) - if len(raman_coefficient.cr) >= 2: - interp_cr = interp1d(raman_coefficient.frequency, raman_coefficient.cr) - cr = interp_cr(freq_diff) - else: - cr = raman_coefficient.cr * np.ones(freq_diff.shape) + interp_cr = interp1d(raman_efficiency['frequency_offset'], raman_efficiency['cr']) + cr = interp_cr(freq_diff) # z propagation axis z = np.arange(0, fiber_length+1, z_resolution) @@ -276,7 +270,6 @@ def stimulated_raman_scattering(self): rho = (stimulated_raman_scattering.y.transpose() / power_spectrum).transpose() rho = np.sqrt(rho) # From power attenuation to field attenuation - setattr(stimulated_raman_scattering, 'frequency', freq_array) setattr(stimulated_raman_scattering, 'z', stimulated_raman_scattering.x) setattr(stimulated_raman_scattering, 'rho', rho) @@ -284,9 +277,11 @@ def stimulated_raman_scattering(self): delattr(stimulated_raman_scattering, 'x') delattr(stimulated_raman_scattering, 'y') - self._stimulated_raman_scattering = stimulated_raman_scattering + self.carriers = carriers + self.raman_pumps = raman_pumps + self.__stimulated_raman_scattering = stimulated_raman_scattering - return self._stimulated_raman_scattering + return self.__stimulated_raman_scattering def _residuals_stimulated_raman(self, ya, yb, power_spectrum, prop_direct): @@ -345,112 +340,3 @@ def _ode_stimulated_raman(self, z, power_spectrum, alphap_fiber, freq_array, cr_ dpdz[f_ind][z_ind] = dpdz_element return np.vstack(dpdz) - -class NLI: - """ This class implements the NLI models. - Model and method can be specified in `self.model_parameters.method`. - List of implemented methods: - 'gn_analytic': brute force triple integral solution - 'GGN_spectrally_separated_xpm_spm': XPM plus SPM - """ - - def __init__(self, fiber_information=None): - """ Initialize the fiber object with its physical parameters - """ - self.fiber_information = fiber_information - self.srs_profile = None - self.model_parameters = None - - @property - def fiber_information(self): - return self.__fiber_information - - @fiber_information.setter - def fiber_information(self, fiber_information): - self.__fiber_information = fiber_information - - @property - def srs_profile(self): - return self.__srs_profile - - @srs_profile.setter - def srs_profile(self, srs_profile): - self.__srs_profile = srs_profile - - @property - def model_parameters(self): - return self.__model_parameters - - @model_parameters.setter - def model_parameters(self, model_params): - """ - :param model_params: namedtuple containing the parameters used to compute the NLI. - """ - self.__model_parameters = model_params - - def alpha0(self, f_eval=193.5e12): - if len(self.fiber_information.attenuation_coefficient.alpha_power) == 1: - alpha0 = self.fiber_information.attenuation_coefficient.alpha_power[0] - else: - alpha_interp = interp1d(self.fiber_information.attenuation_coefficient.frequency, - self.fiber_information.attenuation_coefficient.alpha_power) - alpha0 = alpha_interp(f_eval) - return alpha0 - - def compute_nli(self, carrier, *carriers): - """ Compute NLI power generated by the WDM comb `*carriers` on the channel under test `carrier` - at the end of the fiber span. - """ - if 'gn_model_analytic' == self.model_parameters.method.lower(): - carrier_nli = self._gn_analytic(carrier, *carriers) - else: - raise ValueError(f'Method {self.model_parameters.method_nli} not implemented.') - - return carrier_nli - - # Methods for computing spectrally separated GN - def _gn_analytic(self, carrier, *carriers): - """ Computes the nonlinear interference power on a single carrier. - The method uses eq. 120 from arXiv:1209.0394. - :param carrier: the signal under analysis - :param carriers: the full WDM comb - :return: carrier_nli: the amount of nonlinear interference in W on the under analysis - """ - - alpha = self.alpha0() / 2 - length = self.fiber_information.length - effective_length = (1 - np.exp(-2 * alpha * length)) / (2 * alpha) - asymptotic_length = 1 / (2 * alpha) - - beta2 = self.fiber_information.beta2 - gamma = self.fiber_information.gamma - - g_nli = 0 - for interfering_carrier in carriers: - g_interfearing = interfering_carrier.power.signal / interfering_carrier.baud_rate - g_signal = carrier.power.signal / carrier.baud_rate - g_nli += g_interfearing**2 * g_signal * self._psi(carrier, interfering_carrier) - g_nli *= (16.0 / 27.0) * (gamma * effective_length)**2 /\ - (2 * np.pi * abs(beta2) * asymptotic_length) - - carrier_nli = carrier.baud_rate * g_nli - return carrier_nli - - def _psi(self, carrier, interfering_carrier): - """ Calculates eq. 123 from arXiv:1209.0394. - """ - alpha = self.alpha0() / 2 - beta2 = self.fiber_information.beta2 - - asymptotic_length = 1 / (2 * alpha) - if carrier.channel_number == interfering_carrier.channel_number: # SPM - psi = np.arcsinh(0.5 * np.pi**2 * asymptotic_length - * abs(beta2) * carrier.baud_rate**2) - else: # XPM - delta_f = carrier.frequency - interfering_carrier.frequency - psi = np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * - carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate)) - psi -= np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * - carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) - - return psi From f9bd6310f182b62f9d373f4cbbba11b0af312b9d Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 11 Jun 2019 13:38:39 +0200 Subject: [PATCH 053/117] introcude NLI solver --- gnpy/core/science_utils.py | 105 +++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index e015444ac..8fdcece8a 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -340,3 +340,108 @@ def _ode_stimulated_raman(self, z, power_spectrum, alphap_fiber, freq_array, cr_ dpdz[f_ind][z_ind] = dpdz_element return np.vstack(dpdz) + +class NliSolver: + """ This class implements the NLI models. + Model and method can be specified in `self.nli_params.method`. + List of implemented methods: + 'gn_model_analytic': brute force triple integral solution + 'GGN_spectrally_separated_xpm_spm': XPM plus SPM + """ + + def __init__(self, nli_params=None, fiber_params=None): + """ Initialize the fiber object with its physical parameters + """ + self.fiber_params = fiber_params + self.nli_params = nli_params + self.srs_profile = None + + @property + def fiber_params(self): + return self.___fiber_params + + @fiber_params.setter + def fiber_params(self, fiber_params): + self.___fiber_params = fiber_params + + @property + def srs_profile(self): + return self.__srs_profile + + @srs_profile.setter + def srs_profile(self, srs_profile): + self.__srs_profile = srs_profile + + @property + def nli_params(self): + return self.__nli_params + + @nli_params.setter + def nli_params(self, nli_params): + """ + :param model_params: namedtuple containing the parameters used to compute the NLI. + """ + self.__nli_params = nli_params + + def alpha0(self, f_eval=193.5e12): + if not hasattr(self.fiber_params.loss_coef, 'alpha_power'): + alpha0 = self.fiber_params.loss_coef + else: + alpha_interp = interp1d(self.fiber_params.loss_coef['frequency'], + self.fiber_params.loss_coef['alpha_power']) + alpha0 = alpha_interp(f_eval) + return alpha0 + + def compute_nli(self, carrier, *carriers): + """ Compute NLI power generated by the WDM comb `*carriers` on the channel under test `carrier` + at the end of the fiber span. + """ + if 'gn_model_analytic' == self.nli_params.nli_method_name.lower(): + carrier_nli = self._gn_analytic(carrier, *carriers) + else: + raise ValueError(f'Method {self.nli_params.method_nli} not implemented.') + + return carrier_nli + + # Methods for computing spectrally separated GN + def _gn_analytic(self, carrier, *carriers): + """ Computes the nonlinear interference power on a single carrier. + The method uses eq. 120 from arXiv:1209.0394. + :param carrier: the signal under analysis + :param carriers: the full WDM comb + :return: carrier_nli: the amount of nonlinear interference in W on the under analysis + """ + alpha = self.alpha0() / 2 + beta2 = self.fiber_params.beta2 + gamma = self.fiber_params.gamma + length = self.fiber_params.length + effective_length = (1 - np.exp(-2 * alpha * length)) / (2 * alpha) + asymptotic_length = 1 / (2 * alpha) + + g_nli = 0 + for interfering_carrier in carriers: + g_interfearing = interfering_carrier.power.signal / interfering_carrier.baud_rate + g_signal = carrier.power.signal / carrier.baud_rate + g_nli += g_interfearing**2 * g_signal * self._psi(carrier, interfering_carrier) + g_nli *= (16.0 / 27.0) * (gamma * effective_length)**2 /\ + (2 * np.pi * abs(beta2) * asymptotic_length) + carrier_nli = carrier.baud_rate * g_nli + return carrier_nli + + def _psi(self, carrier, interfering_carrier): + """ Calculates eq. 123 from arXiv:1209.0394. + """ + alpha = self.alpha0() / 2 + beta2 = self.fiber_params.beta2 + asymptotic_length = 1 / (2 * alpha) + + if carrier.channel_number == interfering_carrier.channel_number: # SPM + psi = np.arcsinh(0.5 * np.pi**2 * asymptotic_length + * abs(beta2) * carrier.baud_rate**2) + else: # XPM + delta_f = carrier.frequency - interfering_carrier.frequency + psi = np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * + carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate)) + psi -= np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * + carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) + return psi From ff82c5171b5292e63ded75a947b8b4a7f58cb9a4 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 11 Jun 2019 13:39:41 +0200 Subject: [PATCH 054/117] propagate_raman_fiber function introduced --- gnpy/core/science_utils.py | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 8fdcece8a..fd862f7af 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -44,6 +44,62 @@ def __init__(self, params=None): self.raman_params = RamanParams(params=params['raman_parameters']) self.nli_params = NLIParams(params=params['nli_parameters']) +fib_params = namedtuple('FiberParams', 'loss_coef length beta2 gamma raman_efficiency temperature') +pump = namedtuple('RamanPump', 'power frequency propagation_direction') + +def propagate_raman_fiber(fiber, *carriers): + sim_params = fiber.sim_params + # apply input attenuation to carriers + attenuation_in = db2lin(fiber.con_in + fiber.att_in) + chan = [] + for carrier in carriers: + pwr = carrier.power + pwr = pwr._replace(signal=pwr.signal / attenuation_in, + nonlinear_interference=pwr.nli / attenuation_in, + amplified_spontaneous_emission=pwr.ase / attenuation_in) + carrier = carrier._replace(power=pwr) + chan.append(carrier) + carriers = tuple(f for f in chan) + + fiber_params = fib_params(loss_coef=2*fiber.dbkm_2_lin()[1], length=fiber.length, beta2=fiber.beta2(), + gamma=fiber.gamma, raman_efficiency=fiber.params.raman_efficiency, + temperature=fiber.operational['temperature']) + # evaluate fiber attenuation involving also SRS if required by sim_params + raman_params = fiber.sim_params.raman_params + if 'raman_pumps' in fiber.operational: + raman_pumps = tuple(pump(p['power'], p['frequency'], p['propagation_direction']) + for p in fiber.operational['raman_pumps']) + else: + raman_pumps = None + if raman_params.flag_raman: + raman_solver = RamanSolver(raman_params=raman_params, fiber_params=fiber_params) + stimulated_raman_scattering = raman_solver.stimulated_raman_scattering(carriers=carriers, + raman_pumps=raman_pumps) + fiber_attenuation = (stimulated_raman_scattering.rho[:, -1])**-2 + else: + fiber_attenuation = tuple(fiber.lin_attenuation for _ in carriers) + + # evaluate Raman ASE noise if required by sim_params and if raman pumps are present + if raman_params.flag_raman and raman_pumps: + raman_ase = raman_solver.spontaneous_raman_scattering.power[:, -1] + else: + raman_ase = tuple(0 for _ in carriers) + + # evaluate nli and propagate in fiber + attenuation_out = db2lin(fiber.con_out) + nli_params = fiber.sim_params.nli_params + nli_solver = NliSolver(nli_params=nli_params, fiber_params=fiber_params) + for carrier, attenuation, rmn_ase in zip(carriers, fiber_attenuation, raman_ase): + pwr = carrier.power + if carrier.channel_number in sim_params.list_of_channels_under_test: + carrier_nli = nli_solver.compute_nli(carrier, *carriers) + else: + carrier_nli = np.nan + pwr = pwr._replace(signal=pwr.signal/attenuation/attenuation_out, + nonlinear_interference=(pwr.nli+carrier_nli)/attenuation/attenuation_out, + amplified_spontaneous_emission=((pwr.ase/attenuation)+rmn_ase)/attenuation_out) + yield carrier._replace(power=pwr) + class RamanSolver: def __init__(self, raman_params=None, fiber_params=None): """ Initialize the fiber object with its physical parameters From 0422956ac66ae71e0f178725f583ec5b0faa4c98 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 11 Jun 2019 13:40:42 +0200 Subject: [PATCH 055/117] RamanFiber propagate method now call the propagate_raman_fiber in the science_utils module --- gnpy/core/elements.py | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index f21a27697..c654fa5d8 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -26,6 +26,7 @@ from gnpy.core.node import Node from gnpy.core.units import UNITS from gnpy.core.utils import lin2db, db2lin, itufs, itufl, snr_sum +from gnpy.core.science_utils import propagate_raman_fiber class Transceiver(Node): def __init__(self, *args, **kwargs): @@ -357,7 +358,6 @@ def _psi(self, carrier, interfering_carrier): * carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate)) psi -= arcsinh(pi**2 * self.asymptotic_length * abs(self.beta2()) * carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) - return psi def _gn_analytic(self, carrier, *carriers): @@ -418,7 +418,7 @@ def __call__(self, spectral_info): return spectral_info.update(carriers=carriers, pref=pref) RamanFiberParams = namedtuple('RamanFiberParams', 'type_variety length loss_coef length_units \ - att_in con_in con_out dispersion gamma') + att_in con_in con_out dispersion gamma raman_efficiency') class RamanFiber(Node): def __init__(self, *args, params=None, **kwargs): @@ -596,32 +596,6 @@ def _gn_analytic(self, carrier, *carriers): carrier_nli = carrier.baud_rate * g_nli return carrier_nli - def propagate(self, *carriers): - - # apply connector_att_in on all carriers before computing gn analytics premiere partie pas bonne - attenuation = db2lin(self.con_in + self.att_in) - - chan = [] - for carrier in carriers: - pwr = carrier.power - pwr = pwr._replace(signal=pwr.signal/attenuation, - nonlinear_interference=pwr.nli/attenuation, - amplified_spontaneous_emission=pwr.ase/attenuation) - carrier = carrier._replace(power=pwr) - chan.append(carrier) - - carriers = tuple(f for f in chan) - - # propagate in the fiber and apply attenuation out - attenuation = db2lin(self.con_out) - for carrier in carriers: - pwr = carrier.power - carrier_nli = self._gn_analytic(carrier, *carriers) - pwr = pwr._replace(signal=pwr.signal/self.lin_attenuation/attenuation, - nonlinear_interference=(pwr.nli+carrier_nli)/self.lin_attenuation/attenuation, - amplified_spontaneous_emission=pwr.ase/self.lin_attenuation/attenuation) - yield carrier._replace(power=pwr) - def update_pref(self, pref): self.pch_out_db = round(pref.pi - self.loss, 2) return pref._replace(p_span0=pref.p0, p_spani=self.pch_out_db) @@ -633,6 +607,9 @@ def __call__(self, spectral_info): self.carriers_out = carriers return spectral_info.update(carriers=carriers, pref=pref) + def propagate(self, *carriers): + for propagated_carrier in propagate_raman_fiber(self, *carriers): + yield propagated_carrier class EdfaParams: def __init__(self, **params): From 2a0cb8e14fa38328b1f3ce0bd30ef18bc3f4c501 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 18 Jun 2019 13:21:05 +0200 Subject: [PATCH 056/117] raman pump powers reduced in raman_edfa_example_network.json --- examples/raman_edfa_example_network.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/raman_edfa_example_network.json b/examples/raman_edfa_example_network.json index 6ead4c526..57ecbde76 100644 --- a/examples/raman_edfa_example_network.json +++ b/examples/raman_edfa_example_network.json @@ -20,12 +20,12 @@ "temperature": 283, "raman_pumps": [ { - "power": 200e-3, + "power": 100e-3, "frequency": 200e12, "propagation_direction": "counterprop" }, { - "power": 150e-3, + "power": 100e-3, "frequency": 201e12, "propagation_direction": "counterprop" } From 2da344a56397726ec73c0a743a27861a3a9af862 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 18 Jun 2019 13:27:02 +0200 Subject: [PATCH 057/117] sim_params now include GGN model --- examples/sim_params.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/sim_params.json b/examples/sim_params.json index e011f8ae6..3d244f2fd 100644 --- a/examples/sim_params.json +++ b/examples/sim_params.json @@ -1,13 +1,13 @@ { - "list_of_channels_under_test": [1, 2, 3, 4, 37, 38, 39, 74, 75], + "list_of_channels_under_test": [1, 18, 37, 56, 75], "raman_parameters": { "flag_raman": true, - "space_resolution": 20e3, - "tolerance": 1e-7, + "space_resolution": 10e3, + "tolerance": 1e-8, "verbose": 2 }, "nli_parameters": { - "nli_method_name": "gn_model_analytic", + "nli_method_name": "ggn_spectrally_separated_xpm_spm", "wdm_grid_size": 50e9, "dispersion_tolerance": 1, "phase_shift_tollerance": 0.1, From e29f8485ea7c10c3b888e08774ccd8cfd81d42d0 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 18 Jun 2019 13:31:01 +0200 Subject: [PATCH 058/117] integration of the GGN-model with spectral separation --- gnpy/core/science_utils.py | 256 +++++++++++++++++++++++++++++++++---- 1 file changed, 233 insertions(+), 23 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index fd862f7af..9013ef93e 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -35,6 +35,8 @@ def __init__(self, params=None): self.wdm_grid_size = params['wdm_grid_size'] self.dispersion_tolerance = params['dispersion_tolerance'] self.phase_shift_tollerance = params['phase_shift_tollerance'] + self.f_cut_resolution = None + self.f_pump_resolution = None self.verbose = params['verbose'] class SimParams(): @@ -44,11 +46,13 @@ def __init__(self, params=None): self.raman_params = RamanParams(params=params['raman_parameters']) self.nli_params = NLIParams(params=params['nli_parameters']) -fib_params = namedtuple('FiberParams', 'loss_coef length beta2 gamma raman_efficiency temperature') +fib_params = namedtuple('FiberParams', 'loss_coef length beta2 beta3 f_ref_beta gamma raman_efficiency temperature') pump = namedtuple('RamanPump', 'power frequency propagation_direction') def propagate_raman_fiber(fiber, *carriers): sim_params = fiber.sim_params + raman_params = fiber.sim_params.raman_params + nli_params = fiber.sim_params.nli_params # apply input attenuation to carriers attenuation_in = db2lin(fiber.con_in + fiber.att_in) chan = [] @@ -60,23 +64,26 @@ def propagate_raman_fiber(fiber, *carriers): carrier = carrier._replace(power=pwr) chan.append(carrier) carriers = tuple(f for f in chan) - - fiber_params = fib_params(loss_coef=2*fiber.dbkm_2_lin()[1], length=fiber.length, beta2=fiber.beta2(), - gamma=fiber.gamma, raman_efficiency=fiber.params.raman_efficiency, + raman_efficiency = fiber.params.raman_efficiency + if not raman_params.flag_raman: + raman_efficiency['cr'] = np.array(raman_efficiency['cr']) * 0 + fiber_params = fib_params(loss_coef=2*fiber.dbkm_2_lin()[1], length=fiber.length, gamma=fiber.gamma, + beta2=fiber.beta2(), beta3=fiber.beta3 if hasattr(fiber,'beta3') else 0, + f_ref_beta=fiber.f_ref_beta if hasattr(fiber,'f_ref_beta') else 0, + raman_efficiency=raman_efficiency, temperature=fiber.operational['temperature']) + # evaluate fiber attenuation involving also SRS if required by sim_params - raman_params = fiber.sim_params.raman_params if 'raman_pumps' in fiber.operational: raman_pumps = tuple(pump(p['power'], p['frequency'], p['propagation_direction']) for p in fiber.operational['raman_pumps']) else: raman_pumps = None - if raman_params.flag_raman: - raman_solver = RamanSolver(raman_params=raman_params, fiber_params=fiber_params) - stimulated_raman_scattering = raman_solver.stimulated_raman_scattering(carriers=carriers, - raman_pumps=raman_pumps) - fiber_attenuation = (stimulated_raman_scattering.rho[:, -1])**-2 - else: + raman_solver = RamanSolver(raman_params=raman_params, fiber_params=fiber_params) + stimulated_raman_scattering = raman_solver.stimulated_raman_scattering(carriers=carriers, + raman_pumps=raman_pumps) + fiber_attenuation = (stimulated_raman_scattering.rho[:, -1])**-2 + if not raman_params.flag_raman: fiber_attenuation = tuple(fiber.lin_attenuation for _ in carriers) # evaluate Raman ASE noise if required by sim_params and if raman pumps are present @@ -87,9 +94,13 @@ def propagate_raman_fiber(fiber, *carriers): # evaluate nli and propagate in fiber attenuation_out = db2lin(fiber.con_out) - nli_params = fiber.sim_params.nli_params nli_solver = NliSolver(nli_params=nli_params, fiber_params=fiber_params) + nli_solver.stimulated_raman_scattering = stimulated_raman_scattering for carrier, attenuation, rmn_ase in zip(carriers, fiber_attenuation, raman_ase): + resolution_param = frequency_resolution(carrier, carriers, sim_params, fiber) + f_cut_resolution, f_pump_resolution, _, _ = resolution_param + nli_params.f_cut_resolution = f_cut_resolution + nli_params.f_pump_resolution = f_pump_resolution pwr = carrier.power if carrier.channel_number in sim_params.list_of_channels_under_test: carrier_nli = nli_solver.compute_nli(carrier, *carriers) @@ -100,6 +111,64 @@ def propagate_raman_fiber(fiber, *carriers): amplified_spontaneous_emission=((pwr.ase/attenuation)+rmn_ase)/attenuation_out) yield carrier._replace(power=pwr) +def frequency_resolution(carrier, carriers, sim_params, fiber): + grid_size = sim_params.nli_params.wdm_grid_size + alpha_ef = fiber.dbkm_2_lin()[1] + delta_z = sim_params.raman_params.space_resolution + beta2 = fiber.beta2() + k_tol = sim_params.nli_params.dispersion_tolerance + phi_tol = sim_params.nli_params.phase_shift_tollerance + f_pump_resolution, method_f_pump, res_dict_pump = \ + _get_freq_res_k_phi(0, grid_size, alpha_ef, delta_z, beta2, k_tol, phi_tol) + f_cut_resolution = {} + method_f_cut = {} + res_dict_cut = {} + for cut_carrier in carriers: + delta_number = cut_carrier.channel_number - carrier.channel_number + delta_count = abs(delta_number) + f_res, method, res_dict = \ + _get_freq_res_k_phi(delta_count, grid_size, alpha_ef, delta_z, beta2, k_tol, phi_tol) + f_cut_resolution[f'delta_{delta_number}'] = f_res + method_f_cut[delta_number] = method + res_dict_cut[delta_number] = res_dict + return [f_cut_resolution, f_pump_resolution, (method_f_cut, method_f_pump), (res_dict_cut, res_dict_pump)] + +def _get_freq_res_k_phi(delta_count, grid_size, alpha_ef, delta_z, beta2, k_tol, phi_tol): + res_phi = _get_freq_res_phase_rotation(delta_count, grid_size, delta_z, beta2, phi_tol) + res_k = _get_freq_res_dispersion_attenuation(delta_count, grid_size, alpha_ef, beta2, k_tol) + res_dict = {'res_phi': res_phi, 'res_k': res_k} + method = min(res_dict, key=res_dict.get) + return res_dict[method], method, res_dict + +def _get_freq_res_dispersion_attenuation(delta_count, grid_size, alpha_ef, beta2, k_tol): + return k_tol * 2 * abs(alpha_ef) / abs(beta2) / (1 + delta_count) / (4*np.pi**2 * grid_size) + +def _get_freq_res_phase_rotation(delta_count, grid_size, delta_z, beta2, phi_tol): + return phi_tol / abs(beta2) / (1 + delta_count) / delta_z / (4*np.pi**2 * grid_size) + +def raised_cosine_comb(f, *carriers): + """ Returns an array storing the PSD of a WDM comb of raised cosine shaped + channels at the input frequencies defined in array f + :param f: numpy array of frequencies in Hz + :param carriers: namedtuple describing the WDM comb + :return: PSD of the WDM comb evaluated over f + """ + psd = np.zeros(np.shape(f)) + for carrier in carriers: + f_nch = carrier.frequency + g_ch = carrier.power.signal / carrier.baud_rate + ts = 1 / carrier.baud_rate + passband = (1 - carrier.roll_off) / (2 / carrier.baud_rate) + stopband = (1 + carrier.roll_off) / (2 / carrier.baud_rate) + ff = np.abs(f - f_nch) + tf = ff - passband + if carrier.roll_off == 0: + psd = np.where(tf <= 0, g_ch, 0.) + psd + else: + psd = g_ch * (np.where(tf <= 0, 1., 0.) + 1 / 2 * (1 + np.cos(np.pi * ts / carrier.roll_off * tf)) * + np.where(tf > 0, 1., 0.) * np.where(np.abs(ff) <= stopband, 1., 0.)) + psd + return psd + class RamanSolver: def __init__(self, raman_params=None, fiber_params=None): """ Initialize the fiber object with its physical parameters @@ -168,15 +237,12 @@ def spontaneous_raman_scattering(self): loss_coef = self.fiber_params.loss_coef raman_efficiency = self.fiber_params.raman_efficiency temperature = self.fiber_params.temperature - carriers = self.carriers raman_pumps = self.raman_pumps - verbose = self.raman_params.verbose if verbose: print('Start computing fiber Spontaneous Raman Scattering') - power_spectrum, freq_array, prop_direct, bn_array = self._compute_power_spectrum(carriers, raman_pumps) if not hasattr(loss_coef, 'alpha_power'): @@ -402,7 +468,7 @@ class NliSolver: Model and method can be specified in `self.nli_params.method`. List of implemented methods: 'gn_model_analytic': brute force triple integral solution - 'GGN_spectrally_separated_xpm_spm': XPM plus SPM + 'ggn_spectrally_separated_xpm_spm': XPM plus SPM """ def __init__(self, nli_params=None, fiber_params=None): @@ -410,7 +476,7 @@ def __init__(self, nli_params=None, fiber_params=None): """ self.fiber_params = fiber_params self.nli_params = nli_params - self.srs_profile = None + self.stimulated_raman_scattering = None @property def fiber_params(self): @@ -421,12 +487,12 @@ def fiber_params(self, fiber_params): self.___fiber_params = fiber_params @property - def srs_profile(self): - return self.__srs_profile + def stimulated_raman_scattering(self): + return self.__stimulated_raman_scattering - @srs_profile.setter - def srs_profile(self, srs_profile): - self.__srs_profile = srs_profile + @stimulated_raman_scattering.setter + def stimulated_raman_scattering(self, stimulated_raman_scattering): + self.__stimulated_raman_scattering = stimulated_raman_scattering @property def nli_params(self): @@ -454,12 +520,61 @@ def compute_nli(self, carrier, *carriers): """ if 'gn_model_analytic' == self.nli_params.nli_method_name.lower(): carrier_nli = self._gn_analytic(carrier, *carriers) + elif 'ggn_spectrally_separated' in self.nli_params.nli_method_name.lower(): + eta_matrix = self._compute_eta_matrix(carrier, *carriers) + carrier_nli = self._carrier_nli_from_eta_matrix(eta_matrix, carrier, *carriers) else: raise ValueError(f'Method {self.nli_params.method_nli} not implemented.') return carrier_nli - # Methods for computing spectrally separated GN + @staticmethod + def _carrier_nli_from_eta_matrix(eta_matrix, carrier, *carriers): + carrier_nli = 0 + for pump_carrier_1 in carriers: + for pump_carrier_2 in carriers: + carrier_nli += eta_matrix[pump_carrier_1.channel_number-1, pump_carrier_2.channel_number-1] * \ + pump_carrier_1.power.signal * pump_carrier_2.power.signal + carrier_nli *= carrier.power.signal + + return carrier_nli + + def _compute_eta_matrix(self, carrier_cut, *carriers): + cut_index = carrier_cut.channel_number - 1 + # Matrix initialization + matrix_size = max(carriers, key=lambda x: getattr(x, 'channel_number')).channel_number + eta_matrix = np.zeros(shape=(matrix_size, matrix_size)) + + # SPM + if 'spm' in self.nli_params.nli_method_name.lower(): + if self.nli_params.verbose: + print(f'Start computing SPM on channel #{carrier_cut.channel_number}') + # SPM GGN + if 'ggn' in self.nli_params.nli_method_name.lower(): + partial_nli = self._generalized_spectrally_separated_spm(carrier_cut) + # SPM GN + elif 'gn' in self.nli_params.nli_method_name.lower(): + partial_nli = self._gn_analytic(carrier_cut, *[carrier_cut]) + eta_matrix[cut_index, cut_index] = partial_nli / (carrier_cut.power.signal**3) + + # XPM + if 'xpm' in self.nli_params.nli_method_name.lower(): + for pump_carrier in carriers: + pump_index = pump_carrier.channel_number - 1 + if not (cut_index == pump_index): + if self.nli_params.verbose: + print(f'Start computing XPM on channel #{carrier_cut.channel_number} ' + f'from channel #{pump_carrier.channel_number}') + # spectrally separated GGN + if 'ggn' in self.nli_params.nli_method_name.lower(): + partial_nli = self._generalized_spectrally_separated_xpm(carrier_cut, pump_carrier) + elif 'gn' in self.nli_params.nli_method_name.lower(): + partial_nli = self._gn_analytic(carrier_cut, *[pump_carrier]) + eta_matrix[pump_index, pump_index] = partial_nli /\ + (carrier_cut.power.signal * pump_carrier.power.signal**2) + return eta_matrix + + # Methods for computing GN-model def _gn_analytic(self, carrier, *carriers): """ Computes the nonlinear interference power on a single carrier. The method uses eq. 120 from arXiv:1209.0394. @@ -501,3 +616,98 @@ def _psi(self, carrier, interfering_carrier): psi -= np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) return psi + + # Methods for computing the GGN + def _generalized_spectrally_separated_spm(self, carrier): + f_cut_resolution = self.nli_params.f_cut_resolution['delta_0'] + f_eval = carrier.frequency + g_cut = (carrier.power.signal / carrier.baud_rate) + + + partial_nli = carrier.baud_rate * (16.0 / 27.0) * self.fiber_params.gamma**2 * g_cut**3 * \ + self._generalized_psi(carrier, carrier, f_eval, f_cut_resolution, f_cut_resolution) + + return partial_nli + + def _generalized_spectrally_separated_xpm(self, carrier_cut, pump_carrier): + delta_index = pump_carrier.channel_number - carrier_cut.channel_number + f_cut_resolution = self.nli_params.f_cut_resolution[f'delta_{delta_index}'] + f_pump_resolution = self.nli_params.f_pump_resolution + f_eval = carrier_cut.frequency + g_pump = (pump_carrier.power.signal / pump_carrier.baud_rate) + g_cut = (carrier_cut.power.signal / carrier_cut.baud_rate) + partial_nli = carrier_cut.baud_rate * (16.0 / 27.0) * self.fiber_params.gamma**2 * \ + g_pump**2 * g_cut * \ + 2 * self._generalized_psi(carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution) + + return partial_nli + + def _generalized_gnli(self, carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution): + delta_index = pump_carrier.channel_number - carrier_cut.channel_number + f_cut_resolution = self.nli_params.f_cut_resolution[f'delta_{delta_index}'] + f_pump_resolution = self.nli_params.f_pump_resolution + + g_pump = (pump_carrier.power.signal / pump_carrier.baud_rate) + g_cut = (carrier_cut.power.signal / carrier_cut.baud_rate) + + chi = (16.0 / 27.0) + + partial_nli = chi * self.fiber_params.gamma**2 * \ + g_pump**2 * g_cut * \ + 2 * self._generalized_psi(carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution) + + def _generalized_psi(self, carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution): + """ It computes the generalized psi function similarly to the one used in the GN model + :return: generalized_psi + """ + # Fiber parameters + alpha0 = self.alpha0(f_eval) + beta2 = self.fiber_params.beta2 + beta3 = self.fiber_params.beta3 + f_ref_beta = self.fiber_params.f_ref_beta + z = self.stimulated_raman_scattering.z + frequency_rho = self.stimulated_raman_scattering.frequency + rho = self.stimulated_raman_scattering.rho + rho = rho * np.exp(np.abs(alpha0) * z / 2) + if len(frequency_rho) == 1: + rho_function = lambda f: rho[0, :] + else: + rho_function = interp1d(frequency_rho, rho, axis=0, fill_value='extrapolate') + rho_pump = rho_function(pump_carrier.frequency) + + f1_array = np.arange(pump_carrier.frequency - (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2), + pump_carrier.frequency + (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2), + f_pump_resolution) + f2_array = np.arange(carrier_cut.frequency - (carrier_cut.baud_rate * (1 + carrier_cut.roll_off) / 2), + carrier_cut.frequency + (carrier_cut.baud_rate * (1 + carrier_cut.roll_off) / 2), + f_cut_resolution) + psd1 = raised_cosine_comb(f1_array, pump_carrier) * (pump_carrier.baud_rate / pump_carrier.power.signal) + + integrand_f1 = np.zeros(len(f1_array)) + for f1_index, (f1, psd1_sample) in enumerate(zip(f1_array, psd1)): + f3_array = f1 + f2_array - f_eval + psd2 = raised_cosine_comb(f2_array, carrier_cut) * (carrier_cut.baud_rate / carrier_cut.power.signal) + psd3 = raised_cosine_comb(f3_array, pump_carrier) * (pump_carrier.baud_rate / pump_carrier.power.signal) + ggg = psd1_sample * psd2 * psd3 + + delta_beta = 4 * np.pi**2 * (f1 - f_eval) * (f2_array - f_eval) * \ + (beta2 + np.pi * beta3 * (f1 + f2_array - 2 * f_ref_beta)) + + integrand_f2 = ggg * self._generalized_rho_nli(delta_beta, rho_pump, z, alpha0) + integrand_f1[f1_index] = np.trapz(integrand_f2, f2_array) + generalized_psi = np.trapz(integrand_f1, f1_array) + return generalized_psi + + @staticmethod + def _generalized_rho_nli(delta_beta, rho_pump, z, alpha0): + w = 1j * delta_beta - alpha0 + + generalized_rho_nli = (rho_pump[-1]**2 * np.exp(w * z[-1]) - rho_pump[0]**2 * np.exp(w * z[0])) / w + for z_ind in range(0, len(z) - 1): + derivative_rho = (rho_pump[z_ind + 1]**2 - rho_pump[z_ind]**2) / (z[z_ind + 1] - z[z_ind]) + generalized_rho_nli -= derivative_rho * (np.exp(w * z[z_ind + 1]) - np.exp(w * z[z_ind])) / (w**2) + + generalized_rho_nli = np.abs(generalized_rho_nli)**2 + + return generalized_rho_nli + From 4da7f0cc3827b4607a02d4ad43ea748f5fa098c6 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Tue, 18 Jun 2019 13:31:50 +0200 Subject: [PATCH 059/117] added evaluation of the SNR at the end of the line --- examples/transmission_with_raman_main_example.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index 956b31f39..7e7b8a46c 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -7,18 +7,19 @@ Main example for transmission simulation. -Reads from network JSON (by default, `edfa_example_network.json`) +Reads from network JSON (by default, `raman_edfa_example_network.json`) ''' from gnpy.core.equipment import load_equipment, trx_mode_params from gnpy.core.utils import db2lin, lin2db, write_csv from argparse import ArgumentParser from sys import exit +import time from pathlib import Path from json import loads from collections import Counter from logging import getLogger, basicConfig, INFO, ERROR, DEBUG -from numpy import linspace, mean +from numpy import linspace, mean, log10, array from matplotlib.pyplot import show, axis, figure, title, text from networkx import (draw_networkx_nodes, draw_networkx_edges, draw_networkx_labels, dijkstra_path) @@ -272,8 +273,16 @@ def main(network, equipment, source, destination, sim_params, req = None): trx_params['power'] = db2lin(float(args.power))*1e-3 params.update(trx_params) req = Path_request(**params) + start_time = time.time() path, infos = main(network, equipment, source, destination, sim_params, req) save_network(args.filename, network) + final_signal_power = array([carrier.power.signal for carrier in infos[path[-1]][1].carriers]) + final_ase = array([carrier.power.ase for carrier in infos[path[-1]][1].carriers]) + final_nli = array([carrier.power.nli for carrier in infos[path[-1]][1].carriers]) + print(f'\n Computed after {time.time()-start_time} seconds. \n') + + print('The total SNR per channel is:') + print(10*log10(final_signal_power/(final_nli+final_ase))) if not args.source: print(f'\n(No source node specified: picked {source.uid})') From 8f705e61736bad5f000b0f7e91f33d965a1cfee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Fri, 21 Jun 2019 11:27:07 +0200 Subject: [PATCH 060/117] requirements: only list what we really need Listing all transitive dependencies, including their respective versions (as done by `pip freeze`, for example) is something which effectively leads to installing outdated and possibly vulnerable software. Let's get rid of the transitive dependencies. Thanks to Esther for noticing. --- requirements.txt | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/requirements.txt b/requirements.txt index e33ea915b..bc9b86f41 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,44 +1,10 @@ alabaster==0.7.12 -appdirs==1.4.3 -atomicwrites==1.2.1 -attrs==18.2.0 -Babel==2.6.0 -black==18.9b0 -certifi==2018.10.15 -chardet==3.0.4 -Click==7.0 -cycler==0.10.0 -decorator==4.3.0 -docutils==0.14 -idna==2.7 -imagesize==1.1.0 -Jinja2==2.10 -kiwisolver==1.0.1 -latexcodec==1.0.5 -MarkupSafe==1.0 matplotlib==3.0.0 -more-itertools==4.3.0 networkx==2.3 numpy==1.15.2 -oset==0.1.3 -packaging==18.0 -pluggy==0.7.1 -py==1.7.0 -pybtex==0.21 -pybtex-docutils==0.2.1 Pygments==2.2.0 -pyparsing==2.2.2 pytest==3.8.2 -python-dateutil==2.7.3 -pytz==2018.5 -PyYAML==3.13 -requests==2.19.1 scipy==1.1.0 -six==1.11.0 -snowballstemmer==1.2.1 Sphinx==1.8.1 sphinxcontrib-bibtex==0.4.0 -sphinxcontrib-websupport==1.1.0 -toml==0.10.0 -urllib3==1.23 xlrd==1.2.0 From 89fb2e047b810ab9095d7f383d64c39221bfd54f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Fri, 21 Jun 2019 11:50:17 +0200 Subject: [PATCH 061/117] Update dependencies Pinning dependencies to a specific version is safe in terms of preventing accidental breakage, but it has a cost of possibly using outdated dependencies. Let's see if we can rely on the semantic versioning of our dependencies here. I'm doing this instead of other approaches (such as splitting the top-level deps from "the rest of the dep chain" [1][2]) because I require additional tools in my venv, such as the python-lnaguage-server. There would be little point in injecting these into requirements to be used by other people. [1] https://www.kennethreitz.org/essays/a-better-pip-workflow [2] https://github.com/jktjkt/oopt-gnpy/commit/59eb51c0268efe74d7cbaeb8fff86b3226f82a99 --- requirements.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index bc9b86f41..3a717dee6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ -alabaster==0.7.12 -matplotlib==3.0.0 -networkx==2.3 -numpy==1.15.2 -Pygments==2.2.0 -pytest==3.8.2 -scipy==1.1.0 -Sphinx==1.8.1 -sphinxcontrib-bibtex==0.4.0 -xlrd==1.2.0 +alabaster>=0.7.12,<1 +matplotlib>=3.1.0,<4 +networkx>=2.3,<3 +numpy>=1.16.1,<2 +Pygments>=2.4.2,<3 +pytest>=4.0.0,<5 +scipy>=1.3.0,<2 +Sphinx>=2.1.1,<3 +sphinxcontrib-bibtex>=0.4.2,<1 +xlrd>=1.2.0,<2 From a6ab8055b1a3c6b3f9f3bfd6d9d2fb196dcafc47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 4 Jul 2019 00:27:10 +0200 Subject: [PATCH 062/117] CI: do not attempt to `cd` twice I misunderstood how Travis CI works; it's not a subshell, it's something which affects the other commands. Let's just run everything from the top-level directory. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3504c4a8c..d64d30a9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ install: script: - pytest --cov-report=xml --cov=gnpy - rstcheck --ignore-roles cite --ignore-directives automodule --recursive --ignore-messages '(Duplicate explicit target name.*)' . - - cd examples; ./transmission_main_example.py - - cd examples; ./path_requests_run.py + - ./examples/transmission_main_example.py + - ./examples/path_requests_run.py after_success: - bash <(curl -s https://codecov.io/bash) From 93986f36c35460202bb8b8a29ed9c8ae2d9e7b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 4 Jul 2019 00:13:26 +0200 Subject: [PATCH 063/117] docs: Fix docs building Docs building started failing after our dependency update. The reason is that the updated sphinx bibtex extension started being a bit stricter in their interpretation of bibliography files: parsing bibtex file /home/jkt/work/TIP/oopt-gnpy/docs/biblio.bib... Exception occurred: File "/home/jkt/work/TIP/_py/lib64/python3.6/site-packages/pybtex/errors.py", line 78, in report_error raise exception pybtex.database.input.bibtex.DuplicatePersonField: entry with key bononi_modeling_2012 has a duplicate year field Fix this by using `date` as field name when a full date is given. --- docs/biblio.bib | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/biblio.bib b/docs/biblio.bib index bcfc3a1a2..74446cf18 100644 --- a/docs/biblio.bib +++ b/docs/biblio.bib @@ -874,7 +874,7 @@ @article{bononi_modeling_2012 number = {7}, journal = {Optics Express}, urlyear = {2017-11-14}, - year = {2012-03-26}, + date = {2012-03-26}, year = {2012}, pages = {7777}, author = {Bononi, A. and Serena, P. and Rossi, N. and Grellier, E. and Vacondio, F.} @@ -1114,7 +1114,7 @@ @article{bononi_single-_2013 number = {26}, journal = {Optics Express}, urlyear = {2017-11-16}, - year = {2013-12-30}, + date = {2013-12-30}, year = {2013}, pages = {32254}, author = {Bononi, Alberto and Beucher, Ottmar and Serena, Paolo} From 27dcd290743b417e13a154404b893edb5826988f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 4 Jul 2019 00:15:55 +0200 Subject: [PATCH 064/117] CI: Attempt to builds docs during regular CI, too Since ReadTheDocs does not support [1] running as a CI check on GitHub yet, let's make sure that we at least run sphinx. This would have caught a recent docs breakage (see parent commit). [1] https://github.com/readthedocs/readthedocs.org/issues/1340 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d64d30a9c..df34e4a93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,5 +13,6 @@ script: - rstcheck --ignore-roles cite --ignore-directives automodule --recursive --ignore-messages '(Duplicate explicit target name.*)' . - ./examples/transmission_main_example.py - ./examples/path_requests_run.py + - sphinx-build docs/ x-throwaway-location after_success: - bash <(curl -s https://codecov.io/bash) From 1a1346461be28f37538c5dbbf500b797add3b30e Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Mon, 29 Jul 2019 12:33:05 +0200 Subject: [PATCH 065/117] self.pch_out_db in RamanFiber now takes into account Raman gain --- gnpy/core/elements.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index c654fa5d8..6efb8a68f 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -596,14 +596,15 @@ def _gn_analytic(self, carrier, *carriers): carrier_nli = carrier.baud_rate * g_nli return carrier_nli - def update_pref(self, pref): - self.pch_out_db = round(pref.pi - self.loss, 2) + def update_pref(self, pref, *carriers): + pch_out_db = 10*log10(mean([carrier.power.signal for carrier in carriers])) + 30 + self.pch_out_db = round(pch_out_db, 2) return pref._replace(p_span0=pref.p0, p_spani=self.pch_out_db) def __call__(self, spectral_info): self.carriers_in = spectral_info.carriers carriers = tuple(self.propagate(*spectral_info.carriers)) - pref = self.update_pref(spectral_info.pref) + pref = self.update_pref(spectral_info.pref, *carriers) self.carriers_out = carriers return spectral_info.update(carriers=carriers, pref=pref) From 2f9385451fb63ac93c14b5049991b2add6483004 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Mon, 29 Jul 2019 14:16:49 +0200 Subject: [PATCH 066/117] removed old printing of spectral information --- examples/transmission_with_raman_main_example.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index 7e7b8a46c..849da1346 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -276,9 +276,6 @@ def main(network, equipment, source, destination, sim_params, req = None): start_time = time.time() path, infos = main(network, equipment, source, destination, sim_params, req) save_network(args.filename, network) - final_signal_power = array([carrier.power.signal for carrier in infos[path[-1]][1].carriers]) - final_ase = array([carrier.power.ase for carrier in infos[path[-1]][1].carriers]) - final_nli = array([carrier.power.nli for carrier in infos[path[-1]][1].carriers]) print(f'\n Computed after {time.time()-start_time} seconds. \n') print('The total SNR per channel is:') From ecb8bd9fbe3fc0388636101fb45c0f16805da3bd Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Mon, 29 Jul 2019 14:17:27 +0200 Subject: [PATCH 067/117] new print of final propagation --- examples/transmission_with_raman_main_example.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index 849da1346..98c3f91c3 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -19,7 +19,7 @@ from json import loads from collections import Counter from logging import getLogger, basicConfig, INFO, ERROR, DEBUG -from numpy import linspace, mean, log10, array +from numpy import linspace, mean, log10, array, isnan from matplotlib.pyplot import show, axis, figure, title, text from networkx import (draw_networkx_nodes, draw_networkx_edges, draw_networkx_labels, dijkstra_path) @@ -278,8 +278,18 @@ def main(network, equipment, source, destination, sim_params, req = None): save_network(args.filename, network) print(f'\n Computed after {time.time()-start_time} seconds. \n') + final_carriers = infos[path[-1]][1].carriers + print('The total SNR per channel is:') - print(10*log10(final_signal_power/(final_nli+final_ase))) + print('Ch. # \t Channel frequency (THz) \t' + ' SNR total (signal bw, dB)') + for final_carrier in final_carriers: + ch_freq = final_carrier.frequency * 1e-12 + ch_power = 10 * log10(final_carrier.power.signal) + 30 + ch_snr = ch_power - (10 * log10(final_carrier.power.nli + final_carrier.power.ase) + 30) + + if not isnan(ch_snr): + print(f'{final_carrier.num_chan} \t\t {round(ch_freq, 2):.2f} \t\t\t {round(ch_snr, 2)}') if not args.source: print(f'\n(No source node specified: picked {source.uid})') From 5b6d58ac7d2bd3e79b40860717a9eadd0ff6acfa Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Mon, 29 Jul 2019 14:45:32 +0200 Subject: [PATCH 068/117] minor fix to printed text --- examples/transmission_with_raman_main_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index 98c3f91c3..3812d2468 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -280,7 +280,7 @@ def main(network, equipment, source, destination, sim_params, req = None): final_carriers = infos[path[-1]][1].carriers - print('The total SNR per channel is:') + print('The total SNR per channel at the end of the line is:') print('Ch. # \t Channel frequency (THz) \t' ' SNR total (signal bw, dB)') for final_carrier in final_carriers: From f4db56ca2979c3388e5855881b28cbe2f70269ff Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Wed, 31 Jul 2019 11:18:46 +0200 Subject: [PATCH 069/117] minor fix to comments --- gnpy/core/science_utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 9013ef93e..90b315e4a 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -580,7 +580,7 @@ def _gn_analytic(self, carrier, *carriers): The method uses eq. 120 from arXiv:1209.0394. :param carrier: the signal under analysis :param carriers: the full WDM comb - :return: carrier_nli: the amount of nonlinear interference in W on the under analysis + :return: carrier_nli: the amount of nonlinear interference in W on the carrier under analysis """ alpha = self.alpha0() / 2 beta2 = self.fiber_params.beta2 @@ -607,8 +607,7 @@ def _psi(self, carrier, interfering_carrier): asymptotic_length = 1 / (2 * alpha) if carrier.channel_number == interfering_carrier.channel_number: # SPM - psi = np.arcsinh(0.5 * np.pi**2 * asymptotic_length - * abs(beta2) * carrier.baud_rate**2) + psi = np.arcsinh(0.5 * np.pi**2 * asymptotic_length * abs(beta2) * carrier.baud_rate**2) else: # XPM delta_f = carrier.frequency - interfering_carrier.frequency psi = np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * @@ -617,7 +616,7 @@ def _psi(self, carrier, interfering_carrier): carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) return psi - # Methods for computing the GGN + # Methods for computing the GGN-model def _generalized_spectrally_separated_spm(self, carrier): f_cut_resolution = self.nli_params.f_cut_resolution['delta_0'] f_eval = carrier.frequency From 2c20fd3f9f95a0ac3c04f5d3b2fd8489b9a5e877 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Wed, 31 Jul 2019 11:20:34 +0200 Subject: [PATCH 070/117] strings 'XPM' and 'SPM' removed while computing NLI, now it is implicit --- gnpy/core/science_utils.py | 47 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 90b315e4a..3cecb9349 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -546,32 +546,31 @@ def _compute_eta_matrix(self, carrier_cut, *carriers): eta_matrix = np.zeros(shape=(matrix_size, matrix_size)) # SPM - if 'spm' in self.nli_params.nli_method_name.lower(): - if self.nli_params.verbose: - print(f'Start computing SPM on channel #{carrier_cut.channel_number}') - # SPM GGN - if 'ggn' in self.nli_params.nli_method_name.lower(): - partial_nli = self._generalized_spectrally_separated_spm(carrier_cut) - # SPM GN - elif 'gn' in self.nli_params.nli_method_name.lower(): - partial_nli = self._gn_analytic(carrier_cut, *[carrier_cut]) - eta_matrix[cut_index, cut_index] = partial_nli / (carrier_cut.power.signal**3) + if self.nli_params.verbose: + print(f'Start computing SPM on channel #{carrier_cut.channel_number}') + # SPM GGN + if 'ggn' in self.nli_params.nli_method_name.lower(): + partial_nli = self._generalized_spectrally_separated_spm(carrier_cut) + # SPM GN + elif 'gn' in self.nli_params.nli_method_name.lower(): + partial_nli = self._gn_analytic(carrier_cut, *[carrier_cut]) + eta_matrix[cut_index, cut_index] = partial_nli / (carrier_cut.power.signal**3) # XPM - if 'xpm' in self.nli_params.nli_method_name.lower(): - for pump_carrier in carriers: - pump_index = pump_carrier.channel_number - 1 - if not (cut_index == pump_index): - if self.nli_params.verbose: - print(f'Start computing XPM on channel #{carrier_cut.channel_number} ' - f'from channel #{pump_carrier.channel_number}') - # spectrally separated GGN - if 'ggn' in self.nli_params.nli_method_name.lower(): - partial_nli = self._generalized_spectrally_separated_xpm(carrier_cut, pump_carrier) - elif 'gn' in self.nli_params.nli_method_name.lower(): - partial_nli = self._gn_analytic(carrier_cut, *[pump_carrier]) - eta_matrix[pump_index, pump_index] = partial_nli /\ - (carrier_cut.power.signal * pump_carrier.power.signal**2) + for pump_carrier in carriers: + pump_index = pump_carrier.channel_number - 1 + if not (cut_index == pump_index): + if self.nli_params.verbose: + print(f'Start computing XPM on channel #{carrier_cut.channel_number} ' + f'from channel #{pump_carrier.channel_number}') + # XPM GGN + if 'ggn' in self.nli_params.nli_method_name.lower(): + partial_nli = self._generalized_spectrally_separated_xpm(carrier_cut, pump_carrier) + # XPM GGN + elif 'gn' in self.nli_params.nli_method_name.lower(): + partial_nli = self._gn_analytic(carrier_cut, *[pump_carrier]) + eta_matrix[pump_index, pump_index] = partial_nli /\ + (carrier_cut.power.signal * pump_carrier.power.signal**2) return eta_matrix # Methods for computing GN-model From c0b84e84c8338efb51a9e6eabae86731a7b8bfee Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Wed, 31 Jul 2019 11:24:16 +0200 Subject: [PATCH 071/117] double underscore replaced with single one for some attributes --- gnpy/core/science_utils.py | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 3cecb9349..3502e2071 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -181,22 +181,22 @@ def __init__(self, raman_params=None, fiber_params=None): """ self.fiber_params = fiber_params self.raman_params = raman_params - self.__carriers = None - self.__stimulated_raman_scattering = None - self.__spontaneous_raman_scattering = None + self._carriers = None + self._stimulated_raman_scattering = None + self._spontaneous_raman_scattering = None @property def fiber_params(self): - return self.__fiber_params + return self._fiber_params @fiber_params.setter def fiber_params(self, fiber_params): - self.__stimulated_raman_scattering = None - self.__fiber_params = fiber_params + self._stimulated_raman_scattering = None + self._fiber_params = fiber_params @property def carriers(self): - return self.__carriers + return self._carriers @carriers.setter def carriers(self, carriers): @@ -204,8 +204,8 @@ def carriers(self, carriers): :param carriers: tuple of namedtuples containing information about carriers :return: """ - self.__carriers = carriers - self.__stimulated_raman_scattering = None + self._carriers = carriers + self._stimulated_raman_scattering = None @property def raman_pumps(self): @@ -214,11 +214,11 @@ def raman_pumps(self): @raman_pumps.setter def raman_pumps(self, raman_pumps): self._raman_pumps = raman_pumps - self.__stimulated_raman_scattering = None + self._stimulated_raman_scattering = None @property def raman_params(self): - return self.__raman_params + return self._raman_params @raman_params.setter def raman_params(self, raman_params): @@ -226,13 +226,13 @@ def raman_params(self, raman_params): :param raman_params: namedtuple containing the solver parameters (optional). :return: """ - self.__raman_params = raman_params - self.__stimulated_raman_scattering = None - self.__spontaneous_raman_scattering = None + self._raman_params = raman_params + self._stimulated_raman_scattering = None + self._spontaneous_raman_scattering = None @property def spontaneous_raman_scattering(self): - if self.__spontaneous_raman_scattering is None: + if self._spontaneous_raman_scattering is None: # SET STUFF loss_coef = self.fiber_params.loss_coef raman_efficiency = self.fiber_params.raman_efficiency @@ -256,11 +256,11 @@ def spontaneous_raman_scattering(self): cr = interp_cr(freq_diff) # z propagation axis - z_array = self.__stimulated_raman_scattering.z + z_array = self._stimulated_raman_scattering.z ase_bc = np.zeros(freq_array.shape) # calculate ase power - spontaneous_raman_scattering = self._int_spontaneous_raman(z_array, self.__stimulated_raman_scattering.power, + spontaneous_raman_scattering = self._int_spontaneous_raman(z_array, self._stimulated_raman_scattering.power, alphap_fiber, freq_array, cr, freq_diff, ase_bc, bn_array, temperature) @@ -272,9 +272,9 @@ def spontaneous_raman_scattering(self): if verbose: print(spontaneous_raman_scattering.message) - self.__spontaneous_raman_scattering = spontaneous_raman_scattering + self._spontaneous_raman_scattering = spontaneous_raman_scattering - return self.__spontaneous_raman_scattering + return self._spontaneous_raman_scattering @staticmethod def _compute_power_spectrum(carriers, raman_pumps=None): @@ -351,11 +351,11 @@ def _int_spontaneous_raman(self, z_array, raman_matrix, alphap_fiber, freq_array def stimulated_raman_scattering(self, carriers, raman_pumps=None): """ Returns stimulated Raman scattering solution including fiber gain/loss profile. - :return: self.__stimulated_raman_scattering: the SRS problem solution. + :return: self._stimulated_raman_scattering: the SRS problem solution. scipy.interpolate.PPoly instance """ - if self.__stimulated_raman_scattering is None: + if self._stimulated_raman_scattering is None: # fiber parameters fiber_length = self.fiber_params.length loss_coef = self.fiber_params.loss_coef @@ -401,9 +401,9 @@ def stimulated_raman_scattering(self, carriers, raman_pumps=None): self.carriers = carriers self.raman_pumps = raman_pumps - self.__stimulated_raman_scattering = stimulated_raman_scattering + self._stimulated_raman_scattering = stimulated_raman_scattering - return self.__stimulated_raman_scattering + return self._stimulated_raman_scattering def _residuals_stimulated_raman(self, ya, yb, power_spectrum, prop_direct): @@ -480,30 +480,30 @@ def __init__(self, nli_params=None, fiber_params=None): @property def fiber_params(self): - return self.___fiber_params + return self._fiber_params @fiber_params.setter def fiber_params(self, fiber_params): - self.___fiber_params = fiber_params + self._fiber_params = fiber_params @property def stimulated_raman_scattering(self): - return self.__stimulated_raman_scattering + return self._stimulated_raman_scattering @stimulated_raman_scattering.setter def stimulated_raman_scattering(self, stimulated_raman_scattering): - self.__stimulated_raman_scattering = stimulated_raman_scattering + self._stimulated_raman_scattering = stimulated_raman_scattering @property def nli_params(self): - return self.__nli_params + return self._nli_params @nli_params.setter def nli_params(self, nli_params): """ :param model_params: namedtuple containing the parameters used to compute the NLI. """ - self.__nli_params = nli_params + self._nli_params = nli_params def alpha0(self, f_eval=193.5e12): if not hasattr(self.fiber_params.loss_coef, 'alpha_power'): From 2eed891f8d4a2183a397415eedd252a8d6b48285 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Wed, 31 Jul 2019 11:29:25 +0200 Subject: [PATCH 072/117] new variable names for SPM and XPM --- gnpy/core/science_utils.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 3502e2071..65f796fce 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -621,11 +621,10 @@ def _generalized_spectrally_separated_spm(self, carrier): f_eval = carrier.frequency g_cut = (carrier.power.signal / carrier.baud_rate) - - partial_nli = carrier.baud_rate * (16.0 / 27.0) * self.fiber_params.gamma**2 * g_cut**3 * \ + spm_nli = carrier.baud_rate * (16.0 / 27.0) * self.fiber_params.gamma**2 * g_cut**3 * \ self._generalized_psi(carrier, carrier, f_eval, f_cut_resolution, f_cut_resolution) - return partial_nli + return spm_nli def _generalized_spectrally_separated_xpm(self, carrier_cut, pump_carrier): delta_index = pump_carrier.channel_number - carrier_cut.channel_number @@ -634,16 +633,11 @@ def _generalized_spectrally_separated_xpm(self, carrier_cut, pump_carrier): f_eval = carrier_cut.frequency g_pump = (pump_carrier.power.signal / pump_carrier.baud_rate) g_cut = (carrier_cut.power.signal / carrier_cut.baud_rate) - partial_nli = carrier_cut.baud_rate * (16.0 / 27.0) * self.fiber_params.gamma**2 * \ - g_pump**2 * g_cut * \ - 2 * self._generalized_psi(carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution) - return partial_nli + xpm_nli = carrier_cut.baud_rate * (16.0 / 27.0) * self.fiber_params.gamma**2 * g_pump**2 * g_cut * \ + 2 * self._generalized_psi(carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution) - def _generalized_gnli(self, carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution): - delta_index = pump_carrier.channel_number - carrier_cut.channel_number - f_cut_resolution = self.nli_params.f_cut_resolution[f'delta_{delta_index}'] - f_pump_resolution = self.nli_params.f_pump_resolution + return xpm_nli g_pump = (pump_carrier.power.signal / pump_carrier.baud_rate) g_cut = (carrier_cut.power.signal / carrier_cut.baud_rate) From cde08b32a4a6fe256ce19460395a7b1e88865853 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Thu, 1 Aug 2019 10:45:20 +0200 Subject: [PATCH 073/117] output fixes on transmission_with_raman_main_example.py --- .../transmission_with_raman_main_example.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index 3812d2468..16f33b89d 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- ''' -transmission_main_example.py +transmission_with_raman_main_example.py ============================ Main example for transmission simulation. @@ -278,18 +278,17 @@ def main(network, equipment, source, destination, sim_params, req = None): save_network(args.filename, network) print(f'\n Computed after {time.time()-start_time} seconds. \n') - final_carriers = infos[path[-1]][1].carriers - print('The total SNR per channel at the end of the line is:') - print('Ch. # \t Channel frequency (THz) \t' - ' SNR total (signal bw, dB)') + print('Ch. # \t Channel frequency (THz) \t SNR NL (signal bw, dB) \t SNR total (signal bw, dB)') + final_carriers = infos[path[-1]][1].carriers for final_carrier in final_carriers: ch_freq = final_carrier.frequency * 1e-12 - ch_power = 10 * log10(final_carrier.power.signal) + 30 - ch_snr = ch_power - (10 * log10(final_carrier.power.nli + final_carrier.power.ase) + 30) - + ch_power = 10 * log10(final_carrier.power.signal) + ch_snr_nl = ch_power - 10 * log10(final_carrier.power.nli) + ch_snr = ch_power - 10 * log10(final_carrier.power.nli + final_carrier.power.ase) if not isnan(ch_snr): - print(f'{final_carrier.num_chan} \t\t {round(ch_freq, 2):.2f} \t\t\t {round(ch_snr, 2)}') + print(f'{final_carrier.num_chan} \t\t {round(ch_freq, 2):.2f} \t\t\t {round(ch_snr_nl, 2):.2f} ' + f'\t\t\t\t {round(ch_snr, 2):.2f}') if not args.source: print(f'\n(No source node specified: picked {source.uid})') From 4653dbcf4b4d76374f976302b4d85faa6c501cb2 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Thu, 1 Aug 2019 10:48:02 +0200 Subject: [PATCH 074/117] introduced a faster computation for XPM --- gnpy/core/science_utils.py | 45 ++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 65f796fce..213af661b 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -634,19 +634,46 @@ def _generalized_spectrally_separated_xpm(self, carrier_cut, pump_carrier): g_pump = (pump_carrier.power.signal / pump_carrier.baud_rate) g_cut = (carrier_cut.power.signal / carrier_cut.baud_rate) - xpm_nli = carrier_cut.baud_rate * (16.0 / 27.0) * self.fiber_params.gamma**2 * g_pump**2 * g_cut * \ - 2 * self._generalized_psi(carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution) - + if abs(carrier_cut.frequency - pump_carrier.frequency) <= 3 * 50e9: # For the first ten channels use the more accurate method + xpm_nli = carrier_cut.baud_rate * (16.0 / 27.0) * self.fiber_params.gamma**2 * g_pump**2 * g_cut * \ + 2 * self._generalized_psi(carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution) + else: + xpm_nli = carrier_cut.baud_rate * (16.0 / 27.0) * self.fiber_params.gamma**2 * g_pump**2 * g_cut * \ + 2 * self._fast_generalized_psi(carrier_cut, pump_carrier, f_eval, f_cut_resolution) return xpm_nli - g_pump = (pump_carrier.power.signal / pump_carrier.baud_rate) - g_cut = (carrier_cut.power.signal / carrier_cut.baud_rate) + def _fast_generalized_psi(self, carrier_cut, pump_carrier, f_eval, f_cut_resolution): + """ It computes the generalized psi function similarly to the one used in the GN model + :return: generalized_psi + """ + # Fiber parameters + alpha0 = self.alpha0(f_eval) + beta2 = self.fiber_params.beta2 + beta3 = self.fiber_params.beta3 + f_ref_beta = self.fiber_params.f_ref_beta + z = self.stimulated_raman_scattering.z + frequency_rho = self.stimulated_raman_scattering.frequency + rho_norm = self.stimulated_raman_scattering.rho * np.exp(np.abs(alpha0) * z / 2) + if len(frequency_rho) == 1: + rho_function = lambda f: rho_norm[0, :] + else: + rho_function = interp1d(frequency_rho, rho_norm, axis=0, fill_value='extrapolate') + rho_norm_pump = rho_function(pump_carrier.frequency) - chi = (16.0 / 27.0) + f1_array = np.array([pump_carrier.frequency - (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2), + pump_carrier.frequency + (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2)]) + f2_array = np.arange(carrier_cut.frequency, + carrier_cut.frequency + (carrier_cut.baud_rate * (1 + carrier_cut.roll_off) / 2), + f_cut_resolution) # Only positive f2 is used since integrand_f2 is symmetric - partial_nli = chi * self.fiber_params.gamma**2 * \ - g_pump**2 * g_cut * \ - 2 * self._generalized_psi(carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution) + integrand_f1 = np.zeros(len(f1_array)) + for f1_index, f1 in enumerate(f1_array): + delta_beta = 4 * np.pi**2 * (f1 - f_eval) * (f2_array - f_eval) * \ + (beta2 + np.pi * beta3 * (f1 + f2_array - 2 * f_ref_beta)) + integrand_f2 = self._generalized_rho_nli(delta_beta, rho_norm_pump, z, alpha0) + integrand_f1[f1_index] = 2 * np.trapz(integrand_f2, f2_array) # 2x since integrand_f2 is symmetric in f2 + generalized_psi = 0.5 * sum(integrand_f1) * pump_carrier.baud_rate + return generalized_psi def _generalized_psi(self, carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution): """ It computes the generalized psi function similarly to the one used in the GN model From 8bcde72a10e16a1e0204b3fb2e72bae89f5f92bf Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Thu, 1 Aug 2019 10:49:25 +0200 Subject: [PATCH 075/117] changes on variable names to be more clear --- gnpy/core/science_utils.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 213af661b..033635674 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -622,8 +622,7 @@ def _generalized_spectrally_separated_spm(self, carrier): g_cut = (carrier.power.signal / carrier.baud_rate) spm_nli = carrier.baud_rate * (16.0 / 27.0) * self.fiber_params.gamma**2 * g_cut**3 * \ - self._generalized_psi(carrier, carrier, f_eval, f_cut_resolution, f_cut_resolution) - + self._generalized_psi(carrier, carrier, f_eval, f_cut_resolution, f_cut_resolution) return spm_nli def _generalized_spectrally_separated_xpm(self, carrier_cut, pump_carrier): @@ -686,13 +685,12 @@ def _generalized_psi(self, carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_ref_beta = self.fiber_params.f_ref_beta z = self.stimulated_raman_scattering.z frequency_rho = self.stimulated_raman_scattering.frequency - rho = self.stimulated_raman_scattering.rho - rho = rho * np.exp(np.abs(alpha0) * z / 2) + rho_norm = self.stimulated_raman_scattering.rho * np.exp(np.abs(alpha0) * z / 2) if len(frequency_rho) == 1: - rho_function = lambda f: rho[0, :] + rho_function = lambda f: rho_norm[0, :] else: - rho_function = interp1d(frequency_rho, rho, axis=0, fill_value='extrapolate') - rho_pump = rho_function(pump_carrier.frequency) + rho_function = interp1d(frequency_rho, rho_norm, axis=0, fill_value='extrapolate') + rho_norm_pump = rho_function(pump_carrier.frequency) f1_array = np.arange(pump_carrier.frequency - (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2), pump_carrier.frequency + (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2), @@ -712,21 +710,18 @@ def _generalized_psi(self, carrier_cut, pump_carrier, f_eval, f_cut_resolution, delta_beta = 4 * np.pi**2 * (f1 - f_eval) * (f2_array - f_eval) * \ (beta2 + np.pi * beta3 * (f1 + f2_array - 2 * f_ref_beta)) - integrand_f2 = ggg * self._generalized_rho_nli(delta_beta, rho_pump, z, alpha0) + integrand_f2 = ggg * self._generalized_rho_nli(delta_beta, rho_norm_pump, z, alpha0) integrand_f1[f1_index] = np.trapz(integrand_f2, f2_array) generalized_psi = np.trapz(integrand_f1, f1_array) return generalized_psi @staticmethod - def _generalized_rho_nli(delta_beta, rho_pump, z, alpha0): + def _generalized_rho_nli(delta_beta, rho_norm_pump, z, alpha0): w = 1j * delta_beta - alpha0 - - generalized_rho_nli = (rho_pump[-1]**2 * np.exp(w * z[-1]) - rho_pump[0]**2 * np.exp(w * z[0])) / w + generalized_rho_nli = (rho_norm_pump[-1]**2 * np.exp(w * z[-1]) - rho_norm_pump[0]**2 * np.exp(w * z[0])) / w for z_ind in range(0, len(z) - 1): - derivative_rho = (rho_pump[z_ind + 1]**2 - rho_pump[z_ind]**2) / (z[z_ind + 1] - z[z_ind]) + derivative_rho = (rho_norm_pump[z_ind + 1]**2 - rho_norm_pump[z_ind]**2) / (z[z_ind + 1] - z[z_ind]) generalized_rho_nli -= derivative_rho * (np.exp(w * z[z_ind + 1]) - np.exp(w * z[z_ind])) / (w**2) - generalized_rho_nli = np.abs(generalized_rho_nli)**2 - return generalized_rho_nli From 88c68d206566c46e5ef5ba7b171674242264c09f Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Thu, 1 Aug 2019 11:32:07 +0200 Subject: [PATCH 076/117] introduced classes for the parameters --- gnpy/core/science_utils.py | 210 +++++++++++++++++++++++++++---------- 1 file changed, 155 insertions(+), 55 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 033635674..bbc1dfdab 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -10,6 +10,136 @@ from gnpy.core.utils import db2lin, load_json +class RamanParams(): + def __init__(self, params): + self._flag_raman = params['flag_raman'] + self._space_resolution = params['space_resolution'] + self._tolerance = params['tolerance'] + self._verbose = params['verbose'] + + @property + def flag_raman(self): + return self._flag_raman + + @property + def space_resolution(self): + return self._space_resolution + + @property + def tolerance(self): + return self._tolerance + + @property + def verbose(self): + return self._verbose + +class NLIParams(): + def __init__(self, params): + self._nli_method_name = params['nli_method_name'] + self._wdm_grid_size = params['wdm_grid_size'] + self._dispersion_tolerance = params['dispersion_tolerance'] + self._phase_shift_tollerance = params['phase_shift_tollerance'] + self._f_cut_resolution = None + self._f_pump_resolution = None + self._verbose = params['verbose'] + + @property + def nli_method_name(self): + return self._nli_method_name + + @property + def wdm_grid_size(self): + return self._wdm_grid_size + + @property + def dispersion_tolerance(self): + return self._dispersion_tolerance + + @property + def phase_shift_tollerance(self): + return self._phase_shift_tollerance + + @property + def verbose(self): + return self._verbose + + @property + def f_cut_resolution(self): + return self._f_cut_resolution + + @f_cut_resolution.setter + def f_cut_resolution(self, f_cut_resolution): + self._f_cut_resolution = f_cut_resolution + + @property + def f_pump_resolution(self): + return self._f_pump_resolution + + @f_pump_resolution.setter + def f_pump_resolution(self, f_pump_resolution): + self._f_pump_resolution = f_pump_resolution + +class SimParams(): + def __init__(self, params): + self._list_of_channels_under_test = params['list_of_channels_under_test'] + self._raman_params = RamanParams(params=params['raman_parameters']) + self._nli_params = NLIParams(params=params['nli_parameters']) + + @property + def list_of_channels_under_test(self): + return self._list_of_channels_under_test + + @property + def raman_params(self): + return self._raman_params + + @property + def nli_params(self): + return self._nli_params + +class FiberParams(): + def __init__(self, fiber): + self._loss_coef = 2 * fiber.dbkm_2_lin()[1] + self._length = fiber.length + self._gamma = fiber.gamma + self._beta2 = fiber.beta2() + self._beta3 = fiber.beta3 if hasattr(fiber, 'beta3') else 0 + self._f_ref_beta = fiber.f_ref_beta if hasattr(fiber, 'f_ref_beta') else 0 + self._raman_efficiency = fiber.params.raman_efficiency + self._temperature = fiber.operational['temperature'] + + @property + def loss_coef(self): + return self._loss_coef + + @property + def length(self): + return self._length + + @property + def gamma(self): + return self._gamma + + @property + def beta2(self): + return self._beta2 + + @property + def beta3(self): + return self._beta3 + + @property + def f_ref_beta(self): + return self._f_ref_beta + + @property + def raman_efficiency(self): + return self._raman_efficiency + + @property + def temperature(self): + return self._temperature + def load_sim_params(path_sim_params): sim_params = load_json(path_sim_params) return SimParams(params=sim_params) @@ -20,33 +150,6 @@ def configure_network(network, sim_params): if isinstance(node, RamanFiber): node.sim_params = sim_params -class RamanParams(): - def __init__(self, params=None): - if params: - self.flag_raman = params['flag_raman'] - self.space_resolution = params['space_resolution'] - self.tolerance = params['tolerance'] - self.verbose = params['verbose'] - -class NLIParams(): - def __init__(self, params=None): - if params: - self.nli_method_name = params['nli_method_name'] - self.wdm_grid_size = params['wdm_grid_size'] - self.dispersion_tolerance = params['dispersion_tolerance'] - self.phase_shift_tollerance = params['phase_shift_tollerance'] - self.f_cut_resolution = None - self.f_pump_resolution = None - self.verbose = params['verbose'] - -class SimParams(): - def __init__(self, params=None): - if params: - self.list_of_channels_under_test = params['list_of_channels_under_test'] - self.raman_params = RamanParams(params=params['raman_parameters']) - self.nli_params = NLIParams(params=params['nli_parameters']) - -fib_params = namedtuple('FiberParams', 'loss_coef length beta2 beta3 f_ref_beta gamma raman_efficiency temperature') pump = namedtuple('RamanPump', 'power frequency propagation_direction') def propagate_raman_fiber(fiber, *carriers): @@ -64,14 +167,7 @@ def propagate_raman_fiber(fiber, *carriers): carrier = carrier._replace(power=pwr) chan.append(carrier) carriers = tuple(f for f in chan) - raman_efficiency = fiber.params.raman_efficiency - if not raman_params.flag_raman: - raman_efficiency['cr'] = np.array(raman_efficiency['cr']) * 0 - fiber_params = fib_params(loss_coef=2*fiber.dbkm_2_lin()[1], length=fiber.length, gamma=fiber.gamma, - beta2=fiber.beta2(), beta3=fiber.beta3 if hasattr(fiber,'beta3') else 0, - f_ref_beta=fiber.f_ref_beta if hasattr(fiber,'f_ref_beta') else 0, - raman_efficiency=raman_efficiency, - temperature=fiber.operational['temperature']) + fiber_params = FiberParams(fiber) # evaluate fiber attenuation involving also SRS if required by sim_params if 'raman_pumps' in fiber.operational: @@ -97,7 +193,7 @@ def propagate_raman_fiber(fiber, *carriers): nli_solver = NliSolver(nli_params=nli_params, fiber_params=fiber_params) nli_solver.stimulated_raman_scattering = stimulated_raman_scattering for carrier, attenuation, rmn_ase in zip(carriers, fiber_attenuation, raman_ase): - resolution_param = frequency_resolution(carrier, carriers, sim_params, fiber) + resolution_param = frequency_resolution(carrier, carriers, sim_params, fiber_params) f_cut_resolution, f_pump_resolution, _, _ = resolution_param nli_params.f_cut_resolution = f_cut_resolution nli_params.f_pump_resolution = f_pump_resolution @@ -111,15 +207,28 @@ def propagate_raman_fiber(fiber, *carriers): amplified_spontaneous_emission=((pwr.ase/attenuation)+rmn_ase)/attenuation_out) yield carrier._replace(power=pwr) -def frequency_resolution(carrier, carriers, sim_params, fiber): +def frequency_resolution(carrier, carriers, sim_params, fiber_params): + def _get_freq_res_k_phi(delta_count, grid_size, loss_coef, delta_z, beta2, k_tol, phi_tol): + res_phi = _get_freq_res_phase_rotation(delta_count, grid_size, delta_z, beta2, phi_tol) + res_k = _get_freq_res_dispersion_attenuation(delta_count, grid_size, loss_coef, beta2, k_tol) + res_dict = {'res_phi': res_phi, 'res_k': res_k} + method = min(res_dict, key=res_dict.get) + return res_dict[method], method, res_dict + + def _get_freq_res_dispersion_attenuation(delta_count, grid_size, loss_coef, beta2, k_tol): + return k_tol * abs(loss_coef) / abs(beta2) / (1 + delta_count) / (4 * np.pi ** 2 * grid_size) + + def _get_freq_res_phase_rotation(delta_count, grid_size, delta_z, beta2, phi_tol): + return phi_tol / abs(beta2) / (1 + delta_count) / delta_z / (4 * np.pi ** 2 * grid_size) + grid_size = sim_params.nli_params.wdm_grid_size - alpha_ef = fiber.dbkm_2_lin()[1] + loss_coef = fiber_params.loss_coef delta_z = sim_params.raman_params.space_resolution - beta2 = fiber.beta2() + beta2 = fiber_params.beta2 k_tol = sim_params.nli_params.dispersion_tolerance phi_tol = sim_params.nli_params.phase_shift_tollerance f_pump_resolution, method_f_pump, res_dict_pump = \ - _get_freq_res_k_phi(0, grid_size, alpha_ef, delta_z, beta2, k_tol, phi_tol) + _get_freq_res_k_phi(0, grid_size, loss_coef, delta_z, beta2, k_tol, phi_tol) f_cut_resolution = {} method_f_cut = {} res_dict_cut = {} @@ -127,25 +236,12 @@ def frequency_resolution(carrier, carriers, sim_params, fiber): delta_number = cut_carrier.channel_number - carrier.channel_number delta_count = abs(delta_number) f_res, method, res_dict = \ - _get_freq_res_k_phi(delta_count, grid_size, alpha_ef, delta_z, beta2, k_tol, phi_tol) + _get_freq_res_k_phi(delta_count, grid_size, loss_coef, delta_z, beta2, k_tol, phi_tol) f_cut_resolution[f'delta_{delta_number}'] = f_res method_f_cut[delta_number] = method res_dict_cut[delta_number] = res_dict return [f_cut_resolution, f_pump_resolution, (method_f_cut, method_f_pump), (res_dict_cut, res_dict_pump)] -def _get_freq_res_k_phi(delta_count, grid_size, alpha_ef, delta_z, beta2, k_tol, phi_tol): - res_phi = _get_freq_res_phase_rotation(delta_count, grid_size, delta_z, beta2, phi_tol) - res_k = _get_freq_res_dispersion_attenuation(delta_count, grid_size, alpha_ef, beta2, k_tol) - res_dict = {'res_phi': res_phi, 'res_k': res_k} - method = min(res_dict, key=res_dict.get) - return res_dict[method], method, res_dict - -def _get_freq_res_dispersion_attenuation(delta_count, grid_size, alpha_ef, beta2, k_tol): - return k_tol * 2 * abs(alpha_ef) / abs(beta2) / (1 + delta_count) / (4*np.pi**2 * grid_size) - -def _get_freq_res_phase_rotation(delta_count, grid_size, delta_z, beta2, phi_tol): - return phi_tol / abs(beta2) / (1 + delta_count) / delta_z / (4*np.pi**2 * grid_size) - def raised_cosine_comb(f, *carriers): """ Returns an array storing the PSD of a WDM comb of raised cosine shaped channels at the input frequencies defined in array f @@ -359,7 +455,11 @@ def stimulated_raman_scattering(self, carriers, raman_pumps=None): # fiber parameters fiber_length = self.fiber_params.length loss_coef = self.fiber_params.loss_coef - raman_efficiency = self.fiber_params.raman_efficiency + if self.raman_params.flag_raman: + raman_efficiency = self.fiber_params.raman_efficiency + else: + raman_efficiency = self.fiber_params.raman_efficiency + raman_efficiency['cr'] = np.array(raman_efficiency['cr']) * 0 # raman solver parameters z_resolution = self.raman_params.space_resolution tolerance = self.raman_params.tolerance From 8f3923046b25d7e7d2c04622b525dc29663ac50f Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Thu, 1 Aug 2019 11:36:32 +0200 Subject: [PATCH 077/117] alpha0 computation moved from NLI solver to Fiber Params --- gnpy/core/science_utils.py | 46 +++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index bbc1dfdab..9ea1b9d11 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -140,6 +140,21 @@ def raman_efficiency(self): def temperature(self): return self._temperature + def alpha0(self, f_ref=193.5e12): + """ It returns the zero element of the series expansion of attenuation coefficient alpha(f) in the + reference frequency f_ref + + :param f_ref: reference frequency of series expansion [Hz] + :return: alpha0: power attenuation coefficient in f_ref [Neper/m] + """ + if not hasattr(self.loss_coef, 'alpha_power'): + alpha0 = self.loss_coef + else: + alpha_interp = interp1d(self.loss_coef['frequency'], + self.loss_coef['alpha_power']) + alpha0 = alpha_interp(f_ref) + return alpha0 + def load_sim_params(path_sim_params): sim_params = load_json(path_sim_params) return SimParams(params=sim_params) @@ -208,27 +223,27 @@ def propagate_raman_fiber(fiber, *carriers): yield carrier._replace(power=pwr) def frequency_resolution(carrier, carriers, sim_params, fiber_params): - def _get_freq_res_k_phi(delta_count, grid_size, loss_coef, delta_z, beta2, k_tol, phi_tol): + def _get_freq_res_k_phi(delta_count, grid_size, alpha0, delta_z, beta2, k_tol, phi_tol): res_phi = _get_freq_res_phase_rotation(delta_count, grid_size, delta_z, beta2, phi_tol) - res_k = _get_freq_res_dispersion_attenuation(delta_count, grid_size, loss_coef, beta2, k_tol) + res_k = _get_freq_res_dispersion_attenuation(delta_count, grid_size, alpha0, beta2, k_tol) res_dict = {'res_phi': res_phi, 'res_k': res_k} method = min(res_dict, key=res_dict.get) return res_dict[method], method, res_dict - def _get_freq_res_dispersion_attenuation(delta_count, grid_size, loss_coef, beta2, k_tol): - return k_tol * abs(loss_coef) / abs(beta2) / (1 + delta_count) / (4 * np.pi ** 2 * grid_size) + def _get_freq_res_dispersion_attenuation(delta_count, grid_size, alpha0, beta2, k_tol): + return k_tol * abs(alpha0) / abs(beta2) / (1 + delta_count) / (4 * np.pi ** 2 * grid_size) def _get_freq_res_phase_rotation(delta_count, grid_size, delta_z, beta2, phi_tol): return phi_tol / abs(beta2) / (1 + delta_count) / delta_z / (4 * np.pi ** 2 * grid_size) grid_size = sim_params.nli_params.wdm_grid_size - loss_coef = fiber_params.loss_coef delta_z = sim_params.raman_params.space_resolution + alpha0 = fiber_params.alpha0() beta2 = fiber_params.beta2 k_tol = sim_params.nli_params.dispersion_tolerance phi_tol = sim_params.nli_params.phase_shift_tollerance f_pump_resolution, method_f_pump, res_dict_pump = \ - _get_freq_res_k_phi(0, grid_size, loss_coef, delta_z, beta2, k_tol, phi_tol) + _get_freq_res_k_phi(0, grid_size, alpha0, delta_z, beta2, k_tol, phi_tol) f_cut_resolution = {} method_f_cut = {} res_dict_cut = {} @@ -236,7 +251,7 @@ def _get_freq_res_phase_rotation(delta_count, grid_size, delta_z, beta2, phi_tol delta_number = cut_carrier.channel_number - carrier.channel_number delta_count = abs(delta_number) f_res, method, res_dict = \ - _get_freq_res_k_phi(delta_count, grid_size, loss_coef, delta_z, beta2, k_tol, phi_tol) + _get_freq_res_k_phi(delta_count, grid_size, alpha0, delta_z, beta2, k_tol, phi_tol) f_cut_resolution[f'delta_{delta_number}'] = f_res method_f_cut[delta_number] = method res_dict_cut[delta_number] = res_dict @@ -605,15 +620,6 @@ def nli_params(self, nli_params): """ self._nli_params = nli_params - def alpha0(self, f_eval=193.5e12): - if not hasattr(self.fiber_params.loss_coef, 'alpha_power'): - alpha0 = self.fiber_params.loss_coef - else: - alpha_interp = interp1d(self.fiber_params.loss_coef['frequency'], - self.fiber_params.loss_coef['alpha_power']) - alpha0 = alpha_interp(f_eval) - return alpha0 - def compute_nli(self, carrier, *carriers): """ Compute NLI power generated by the WDM comb `*carriers` on the channel under test `carrier` at the end of the fiber span. @@ -681,7 +687,7 @@ def _gn_analytic(self, carrier, *carriers): :param carriers: the full WDM comb :return: carrier_nli: the amount of nonlinear interference in W on the carrier under analysis """ - alpha = self.alpha0() / 2 + alpha = self.fiber_params.alpha0() / 2 beta2 = self.fiber_params.beta2 gamma = self.fiber_params.gamma length = self.fiber_params.length @@ -701,7 +707,7 @@ def _gn_analytic(self, carrier, *carriers): def _psi(self, carrier, interfering_carrier): """ Calculates eq. 123 from arXiv:1209.0394. """ - alpha = self.alpha0() / 2 + alpha = self.fiber_params.alpha0() / 2 beta2 = self.fiber_params.beta2 asymptotic_length = 1 / (2 * alpha) @@ -746,7 +752,7 @@ def _fast_generalized_psi(self, carrier_cut, pump_carrier, f_eval, f_cut_resolut :return: generalized_psi """ # Fiber parameters - alpha0 = self.alpha0(f_eval) + alpha0 = self.fiber_params.alpha0(f_eval) beta2 = self.fiber_params.beta2 beta3 = self.fiber_params.beta3 f_ref_beta = self.fiber_params.f_ref_beta @@ -779,7 +785,7 @@ def _generalized_psi(self, carrier_cut, pump_carrier, f_eval, f_cut_resolution, :return: generalized_psi """ # Fiber parameters - alpha0 = self.alpha0(f_eval) + alpha0 = self.fiber_params.alpha0(f_eval) beta2 = self.fiber_params.beta2 beta3 = self.fiber_params.beta3 f_ref_beta = self.fiber_params.f_ref_beta From beb2b576aab25528b437f92608b41ff7fdf21d4e Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Thu, 1 Aug 2019 14:41:40 +0200 Subject: [PATCH 078/117] changed output of propagate_raman_fiber to return a list --- gnpy/core/science_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 9ea1b9d11..21bb1de80 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -207,6 +207,7 @@ def propagate_raman_fiber(fiber, *carriers): attenuation_out = db2lin(fiber.con_out) nli_solver = NliSolver(nli_params=nli_params, fiber_params=fiber_params) nli_solver.stimulated_raman_scattering = stimulated_raman_scattering + new_carriers = [] for carrier, attenuation, rmn_ase in zip(carriers, fiber_attenuation, raman_ase): resolution_param = frequency_resolution(carrier, carriers, sim_params, fiber_params) f_cut_resolution, f_pump_resolution, _, _ = resolution_param @@ -220,7 +221,8 @@ def propagate_raman_fiber(fiber, *carriers): pwr = pwr._replace(signal=pwr.signal/attenuation/attenuation_out, nonlinear_interference=(pwr.nli+carrier_nli)/attenuation/attenuation_out, amplified_spontaneous_emission=((pwr.ase/attenuation)+rmn_ase)/attenuation_out) - yield carrier._replace(power=pwr) + new_carriers.append(carrier._replace(power=pwr)) + return new_carriers def frequency_resolution(carrier, carriers, sim_params, fiber_params): def _get_freq_res_k_phi(delta_count, grid_size, alpha0, delta_z, beta2, k_tol, phi_tol): From 8a1001cd4067936861695f86541a8886a9ccde12 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Thu, 1 Aug 2019 14:42:47 +0200 Subject: [PATCH 079/117] dynamic evaluation of threshold frequency between near XPM and far XPM --- gnpy/core/science_utils.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 21bb1de80..0ad3db9aa 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -740,8 +740,8 @@ def _generalized_spectrally_separated_xpm(self, carrier_cut, pump_carrier): f_eval = carrier_cut.frequency g_pump = (pump_carrier.power.signal / pump_carrier.baud_rate) g_cut = (carrier_cut.power.signal / carrier_cut.baud_rate) - - if abs(carrier_cut.frequency - pump_carrier.frequency) <= 3 * 50e9: # For the first ten channels use the more accurate method + frequency_offset_threshold = self._frequency_offset_threshold(pump_carrier.baud_rate) + if abs(carrier_cut.frequency - pump_carrier.frequency) <= frequency_offset_threshold: xpm_nli = carrier_cut.baud_rate * (16.0 / 27.0) * self.fiber_params.gamma**2 * g_pump**2 * g_cut * \ 2 * self._generalized_psi(carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution) else: @@ -833,3 +833,10 @@ def _generalized_rho_nli(delta_beta, rho_norm_pump, z, alpha0): generalized_rho_nli = np.abs(generalized_rho_nli)**2 return generalized_rho_nli + def _frequency_offset_threshold(self, symbol_rate): + k_ref = 5 + beta2_ref = 21.3e-27 + delta_f_ref = 50e9 + rs_ref = 32e9 + freq_offset_th = ((k_ref * delta_f_ref) * rs_ref * beta2_ref) / (self.fiber_params.beta2 * symbol_rate) + return freq_offset_th From 6c975a53a1403033828b20cd9f251e79f3502128 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Thu, 1 Aug 2019 14:45:17 +0200 Subject: [PATCH 080/117] minor fix to eqpt_with_raman_config.json --- examples/eqpt_with_raman_config.json | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/examples/eqpt_with_raman_config.json b/examples/eqpt_with_raman_config.json index e9737c1fa..5bf6aaccc 100644 --- a/examples/eqpt_with_raman_config.json +++ b/examples/eqpt_with_raman_config.json @@ -7,7 +7,8 @@ "advanced_config_from_json": "std_medium_gain_advanced_config.json", "out_voa_auto": false, "allowed_for_design": false - }, { + }, + { "type_variety": "Juniper_BoosterHG", "type_def": "advanced_model", "gain_flatmax": 25, @@ -148,8 +149,28 @@ "dispersion": 1.67e-05, "gamma": 0.00127, "raman_efficiency": { - "cr": [0, 9.4E-06, 2.92E-05, 4.88E-05, 6.82E-05, 8.31E-05, 9.4E-05, 0.0001014, 0.0001069, 0.0001119, 0.0001217, 0.0001268, 0.0001365, 0.000149, 0.000165, 0.000181, 0.0001977, 0.0002192, 0.0002469, 0.0002749, 0.0002999, 0.0003206, 0.0003405, 0.0003592, 0.000374, 0.0003826, 0.0003841, 0.0003826, 0.0003802, 0.0003756, 0.0003549, 0.0003795, 0.000344, 0.0002933, 0.0002024, 0.0001158, 8.46E-05, 7.14E-05, 6.86E-05, 8.5E-05, 8.93E-05, 9.01E-05, 8.15E-05, 6.67E-05, 4.37E-05, 3.28E-05, 2.96E-05, 2.65E-05, 2.57E-05, 2.81E-05, 3.08E-05, 3.67E-05, 5.85E-05, 6.63E-05, 6.36E-05, 5.5E-05, 4.06E-05, 2.77E-05, 2.42E-05, 1.87E-05, 1.6E-05, 1.4E-05, 1.13E-05, 1.05E-05, 9.8E-06, 9.8E-06, 1.13E-05, 1.64E-05, 1.95E-05, 2.38E-05, 2.26E-05, 2.03E-05, 1.48E-05, 1.09E-05, 9.8E-06, 1.05E-05, 1.17E-05, 1.25E-05, 1.21E-05, 1.09E-05, 9.8E-06, 8.2E-06, 6.6E-06, 4.7E-06, 2.7E-06, 1.9E-06, 1.2E-06, 4E-07, 2E-07, 1E-07], - "frequency_offset": [0, 0.5e12, 1e12, 1.5e12, 2e12, 2.5e12, 3e12, 3.5e12, 4e12, 4.5e12, 5e12, 5.5e12, 6e12, 6.5e12, 7e12, 7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 1e120, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12750000000000, 13e12, 13250000000000, 13.5e12, 14e12, 14.5e12, 14750000000000, 15e12, 15.5e12, 16e12, 16.5e12, 17e12, 17.5e12, 18e12, 18250000000000, 18.5e12, 18750000000000, 19e12, 19.5e12, 2e120, 20.5e12, 21e12, 21.5e12, 22e12, 22.5e12, 23e12, 23.5e12, 24e12, 24.5e12, 25e12, 25.5e12, 26e12, 26.5e12, 27e12, 27.5e12, 28e12, 28.5e12, 29e12, 29.5e12, 3e120, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12, 35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 4e120, 40.5e12, 41e12, 41.5e12, 42e12] + "cr":[ + 0, 9.4E-06, 2.92E-05, 4.88E-05, 6.82E-05, 8.31E-05, 9.4E-05, 0.0001014, 0.0001069, 0.0001119, + 0.0001217, 0.0001268, 0.0001365, 0.000149, 0.000165, 0.000181, 0.0001977, 0.0002192, 0.0002469, + 0.0002749, 0.0002999, 0.0003206, 0.0003405, 0.0003592, 0.000374, 0.0003826, 0.0003841, 0.0003826, + 0.0003802, 0.0003756, 0.0003549, 0.0003795, 0.000344, 0.0002933, 0.0002024, 0.0001158, 8.46E-05, + 7.14E-05, 6.86E-05, 8.5E-05, 8.93E-05, 9.01E-05, 8.15E-05, 6.67E-05, 4.37E-05, 3.28E-05, 2.96E-05, + 2.65E-05, 2.57E-05, 2.81E-05, 3.08E-05, 3.67E-05, 5.85E-05, 6.63E-05, 6.36E-05, 5.5E-05, 4.06E-05, + 2.77E-05, 2.42E-05, 1.87E-05, 1.6E-05, 1.4E-05, 1.13E-05, 1.05E-05, 9.8E-06, 9.8E-06, 1.13E-05, + 1.64E-05, 1.95E-05, 2.38E-05, 2.26E-05, 2.03E-05, 1.48E-05, 1.09E-05, 9.8E-06, 1.05E-05, 1.17E-05, + 1.25E-05, 1.21E-05, 1.09E-05, 9.8E-06, 8.2E-06, 6.6E-06, 4.7E-06, 2.7E-06, 1.9E-06, 1.2E-06, 4E-07, + 2E-07, 1E-07 + ], + "frequency_offset":[ + 0, 0.5e12, 1e12, 1.5e12, 2e12, 2.5e12, 3e12, 3.5e12, 4e12, 4.5e12, 5e12, 5.5e12, 6e12, 6.5e12, 7e12, + 7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 1e120, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12.75e12, + 13e12, 13.25e12, 13.5e12, 14e12, 14.5e12, 14.75e12, 15e12, 15.5e12, 16e12, 16.5e12, 17e12, + 17.5e12, 18e12, 18.25e12, 18.5e12, 18.75e12, 19e12, 19.5e12, 2e120, 20.5e12, 21e12, 21.5e12, + 22e12, 22.5e12, 23e12, 23.5e12, 24e12, 24.5e12, 25e12, 25.5e12, 26e12, 26.5e12, 27e12, 27.5e12, 28e12, + 28.5e12, 29e12, 29.5e12, 3e120, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12, + 35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 4e120, 40.5e12, 41e12, + 41.5e12, 42e12 + ] } } ], From 8bd43130ab0876a1f11d90076fd2b451c971d621 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Thu, 1 Aug 2019 14:47:16 +0200 Subject: [PATCH 081/117] modified Raman pumps in the example raman_edfa_example_network.json to make Raman effect more evident --- examples/raman_edfa_example_network.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/raman_edfa_example_network.json b/examples/raman_edfa_example_network.json index 57ecbde76..4974238f9 100644 --- a/examples/raman_edfa_example_network.json +++ b/examples/raman_edfa_example_network.json @@ -20,12 +20,12 @@ "temperature": 283, "raman_pumps": [ { - "power": 100e-3, - "frequency": 200e12, + "power": 200e-3, + "frequency": 205e12, "propagation_direction": "counterprop" }, { - "power": 100e-3, + "power": 206e-3, "frequency": 201e12, "propagation_direction": "counterprop" } From ed1f51393aec08521189638982da05079764316a Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Thu, 1 Aug 2019 14:54:33 +0200 Subject: [PATCH 082/117] method name for computing NLI updated in sim_params.json --- examples/sim_params.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sim_params.json b/examples/sim_params.json index 3d244f2fd..ce27e18ad 100644 --- a/examples/sim_params.json +++ b/examples/sim_params.json @@ -7,7 +7,7 @@ "verbose": 2 }, "nli_parameters": { - "nli_method_name": "ggn_spectrally_separated_xpm_spm", + "nli_method_name": "ggn_spectrally_separated", "wdm_grid_size": 50e9, "dispersion_tolerance": 1, "phase_shift_tollerance": 0.1, From c249f44ea11358b31dc09eb188c3379afd8c3408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 6 Aug 2019 10:51:58 +0200 Subject: [PATCH 083/117] Remove property aliases For some reason, the code allowed using "convenience names" for accessing properties since commit 58ac717f. To me, this looks like an obvious anti-pattern because accessing a single property via three different names only makes the code less readable. Let's kill this "feature". In case of the `Power` class, the code used "ase" and "nli" on the majority of places, so let's use these abbreviations instead of their spelt-out variants. SpectralInformation was "clean" already, but there were calls to the `update()` wrapper around the `namedtuple._replace`. Given that there were no property aliases, it's safe to just call `_replace()` directly. In case of the `Pref` class, once again always use `p_span0`, `p_spani` instead of `p0` and `pi` -- it's a trivial change. --- gnpy/core/elements.py | 68 ++++++++++++++++++------------------------- gnpy/core/info.py | 41 +++++++------------------- 2 files changed, 40 insertions(+), 69 deletions(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index b9024cac0..b97990c95 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -154,8 +154,8 @@ def propagate(self, pref, *carriers): #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 - self.effective_pch_out_db = min(pref.pi, self.params.target_pch_out_db) - self.effective_loss = pref.pi - self.effective_pch_out_db + self.effective_pch_out_db = min(pref.p_spani, self.params.target_pch_out_db) + self.effective_loss = pref.p_spani - 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) @@ -163,17 +163,17 @@ def propagate(self, pref, *carriers): for carrier_att, carrier in zip(carriers_att, carriers) : pwr = carrier.power pwr = pwr._replace( signal = pwr.signal/carrier_att, - nonlinear_interference = pwr.nli/carrier_att, - amplified_spontaneous_emission = pwr.ase/carrier_att) + nli = pwr.nli/carrier_att, + ase = pwr.ase/carrier_att) yield carrier._replace(power=pwr) def update_pref(self, pref): - return pref._replace(p_span0=pref.p0, p_spani=self.effective_pch_out_db) + return pref._replace(p_span0=pref.p_span0, p_spani=self.effective_pch_out_db) def __call__(self, spectral_info): carriers = tuple(self.propagate(spectral_info.pref, *spectral_info.carriers)) pref = self.update_pref(spectral_info.pref) - return spectral_info.update(carriers=carriers, pref=pref) + return spectral_info._replace(carriers=carriers, pref=pref) FusedParams = namedtuple('FusedParams', 'loss') @@ -211,17 +211,17 @@ def propagate(self, *carriers): for carrier in carriers: pwr = carrier.power pwr = pwr._replace(signal=pwr.signal/attenuation, - nonlinear_interference=pwr.nli/attenuation, - amplified_spontaneous_emission=pwr.ase/attenuation) + nli=pwr.nli/attenuation, + ase=pwr.ase/attenuation) yield carrier._replace(power=pwr) def update_pref(self, pref): - return pref._replace(p_span0=pref.p0, p_spani=pref.pi - self.loss) + return pref._replace(p_span0=pref.p_span0, p_spani=pref.p_spani - self.loss) def __call__(self, spectral_info): carriers = tuple(self.propagate(*spectral_info.carriers)) pref = self.update_pref(spectral_info.pref) - return spectral_info.update(carriers=carriers, pref=pref) + return spectral_info._replace(carriers=carriers, pref=pref) FiberParams = namedtuple('FiberParams', 'type_variety length loss_coef length_units \ att_in con_in con_out dispersion gamma') @@ -327,11 +327,6 @@ def carriers(self, loc, attr): if not (loc in ('in', 'out') and attr in ('nli', 'signal', 'total', 'ase')): yield None return - power_dict = { - 'nli': 'nonlinear_interference', - 'ase': 'amplified_spontaneous_emission' - } - attr = power_dict.get(attr, attr) loc_attr = 'carriers_'+loc for c in getattr(self, loc_attr) : if attr == 'total': @@ -361,11 +356,11 @@ def dbkm_2_lin(self): def _psi(self, carrier, interfering_carrier): """Calculates eq. 123 from `arXiv:1209.0394 `__""" - if carrier.num_chan == interfering_carrier.num_chan: # SCI + if carrier.channel_number == interfering_carrier.channel_number: # SCI psi = arcsinh(0.5 * pi**2 * self.asymptotic_length * abs(self.beta2()) * carrier.baud_rate**2) else: # XCI - delta_f = carrier.freq - interfering_carrier.freq + delta_f = carrier.frequency - interfering_carrier.frequency psi = arcsinh(pi**2 * self.asymptotic_length * abs(self.beta2()) * carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate)) psi -= arcsinh(pi**2 * self.asymptotic_length * abs(self.beta2()) @@ -403,8 +398,8 @@ def propagate(self, *carriers): for carrier in carriers: pwr = carrier.power pwr = pwr._replace(signal=pwr.signal/attenuation, - nonlinear_interference=pwr.nli/attenuation, - amplified_spontaneous_emission=pwr.ase/attenuation) + nli=pwr.nli/attenuation, + ase=pwr.ase/attenuation) carrier = carrier._replace(power=pwr) chan.append(carrier) @@ -416,20 +411,20 @@ def propagate(self, *carriers): pwr = carrier.power carrier_nli = self._gn_analytic(carrier, *carriers) pwr = pwr._replace(signal=pwr.signal/self.lin_attenuation/attenuation, - nonlinear_interference=(pwr.nli+carrier_nli)/self.lin_attenuation/attenuation, - amplified_spontaneous_emission=pwr.ase/self.lin_attenuation/attenuation) + nli=(pwr.nli+carrier_nli)/self.lin_attenuation/attenuation, + ase=pwr.ase/self.lin_attenuation/attenuation) yield carrier._replace(power=pwr) def update_pref(self, pref): - self.pch_out_db = round(pref.pi - self.loss, 2) - return pref._replace(p_span0=pref.p0, p_spani=self.pch_out_db) + self.pch_out_db = round(pref.p_spani - self.loss, 2) + return pref._replace(p_span0=pref.p_span0, p_spani=self.pch_out_db) def __call__(self, spectral_info): self.carriers_in = spectral_info.carriers carriers = tuple(self.propagate(*spectral_info.carriers)) pref = self.update_pref(spectral_info.pref) self.carriers_out = carriers - return spectral_info.update(carriers=carriers, pref=pref) + return spectral_info._replace(carriers=carriers, pref=pref) class EdfaParams: def __init__(self, **params): @@ -563,11 +558,6 @@ def carriers(self, loc, attr): if not (loc in ('in', 'out') and attr in ('nli', 'signal', 'total', 'ase')): yield None return - power_dict = { - 'nli': 'nonlinear_interference', - 'ase': 'amplified_spontaneous_emission' - } - attr = power_dict.get(attr, attr) loc_attr = 'carriers_'+loc for c in getattr(self, loc_attr) : if attr == 'total': @@ -594,19 +584,19 @@ def interpol_params(self, frequencies, pin, baud_rates, pref): """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.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 + self.target_pch_out_db = round(self.delta_p + pref.p_span0, 2) + self.effective_gain = self.target_pch_out_db - pref.p_spani """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) + self.params.p_max - (pref.p_spani + 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) + self.effective_pch_out_db = round(pref.p_spani + self.effective_gain, 2) """check power saturation and correct target_gain accordingly:""" - #print(self.uid, self.effective_gain, self.pin_db, pref.pi) + #print(self.uid, self.effective_gain, self.pin_db, pref.p_spani) self.nf = self._calc_nf() self.gprofile = self._gain_profile(pin) @@ -844,17 +834,17 @@ def propagate(self, pref, *carriers): for gain, carrier_ase, carrier in zip(gains, carrier_ases, carriers): pwr = carrier.power pwr = pwr._replace(signal=pwr.signal*gain/att, - nonlinear_interference=pwr.nli*gain/att, - amplified_spontaneous_emission=(pwr.ase+carrier_ase)*gain/att) + nli=pwr.nli*gain/att, + ase=(pwr.ase+carrier_ase)*gain/att) yield carrier._replace(power=pwr) def update_pref(self, pref): - return pref._replace(p_span0=pref.p0, - p_spani=pref.pi + self.effective_gain - self.out_voa) + return pref._replace(p_span0=pref.p_span0, + p_spani=pref.p_spani + self.effective_gain - self.out_voa) def __call__(self, spectral_info): self.carriers_in = spectral_info.carriers carriers = tuple(self.propagate(spectral_info.pref, *spectral_info.carriers)) pref = self.update_pref(spectral_info.pref) self.carriers_out = carriers - return spectral_info.update(carriers=carriers, pref=pref) + return spectral_info._replace(carriers=carriers, pref=pref) diff --git a/gnpy/core/info.py b/gnpy/core/info.py index ceb6f4049..3d04da91e 100644 --- a/gnpy/core/info.py +++ b/gnpy/core/info.py @@ -16,46 +16,27 @@ from gnpy.core.utils import load_json from gnpy.core.equipment import automatic_nch, automatic_spacing -class ConvenienceAccess: - - def __init_subclass__(cls): - for abbrev, field in getattr(cls, '_ABBREVS', {}).items(): - setattr(cls, abbrev, property(lambda self, f=field: getattr(self, f))) - - def update(self, **kwargs): - for abbrev, field in getattr(self, '_ABBREVS', {}).items(): - if abbrev in kwargs: - kwargs[field] = kwargs.pop(abbrev) - return self._replace(**kwargs) - - -class Power(namedtuple('Power', 'signal nonlinear_interference amplified_spontaneous_emission'), ConvenienceAccess): +class Power(namedtuple('Power', 'signal nli ase')): """carriers power in W""" - _ABBREVS = {'nli': 'nonlinear_interference', - 'ase': 'amplified_spontaneous_emission',} -class Channel(namedtuple('Channel', 'channel_number frequency baud_rate roll_off power'), ConvenienceAccess): +class Channel(namedtuple('Channel', 'channel_number frequency baud_rate roll_off power')): + pass - _ABBREVS = {'channel': 'channel_number', - 'num_chan': 'channel_number', - 'ffs': 'frequency', - 'freq': 'frequency',} -class Pref(namedtuple('Pref', 'p_span0, p_spani, neq_ch '), ConvenienceAccess): +class Pref(namedtuple('Pref', 'p_span0, p_spani, neq_ch ')): """noiseless reference power in dBm: - p0: inital target carrier power - pi: carrier power after element i - neqch: equivalent channel count in dB""" + p_span0: inital target carrier power + p_spani: carrier power after element i + neq_ch: equivalent channel count in dB""" - _ABBREVS = {'p0' : 'p_span0', - 'pi' : 'p_spani'} -class SpectralInformation(namedtuple('SpectralInformation', 'pref carriers'), ConvenienceAccess): +class SpectralInformation(namedtuple('SpectralInformation', 'pref carriers')): def __new__(cls, pref, carriers): return super().__new__(cls, pref, carriers) + def merge_input_spectral_information(*si): """mix channel combs of different baud rates and power""" #TODO @@ -86,11 +67,11 @@ def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, si = SpectralInformation() spacing = 0.05 # THz - si = si.update(carriers=tuple(Channel(f+1, 191.3+spacing*(f+1), 32e9, 0.15, Power(1e-3, f, 1)) for f in range(96))) + si = si._replace(carriers=tuple(Channel(f+1, 191.3+spacing*(f+1), 32e9, 0.15, Power(1e-3, f, 1)) for f in range(96))) print(f'si = {si}') print(f'si = {si.carriers[0].power.nli}') print(f'si = {si.carriers[20].power.nli}') - si2 = si.update(carriers=tuple(c.update(power = c.power.update(nli = c.power.nli * 1e5)) + si2 = si._replace(carriers=tuple(c._replace(power = c.power._replace(nli = c.power.nli * 1e5)) for c in si.carriers)) print(f'si2 = {si2}') From cd234a909b8767bd6ec38e167f0963f2cb8a4347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 6 Aug 2019 11:22:56 +0200 Subject: [PATCH 084/117] Remove unimplemented and unused code --- gnpy/core/info.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gnpy/core/info.py b/gnpy/core/info.py index 3d04da91e..10a935f80 100644 --- a/gnpy/core/info.py +++ b/gnpy/core/info.py @@ -37,11 +37,6 @@ def __new__(cls, pref, carriers): return super().__new__(cls, pref, carriers) -def merge_input_spectral_information(*si): - """mix channel combs of different baud rates and power""" - #TODO - pass - 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) From 9c9e3be9675a78aefae555c32596e8faff3585f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 6 Aug 2019 11:49:32 +0200 Subject: [PATCH 085/117] Do not measure time from the example directly Let users wrap this with the Unix `time` command if they are interested in the time spent. --- examples/transmission_with_raman_main_example.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index 4b05fd0cc..7c198ff0d 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -14,7 +14,6 @@ from gnpy.core.utils import db2lin, lin2db, write_csv from argparse import ArgumentParser from sys import exit -import time from pathlib import Path from json import loads from collections import Counter @@ -273,10 +272,8 @@ def main(network, equipment, source, destination, sim_params, req = None): trx_params['power'] = db2lin(float(args.power))*1e-3 params.update(trx_params) req = Path_request(**params) - start_time = time.time() path, infos = main(network, equipment, source, destination, sim_params, req) save_network(args.filename, network) - print(f'\n Computed after {time.time()-start_time} seconds. \n') print('The total SNR per channel at the end of the line is:') print('Ch. # \t Channel frequency (THz) \t SNR NL (signal bw, dB) \t SNR total (signal bw, dB)') From 4d6966cbd389cea002da648594a0fb3ad2140a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 6 Aug 2019 11:51:25 +0200 Subject: [PATCH 086/117] Remove unused imports --- examples/transmission_with_raman_main_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index 7c198ff0d..dfc7a37a5 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -18,7 +18,7 @@ from json import loads from collections import Counter from logging import getLogger, basicConfig, INFO, ERROR, DEBUG -from numpy import linspace, mean, log10, array, isnan +from numpy import linspace, mean, log10, isnan from matplotlib.pyplot import show, axis, figure, title, text from networkx import (draw_networkx_nodes, draw_networkx_edges, draw_networkx_labels, dijkstra_path) From 528ff315901678fe30284a4b5d985935764b79cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 6 Aug 2019 11:56:01 +0200 Subject: [PATCH 087/117] CI: Run the Raman example as well --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index df34e4a93..de6ea2159 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ script: - rstcheck --ignore-roles cite --ignore-directives automodule --recursive --ignore-messages '(Duplicate explicit target name.*)' . - ./examples/transmission_main_example.py - ./examples/path_requests_run.py + - ./examples/transmission_with_raman_main_example.py - sphinx-build docs/ x-throwaway-location after_success: - bash <(curl -s https://codecov.io/bash) From 22b76e36db60bf5a69268a43d50ac06de62a9972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 6 Aug 2019 14:26:00 +0200 Subject: [PATCH 088/117] Fix typos in Raman's frequency offset This is a typo, the parameters were clearly supposed to be uniformly spaced. It doesn't affect the results (much), because the remaining values are "close enough" for a reasonable interpolation. Using the existing Raman example, the difference in OSNR ASE at the signal bandwidth is 0.01 dB (31.46 -> 31.47 dB). Perhaps it would make sense to enforce that these offsets are a monotonic sequence. --- examples/eqpt_with_raman_config.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/eqpt_with_raman_config.json b/examples/eqpt_with_raman_config.json index ea74bcbc2..581fb6301 100644 --- a/examples/eqpt_with_raman_config.json +++ b/examples/eqpt_with_raman_config.json @@ -163,12 +163,12 @@ ], "frequency_offset":[ 0, 0.5e12, 1e12, 1.5e12, 2e12, 2.5e12, 3e12, 3.5e12, 4e12, 4.5e12, 5e12, 5.5e12, 6e12, 6.5e12, 7e12, - 7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 1e120, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12.75e12, + 7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 10e12, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12.75e12, 13e12, 13.25e12, 13.5e12, 14e12, 14.5e12, 14.75e12, 15e12, 15.5e12, 16e12, 16.5e12, 17e12, - 17.5e12, 18e12, 18.25e12, 18.5e12, 18.75e12, 19e12, 19.5e12, 2e120, 20.5e12, 21e12, 21.5e12, + 17.5e12, 18e12, 18.25e12, 18.5e12, 18.75e12, 19e12, 19.5e12, 20e12, 20.5e12, 21e12, 21.5e12, 22e12, 22.5e12, 23e12, 23.5e12, 24e12, 24.5e12, 25e12, 25.5e12, 26e12, 26.5e12, 27e12, 27.5e12, 28e12, - 28.5e12, 29e12, 29.5e12, 3e120, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12, - 35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 4e120, 40.5e12, 41e12, + 28.5e12, 29e12, 29.5e12, 30e12, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12, + 35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 40e12, 40.5e12, 41e12, 41.5e12, 42e12 ] } From 33a8de9b398a94b8b5b66c5c6561a70f818d68ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 7 Aug 2019 13:36:21 +0200 Subject: [PATCH 089/117] Raman: stricter validation of input parameters The goal here is to try to prevent typos in configuration from slipping in undetected. --- gnpy/core/equipment.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gnpy/core/equipment.py b/gnpy/core/equipment.py index 0af3c53e3..52b4ca64b 100644 --- a/gnpy/core/equipment.py +++ b/gnpy/core/equipment.py @@ -120,6 +120,11 @@ class RamanFiber(common): def __init__(self, **kwargs): self.update_attr(self.default_values, kwargs, 'RamanFiber') + for param in ('cr', 'frequency_offset'): + if param not in self.raman_efficiency: + raise EquipmentConfigError(f'RamanFiber.raman_efficiency: missing "{param}" parameter') + if self.raman_efficiency['frequency_offset'] != sorted(self.raman_efficiency['frequency_offset']): + raise EquipmentConfigError(f'RamanFiber.raman_efficiency.frequency_offset is not sorted') class Amp(common): default_values = \ From 36ca22db9be7ef13e159777fdcf591966a684d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 7 Aug 2019 23:31:20 +0200 Subject: [PATCH 090/117] Raman: use logging instead of debugging print()s --- examples/sim_params.json | 8 +++----- gnpy/core/science_utils.py | 35 +++++++++++------------------------ 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/examples/sim_params.json b/examples/sim_params.json index ce27e18ad..0c51529c2 100644 --- a/examples/sim_params.json +++ b/examples/sim_params.json @@ -3,14 +3,12 @@ "raman_parameters": { "flag_raman": true, "space_resolution": 10e3, - "tolerance": 1e-8, - "verbose": 2 + "tolerance": 1e-8 }, "nli_parameters": { "nli_method_name": "ggn_spectrally_separated", "wdm_grid_size": 50e9, "dispersion_tolerance": 1, - "phase_shift_tollerance": 0.1, - "verbose": 1 + "phase_shift_tollerance": 0.1 } -} \ No newline at end of file +} diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 2be1ff82b..aaa53c797 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -1,6 +1,7 @@ import numpy as np from operator import attrgetter from collections import namedtuple +from logging import getLogger import scipy.constants as ph from scipy.integrate import solve_bvp from scipy.integrate import cumtrapz @@ -10,12 +11,14 @@ from gnpy.core.utils import db2lin, load_json +logger = getLogger(__name__) + + class RamanParams(): def __init__(self, params): self._flag_raman = params['flag_raman'] self._space_resolution = params['space_resolution'] self._tolerance = params['tolerance'] - self._verbose = params['verbose'] @property def flag_raman(self): @@ -29,10 +32,6 @@ def space_resolution(self): def tolerance(self): return self._tolerance - @property - def verbose(self): - return self._verbose - class NLIParams(): def __init__(self, params): self._nli_method_name = params['nli_method_name'] @@ -41,7 +40,6 @@ def __init__(self, params): self._phase_shift_tollerance = params['phase_shift_tollerance'] self._f_cut_resolution = None self._f_pump_resolution = None - self._verbose = params['verbose'] @property def nli_method_name(self): @@ -59,10 +57,6 @@ def dispersion_tolerance(self): def phase_shift_tollerance(self): return self._phase_shift_tollerance - @property - def verbose(self): - return self._verbose - @property def f_cut_resolution(self): return self._f_cut_resolution @@ -352,10 +346,8 @@ def spontaneous_raman_scattering(self): temperature = self.fiber_params.temperature carriers = self.carriers raman_pumps = self.raman_pumps - verbose = self.raman_params.verbose - if verbose: - print('Start computing fiber Spontaneous Raman Scattering') + logger.debug('Start computing fiber Spontaneous Raman Scattering') power_spectrum, freq_array, prop_direct, bn_array = self._compute_power_spectrum(carriers, raman_pumps) if not hasattr(loss_coef, 'alpha_power'): @@ -382,8 +374,7 @@ def spontaneous_raman_scattering(self): setattr(spontaneous_raman_scattering, 'power', spontaneous_raman_scattering.x) delattr(spontaneous_raman_scattering, 'x') - if verbose: - print(spontaneous_raman_scattering.message) + logger.debug(spontaneous_raman_scattering.message) self._spontaneous_raman_scattering = spontaneous_raman_scattering @@ -480,10 +471,8 @@ def stimulated_raman_scattering(self, carriers, raman_pumps=None): # raman solver parameters z_resolution = self.raman_params.space_resolution tolerance = self.raman_params.tolerance - verbose = self.raman_params.verbose - if verbose: - print('Start computing fiber Stimulated Raman Scattering') + logger.debug('Start computing fiber Stimulated Raman Scattering') power_spectrum, freq_array, prop_direct, _ = self._compute_power_spectrum(carriers, raman_pumps) @@ -505,7 +494,7 @@ def stimulated_raman_scattering(self, carriers, raman_pumps=None): initial_guess_conditions = self._initial_guess_stimulated_raman(z, power_spectrum, alphap_fiber, prop_direct) # ODE SOLVER - stimulated_raman_scattering = solve_bvp(ode_function, boundary_residual, z, initial_guess_conditions, tol=tolerance, verbose=verbose) + stimulated_raman_scattering = solve_bvp(ode_function, boundary_residual, z, initial_guess_conditions, tol=tolerance) rho = (stimulated_raman_scattering.y.transpose() / power_spectrum).transpose() rho = np.sqrt(rho) # From power attenuation to field attenuation @@ -654,8 +643,7 @@ def _compute_eta_matrix(self, carrier_cut, *carriers): eta_matrix = np.zeros(shape=(matrix_size, matrix_size)) # SPM - if self.nli_params.verbose: - print(f'Start computing SPM on channel #{carrier_cut.channel_number}') + logger.debug(f'Start computing SPM on channel #{carrier_cut.channel_number}') # SPM GGN if 'ggn' in self.nli_params.nli_method_name.lower(): partial_nli = self._generalized_spectrally_separated_spm(carrier_cut) @@ -668,9 +656,8 @@ def _compute_eta_matrix(self, carrier_cut, *carriers): for pump_carrier in carriers: pump_index = pump_carrier.channel_number - 1 if not (cut_index == pump_index): - if self.nli_params.verbose: - print(f'Start computing XPM on channel #{carrier_cut.channel_number} ' - f'from channel #{pump_carrier.channel_number}') + logger.debug(f'Start computing XPM on channel #{carrier_cut.channel_number} ' + f'from channel #{pump_carrier.channel_number}') # XPM GGN if 'ggn' in self.nli_params.nli_method_name.lower(): partial_nli = self._generalized_spectrally_separated_xpm(carrier_cut, pump_carrier) From 27ce55de38ee8c1a028f9249bf6f0c0e9f158762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 7 Aug 2019 23:41:30 +0200 Subject: [PATCH 091/117] diagnostics: Correctly report all spans, not just the Raman ones It's just a harmless message, the path traversal was still using all connections as defined in the topology JSON. --- examples/transmission_with_raman_main_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index dfc7a37a5..ed4357b08 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -126,7 +126,7 @@ def main(network, equipment, source, destination, sim_params, req = None): configure_network(network, sim_params) path = compute_constrained_path(network, req) - spans = [s.length for s in path if isinstance(s, RamanFiber)] + spans = [s.length for s in path if isinstance(s, RamanFiber) or isinstance(s, Fiber)] print(f'\nThere are {len(spans)} fiber spans over {sum(spans):.0f}m between {source.uid} and {destination.uid}') print(f'\nNow propagating between {source.uid} and {destination.uid}:') From 58bcf65cf6427c26a2e6ac3aab64f0fbe18cec81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 7 Aug 2019 23:45:44 +0200 Subject: [PATCH 092/117] Raman: do not introduce a new copy of the equipment library Compared to `eqpt_config.json`, the only extra content in the new copy was the `RamanFiber` block. There's no disadvantage in just using one equipment library; the traditional code can easily ignore the RamanFiber stanza. --- examples/eqpt_config.json | 30 ++ examples/eqpt_with_raman_config.json | 308 ------------------ .../transmission_with_raman_main_example.py | 4 +- 3 files changed, 32 insertions(+), 310 deletions(-) delete mode 100644 examples/eqpt_with_raman_config.json diff --git a/examples/eqpt_config.json b/examples/eqpt_config.json index 070509069..bf9d7c1ec 100644 --- a/examples/eqpt_config.json +++ b/examples/eqpt_config.json @@ -159,6 +159,36 @@ "gamma": 0.000843 } ], + "RamanFiber":[{ + "type_variety": "SSMF", + "dispersion": 1.67e-05, + "gamma": 0.00127, + "raman_efficiency": { + "cr":[ + 0, 9.4E-06, 2.92E-05, 4.88E-05, 6.82E-05, 8.31E-05, 9.4E-05, 0.0001014, 0.0001069, 0.0001119, + 0.0001217, 0.0001268, 0.0001365, 0.000149, 0.000165, 0.000181, 0.0001977, 0.0002192, 0.0002469, + 0.0002749, 0.0002999, 0.0003206, 0.0003405, 0.0003592, 0.000374, 0.0003826, 0.0003841, 0.0003826, + 0.0003802, 0.0003756, 0.0003549, 0.0003795, 0.000344, 0.0002933, 0.0002024, 0.0001158, 8.46E-05, + 7.14E-05, 6.86E-05, 8.5E-05, 8.93E-05, 9.01E-05, 8.15E-05, 6.67E-05, 4.37E-05, 3.28E-05, 2.96E-05, + 2.65E-05, 2.57E-05, 2.81E-05, 3.08E-05, 3.67E-05, 5.85E-05, 6.63E-05, 6.36E-05, 5.5E-05, 4.06E-05, + 2.77E-05, 2.42E-05, 1.87E-05, 1.6E-05, 1.4E-05, 1.13E-05, 1.05E-05, 9.8E-06, 9.8E-06, 1.13E-05, + 1.64E-05, 1.95E-05, 2.38E-05, 2.26E-05, 2.03E-05, 1.48E-05, 1.09E-05, 9.8E-06, 1.05E-05, 1.17E-05, + 1.25E-05, 1.21E-05, 1.09E-05, 9.8E-06, 8.2E-06, 6.6E-06, 4.7E-06, 2.7E-06, 1.9E-06, 1.2E-06, 4E-07, + 2E-07, 1E-07 + ], + "frequency_offset":[ + 0, 0.5e12, 1e12, 1.5e12, 2e12, 2.5e12, 3e12, 3.5e12, 4e12, 4.5e12, 5e12, 5.5e12, 6e12, 6.5e12, 7e12, + 7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 10e12, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12.75e12, + 13e12, 13.25e12, 13.5e12, 14e12, 14.5e12, 14.75e12, 15e12, 15.5e12, 16e12, 16.5e12, 17e12, + 17.5e12, 18e12, 18.25e12, 18.5e12, 18.75e12, 19e12, 19.5e12, 20e12, 20.5e12, 21e12, 21.5e12, + 22e12, 22.5e12, 23e12, 23.5e12, 24e12, 24.5e12, 25e12, 25.5e12, 26e12, 26.5e12, 27e12, 27.5e12, 28e12, + 28.5e12, 29e12, 29.5e12, 30e12, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12, + 35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 40e12, 40.5e12, 41e12, + 41.5e12, 42e12 + ] + } + } + ], "Span":[{ "power_mode":true, "delta_power_range_db": [-2,3,0.5], diff --git a/examples/eqpt_with_raman_config.json b/examples/eqpt_with_raman_config.json deleted file mode 100644 index 581fb6301..000000000 --- a/examples/eqpt_with_raman_config.json +++ /dev/null @@ -1,308 +0,0 @@ -{ "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": "Juniper_BoosterHG", - "type_def": "advanced_model", - "gain_flatmax": 25, - "gain_min": 10, - "p_max": 21, - "advanced_config_from_json": "Juniper-BoosterHG.json", - "out_voa_auto": false, - "allowed_for_design": false - }, - { - "type_variety": "operator_model_example", - "type_def": "variable_gain", - "gain_flatmax": 26, - "gain_min": 15, - "p_max": 23, - "nf_min": 6, - "nf_max": 10, - "out_voa_auto": false, - "allowed_for_design": false - }, - { - "type_variety": "low_noise", - "type_def": "openroadm", - "gain_flatmax": 27, - "gain_min": 12, - "p_max": 22, - "nf_coef": [-8.104e-4,-6.221e-2,-5.889e-1,37.62], - "allowed_for_design": false - }, - { - "type_variety": "standard", - "type_def": "openroadm", - "gain_flatmax": 27, - "gain_min": 12, - "p_max": 22, - "nf_coef": [-5.952e-4,-6.250e-2,-1.071,28.99], - "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, - "gain_min": 15, - "p_max": 23, - "nf_min": 6, - "nf_max": 10, - "out_voa_auto": false, - "allowed_for_design": true - }, - { - "type_variety": "std_low_gain", - "type_def": "variable_gain", - "gain_flatmax": 16, - "gain_min": 8, - "p_max": 23, - "nf_min": 6.5, - "nf_max": 11, - "out_voa_auto": false, - "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, - "gain_min": 20, - "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 - } - ], - "RamanFiber":[{ - "type_variety": "SSMF", - "dispersion": 1.67e-05, - "gamma": 0.00127, - "raman_efficiency": { - "cr":[ - 0, 9.4E-06, 2.92E-05, 4.88E-05, 6.82E-05, 8.31E-05, 9.4E-05, 0.0001014, 0.0001069, 0.0001119, - 0.0001217, 0.0001268, 0.0001365, 0.000149, 0.000165, 0.000181, 0.0001977, 0.0002192, 0.0002469, - 0.0002749, 0.0002999, 0.0003206, 0.0003405, 0.0003592, 0.000374, 0.0003826, 0.0003841, 0.0003826, - 0.0003802, 0.0003756, 0.0003549, 0.0003795, 0.000344, 0.0002933, 0.0002024, 0.0001158, 8.46E-05, - 7.14E-05, 6.86E-05, 8.5E-05, 8.93E-05, 9.01E-05, 8.15E-05, 6.67E-05, 4.37E-05, 3.28E-05, 2.96E-05, - 2.65E-05, 2.57E-05, 2.81E-05, 3.08E-05, 3.67E-05, 5.85E-05, 6.63E-05, 6.36E-05, 5.5E-05, 4.06E-05, - 2.77E-05, 2.42E-05, 1.87E-05, 1.6E-05, 1.4E-05, 1.13E-05, 1.05E-05, 9.8E-06, 9.8E-06, 1.13E-05, - 1.64E-05, 1.95E-05, 2.38E-05, 2.26E-05, 2.03E-05, 1.48E-05, 1.09E-05, 9.8E-06, 1.05E-05, 1.17E-05, - 1.25E-05, 1.21E-05, 1.09E-05, 9.8E-06, 8.2E-06, 6.6E-06, 4.7E-06, 2.7E-06, 1.9E-06, 1.2E-06, 4E-07, - 2E-07, 1E-07 - ], - "frequency_offset":[ - 0, 0.5e12, 1e12, 1.5e12, 2e12, 2.5e12, 3e12, 3.5e12, 4e12, 4.5e12, 5e12, 5.5e12, 6e12, 6.5e12, 7e12, - 7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 10e12, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12.75e12, - 13e12, 13.25e12, 13.5e12, 14e12, 14.5e12, 14.75e12, 15e12, 15.5e12, 16e12, 16.5e12, 17e12, - 17.5e12, 18e12, 18.25e12, 18.5e12, 18.75e12, 19e12, 19.5e12, 20e12, 20.5e12, 21e12, 21.5e12, - 22e12, 22.5e12, 23e12, 23.5e12, 24e12, 24.5e12, 25e12, 25.5e12, 26e12, 26.5e12, 27e12, 27.5e12, 28e12, - 28.5e12, 29e12, 29.5e12, 30e12, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12, - 35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 40e12, 40.5e12, 41e12, - 41.5e12, 42e12 - ] - } - } - ], - "Fiber":[{ - "type_variety": "SSMF", - "dispersion": 1.67e-05, - "gamma": 0.00127 - }, - { - "type_variety": "NZDF", - "dispersion": 0.5e-05, - "gamma": 0.00146 - }, - { - "type_variety": "LOF", - "dispersion": 2.2e-05, - "gamma": 0.000843 - } - ], - "Span":[{ - "power_mode":true, - "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, - "padding": 10, - "EOL": 0, - "con_in": 0, - "con_out": 0 - } - ], - "Roadm":[{ - "target_pch_out_db": -20, - "add_drop_osnr": 38, - "restrictions": { - "preamp_variety_list":[], - "booster_variety_list":[] - } - }], - "SI":[{ - "f_min": 191.3e12, - "baud_rate": 32e9, - "f_max":195.1e12, - "spacing": 50e9, - "power_dbm": 0, - "power_range_db": [0,0,1], - "roll_off": 0.15, - "tx_osnr": 40, - "sys_margins": 2 - }], - "Transceiver":[ - { - "type_variety": "vendorA_trx-type1", - "frequency":{ - "min": 191.35e12, - "max": 196.1e12 - }, - "mode":[ - { - - "format": "mode 1", - "baud_rate": 32e9, - "OSNR": 11, - "bit_rate": 100e9, - "roll_off": 0.15, - "tx_osnr": 40, - "min_spacing": 37.5e9, - "cost":1 - }, - { - "format": "mode 2", - "baud_rate": 66e9, - "OSNR": 15, - "bit_rate": 200e9, - "roll_off": 0.15, - "tx_osnr": 40, - "min_spacing": 75e9, - "cost":1 - } - ] - }, - { - "type_variety": "Voyager", - "frequency":{ - "min": 191.35e12, - "max": 196.1e12 - }, - "mode":[ - { - "format": "mode 1", - "baud_rate": 32e9, - "OSNR": 12, - "bit_rate": 100e9, - "roll_off": 0.15, - "tx_osnr": 40, - "min_spacing": 37.5e9, - "cost":1 - }, - { - "format": "mode 3", - "baud_rate": 44e9, - "OSNR": 18, - "bit_rate": 300e9, - "roll_off": 0.15, - "tx_osnr": 40, - "min_spacing": 62.5e9, - "cost":1 - }, - { - "format": "mode 2", - "baud_rate": 66e9, - "OSNR": 21, - "bit_rate": 400e9, - "roll_off": 0.15, - "tx_osnr": 40, - "min_spacing": 75e9, - "cost":1 - }, - { - "format": "mode 4", - "baud_rate": 66e9, - "OSNR": 16, - "bit_rate": 200e9, - "roll_off": 0.15, - "tx_osnr": 40, - "min_spacing": 75e9, - "cost":1 - } - ] - } - ] - -} diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index ed4357b08..90bdf4280 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -118,7 +118,7 @@ def main(network, equipment, source, destination, sim_params, req = None): 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_with_raman_config.json - Span'])) + 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) @@ -183,7 +183,7 @@ def main(network, equipment, source, destination, sim_params, req = None): parser = ArgumentParser() parser.add_argument('-e', '--equipment', type=Path, - default=Path(__file__).parent / 'eqpt_with_raman_config.json') + default=Path(__file__).parent / 'eqpt_config.json') parser.add_argument('-sim', '--sim-params', type=Path, default=Path(__file__).parent / 'sim_params.json', help='Path to the json containing simulation parameters') parser.add_argument('-pl', '--plot', action='store_true') From a6e741d8febbf3851f69a0031b50ee73ba801bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 8 Aug 2019 08:45:17 +0200 Subject: [PATCH 093/117] Do not copy the whole Fiber class --- gnpy/core/elements.py | 139 +----------------------------------------- 1 file changed, 3 insertions(+), 136 deletions(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index d345f371d..1d6266386 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -429,7 +429,7 @@ def __call__(self, spectral_info): RamanFiberParams = namedtuple('RamanFiberParams', 'type_variety length loss_coef length_units \ att_in con_in con_out dispersion gamma raman_efficiency') -class RamanFiber(Node): +class RamanFiber(Fiber): def __init__(self, *args, params=None, **kwargs): if params is None: params = {} @@ -443,7 +443,8 @@ def __init__(self, *args, params=None, **kwargs): #fixed attenuator for padding params['att_in'] = 0 - super().__init__(*args, params=RamanFiberParams(**params), **kwargs) + # TODO: can we re-use the Fiber constructor in a better way? + Node.__init__(self, *args, params=RamanFiberParams(**params), **kwargs) self.type_variety = self.params.type_variety self.length = self.params.length * UNITS[self.params.length_units] # in m self.loss_coef = self.params.loss_coef * 1e-3 # lineic loss dB/m @@ -458,69 +459,6 @@ def __init__(self, *args, params=None, **kwargs): self.carriers_out = None # TODO|jla: discuss factor 2 in the linear lineic attenuation - @property - def to_json(self): - return {'uid' : self.uid, - 'type' : type(self).__name__, - 'type_variety' : self.type_variety, - 'params' : { - #have to specify each because namedtupple cannot be updated :( - 'type_variety' : self.type_variety, - 'length' : self.length/UNITS[self.params.length_units], - 'loss_coef' : self.loss_coef*1e3, - 'length_units' : self.params.length_units, - 'att_in' : self.att_in, - 'con_in' : self.con_in, - 'con_out' : self.con_out - }, - 'metadata' : { - 'location': self.metadata['location']._asdict() - } - } - - def __repr__(self): - return f'{type(self).__name__}(uid={self.uid!r}, length={round(self.length*1e-3,1)!r}km, loss={round(self.loss,1)!r}dB)' - - def __str__(self): - return '\n'.join([f'{type(self).__name__} {self.uid}', - f' type_variety: {self.type_variety}', - f' length (km): {round(self.length*1e-3):.2f}', - f' pad att_in (dB): {self.att_in:.2f}', - f' total loss (dB): {self.loss:.2f}', - f' (includes conn loss (dB) in: {self.con_in:.2f} out: {self.con_out:.2f})', - f' (conn loss out includes EOL margin defined in eqpt_config.json)', - f' pch out (dBm): {self.pch_out_db!r}']) - - @property - def fiber_loss(self): - # dB fiber loss, not including padding attenuator - return self.loss_coef * self.length + self.con_in + self.con_out - - @property - def loss(self): - #total loss incluiding padding att_in: useful for polymorphism with roadm loss - return self.loss_coef * self.length + self.con_in + self.con_out + self.att_in - - @property - def passive(self): - return True - - @property - def lin_attenuation(self): - return db2lin(self.length * self.loss_coef) - - @property - def effective_length(self): - _, alpha = self.dbkm_2_lin() - leff = (1 - exp(-2 * alpha * self.length)) / (2 * alpha) - return leff - - @property - def asymptotic_length(self): - _, alpha = self.dbkm_2_lin() - aleff = 1 / (2 * alpha) - return aleff - @property def sim_params(self): return self._sim_params @@ -529,77 +467,6 @@ def sim_params(self): def sim_params(self, sim_params=None): self._sim_params = sim_params - def carriers(self, loc, attr): - """retrieve carriers information - loc = (in, out) of the class element - attr = (ase, nli, signal, total) power information""" - if not (loc in ('in', 'out') and attr in ('nli', 'signal', 'total', 'ase')): - yield None - return - loc_attr = 'carriers_'+loc - for c in getattr(self, loc_attr) : - if attr == 'total': - yield c.power.ase+c.power.nli+c.power.signal - else: - yield c.power._asdict().get(attr, None) - - def beta2(self, ref_wavelength=None): - """ Returns beta2 from dispersion parameter. - Dispersion is entered in ps/nm/km. - Disperion can be a numpy array or a single value. If a - value ref_wavelength is not entered 1550e-9m will be assumed. - ref_wavelength can be a numpy array. - """ - # TODO|jla: discuss beta2 as method or attribute - wl = 1550e-9 if ref_wavelength is None else ref_wavelength - D = abs(self.dispersion) - b2 = (wl ** 2) * D / (2 * pi * c) # 10^21 scales [ps^2/km] - return b2 # s/Hz/m - - def dbkm_2_lin(self): - """ calculates the linear loss coefficient - """ - # alpha_pcoef is linear loss coefficient in dB/km^-1 - # alpha_acoef is linear loss field amplitude coefficient in m^-1 - alpha_pcoef = self.loss_coef - alpha_acoef = alpha_pcoef / (2 * 10 * log10(exp(1))) - return alpha_pcoef, alpha_acoef - - def _psi(self, carrier, interfering_carrier): - """ Calculates eq. 123 from arXiv:1209.0394. - """ - if carrier.channel_number == interfering_carrier.channel_number: # SCI - psi = arcsinh(0.5 * pi**2 * self.asymptotic_length - * abs(self.beta2()) * carrier.baud_rate**2) - else: # XCI - delta_f = carrier.frequency - interfering_carrier.frequency - psi = arcsinh(pi**2 * self.asymptotic_length * abs(self.beta2()) - * carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate)) - psi -= arcsinh(pi**2 * self.asymptotic_length * abs(self.beta2()) - * carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) - - return psi - - def _gn_analytic(self, carrier, *carriers): - """ Computes the nonlinear interference power on a single carrier. - The method uses eq. 120 from arXiv:1209.0394. - :param carrier: the signal under analysis - :param carriers: the full WDM comb - :return: carrier_nli: the amount of nonlinear interference in W on the under analysis - """ - - g_nli = 0 - for interfering_carrier in carriers: - psi = self._psi(carrier, interfering_carrier) - g_nli += (interfering_carrier.power.signal/interfering_carrier.baud_rate)**2 \ - * (carrier.power.signal/carrier.baud_rate) * psi - - g_nli *= (16 / 27) * (self.gamma * self.effective_length)**2 \ - / (2 * pi * abs(self.beta2()) * self.asymptotic_length) - - carrier_nli = carrier.baud_rate * g_nli - return carrier_nli - def update_pref(self, pref, *carriers): pch_out_db = 10*log10(mean([carrier.power.signal for carrier in carriers])) + 30 self.pch_out_db = round(pch_out_db, 2) From 71d6a1138c4d7358fd5f4038cff56edab680f93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 8 Aug 2019 10:53:52 +0200 Subject: [PATCH 094/117] Fix a typo in my e-mail address This has been around since before 1a104956, but I haven't noticed the missing `.com`. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b26bffdee..09eae4aa3 100644 --- a/README.rst +++ b/README.rst @@ -118,7 +118,7 @@ fully-functional programs. **Note**: *If you are a network operator or involved in route planning and optimization for your organization, please contact project maintainer Jan - Kundrát . gnpy is looking for users with + Kundrát . gnpy is looking for users with specific, delineated use cases to drive requirements for future development.* From 660b8b3c6e4d5fa2cd2e056bfb812b4b6bb17f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 8 Aug 2019 11:16:26 +0200 Subject: [PATCH 095/117] Use lin2db() when we have it --- gnpy/core/elements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index 1d6266386..f461a2e55 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -468,7 +468,7 @@ def sim_params(self, sim_params=None): self._sim_params = sim_params def update_pref(self, pref, *carriers): - pch_out_db = 10*log10(mean([carrier.power.signal for carrier in carriers])) + 30 + pch_out_db = lin2db(mean([carrier.power.signal for carrier in carriers])) + 30 self.pch_out_db = round(pch_out_db, 2) return pref._replace(p_span0=pref.p_span0, p_spani=self.pch_out_db) From 0f4d8573cf0f49d7facd1728165f2b6e084df9fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 8 Aug 2019 11:18:02 +0200 Subject: [PATCH 096/117] Move Raman parameter propagation to gnpy.core.network Conceptually, this is just about propagating the input parameters (which drive the simulation) into all RamanFiber instances. The network module already contains similar functions, let's move it there. --- examples/transmission_with_raman_main_example.py | 3 +-- gnpy/core/network.py | 12 +++++++++++- gnpy/core/science_utils.py | 12 +----------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py index 90bdf4280..0093e1eb5 100755 --- a/examples/transmission_with_raman_main_example.py +++ b/examples/transmission_with_raman_main_example.py @@ -22,11 +22,10 @@ from matplotlib.pyplot import show, axis, figure, title, text from networkx import (draw_networkx_nodes, draw_networkx_edges, draw_networkx_labels, dijkstra_path) -from gnpy.core.network import load_network, build_network, save_network +from gnpy.core.network import load_network, build_network, save_network, load_sim_params, configure_network from gnpy.core.elements import Transceiver, Fiber, RamanFiber, Edfa, Roadm from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power, Pref from gnpy.core.request import Path_request, RequestParams, compute_constrained_path, propagate2 -from gnpy.core.science_utils import load_sim_params, configure_network logger = getLogger(__name__) diff --git a/gnpy/core/network.py b/gnpy/core/network.py index 8ea813252..dd1ce0fb7 100644 --- a/gnpy/core/network.py +++ b/gnpy/core/network.py @@ -16,12 +16,13 @@ from os import path from operator import itemgetter, attrgetter from gnpy.core import elements -from gnpy.core.elements import Fiber, Edfa, Transceiver, Roadm, Fused +from gnpy.core.elements import Fiber, Edfa, Transceiver, Roadm, Fused, RamanFiber from gnpy.core.equipment import edfa_nf from gnpy.core.exceptions import ConfigurationError, NetworkTopologyError from gnpy.core.units import UNITS from gnpy.core.utils import (load_json, save_json, round2float, db2lin, merge_amplifier_restrictions) +from gnpy.core.science_utils import SimParams from collections import namedtuple logger = getLogger(__name__) @@ -548,3 +549,12 @@ def build_network(network, equipment, pref_ch_db, pref_total_db): trx = [t for t in network.nodes() if isinstance(t, Transceiver)] for t in trx: set_egress_amplifier(network, t, equipment, pref_total_db) + +def load_sim_params(filename): + sim_params = load_json(filename) + return SimParams(params=sim_params) + +def configure_network(network, sim_params): + for node in network.nodes: + if isinstance(node, RamanFiber): + node.sim_params = sim_params diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index aaa53c797..af8e81849 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -8,7 +8,7 @@ from scipy.interpolate import interp1d from scipy.optimize import OptimizeResult -from gnpy.core.utils import db2lin, load_json +from gnpy.core.utils import db2lin logger = getLogger(__name__) @@ -149,16 +149,6 @@ def alpha0(self, f_ref=193.5e12): alpha0 = alpha_interp(f_ref) return alpha0 -def load_sim_params(path_sim_params): - sim_params = load_json(path_sim_params) - return SimParams(params=sim_params) - -def configure_network(network, sim_params): - from gnpy.core.elements import RamanFiber - for node in network.nodes: - if isinstance(node, RamanFiber): - node.sim_params = sim_params - pump = namedtuple('RamanPump', 'power frequency propagation_direction') def propagate_raman_fiber(fiber, *carriers): From 2f52c11589253b3d3178013054999c7cde29d71a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 8 Aug 2019 11:34:33 +0200 Subject: [PATCH 097/117] More intuitive name for list of channels where Raman gain is computed --- examples/sim_params.json | 2 +- gnpy/core/science_utils.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/sim_params.json b/examples/sim_params.json index 0c51529c2..62395ef31 100644 --- a/examples/sim_params.json +++ b/examples/sim_params.json @@ -1,5 +1,5 @@ { - "list_of_channels_under_test": [1, 18, 37, 56, 75], + "raman_computed_channels": [1, 18, 37, 56, 75], "raman_parameters": { "flag_raman": true, "space_resolution": 10e3, diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index af8e81849..54675f579 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -75,13 +75,13 @@ def f_pump_resolution(self, f_pump_resolution): class SimParams(): def __init__(self, params): - self._list_of_channels_under_test = params['list_of_channels_under_test'] + self._raman_computed_channels = params['raman_computed_channels'] self._raman_params = RamanParams(params=params['raman_parameters']) self._nli_params = NLIParams(params=params['nli_parameters']) @property - def list_of_channels_under_test(self): - return self._list_of_channels_under_test + def raman_computed_channels(self): + return self._raman_computed_channels @property def raman_params(self): @@ -198,7 +198,7 @@ def propagate_raman_fiber(fiber, *carriers): nli_params.f_cut_resolution = f_cut_resolution nli_params.f_pump_resolution = f_pump_resolution pwr = carrier.power - if carrier.channel_number in sim_params.list_of_channels_under_test: + if carrier.channel_number in sim_params.raman_computed_channels: carrier_nli = nli_solver.compute_nli(carrier, *carriers) else: carrier_nli = np.nan From 81585c5a862e1c32399c8debabca24a822274ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 8 Aug 2019 11:54:01 +0200 Subject: [PATCH 098/117] Unify implementations of psi computation Both of these places referred to "eq. 123 from arXiv:1209.0394", the only difference (apart from the source of the input parameters, beta2 and asymptotic_length) was calling the two branches "SCI" and "XCI" vs. "SPM" and "XPM". In this commit I've only moved the code to a single implementation. The input data are still being read from the same parameters, of course. --- gnpy/core/elements.py | 19 +++---------------- gnpy/core/science_utils.py | 33 +++++++++++++++------------------ 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index f461a2e55..44ac6824f 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -18,7 +18,7 @@ unique identifier and a printable name. ''' -from numpy import abs, arange, arcsinh, array, exp, divide, errstate +from numpy import abs, arange, array, exp, divide, errstate from numpy import interp, log10, mean, pi, polyfit, polyval, sum from scipy.constants import c, h from collections import namedtuple @@ -26,7 +26,7 @@ from gnpy.core.node import Node from gnpy.core.units import UNITS from gnpy.core.utils import lin2db, db2lin, itufs, itufl, snr_sum -from gnpy.core.science_utils import propagate_raman_fiber +from gnpy.core.science_utils import propagate_raman_fiber, _psi class Transceiver(Node): def __init__(self, *args, **kwargs): @@ -355,19 +355,6 @@ def dbkm_2_lin(self): alpha_acoef = alpha_pcoef / (2 * 10 * log10(exp(1))) return alpha_pcoef, alpha_acoef - def _psi(self, carrier, interfering_carrier): - """Calculates eq. 123 from `arXiv:1209.0394 `__""" - if carrier.channel_number == interfering_carrier.channel_number: # SCI - psi = arcsinh(0.5 * pi**2 * self.asymptotic_length - * abs(self.beta2()) * carrier.baud_rate**2) - else: # XCI - delta_f = carrier.frequency - interfering_carrier.frequency - psi = arcsinh(pi**2 * self.asymptotic_length * abs(self.beta2()) - * carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate)) - psi -= arcsinh(pi**2 * self.asymptotic_length * abs(self.beta2()) - * carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) - return psi - def _gn_analytic(self, carrier, *carriers): """Computes the nonlinear interference power on a single carrier. The method uses eq. 120 from `arXiv:1209.0394 `__. @@ -379,7 +366,7 @@ def _gn_analytic(self, carrier, *carriers): g_nli = 0 for interfering_carrier in carriers: - psi = self._psi(carrier, interfering_carrier) + psi = _psi(carrier, interfering_carrier, beta2=self.beta2(), asymptotic_length=self.asymptotic_length) g_nli += (interfering_carrier.power.signal/interfering_carrier.baud_rate)**2 \ * (carrier.power.signal/carrier.baud_rate) * psi diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 54675f579..6cdace5f0 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -677,29 +677,13 @@ def _gn_analytic(self, carrier, *carriers): for interfering_carrier in carriers: g_interfearing = interfering_carrier.power.signal / interfering_carrier.baud_rate g_signal = carrier.power.signal / carrier.baud_rate - g_nli += g_interfearing**2 * g_signal * self._psi(carrier, interfering_carrier) + g_nli += g_interfearing**2 * g_signal \ + * _psi(carrier, interfering_carrier, beta2=self.fiber_params.beta2, asymptotic_length=1/self.fiber_params.alpha0()) g_nli *= (16.0 / 27.0) * (gamma * effective_length)**2 /\ (2 * np.pi * abs(beta2) * asymptotic_length) carrier_nli = carrier.baud_rate * g_nli return carrier_nli - def _psi(self, carrier, interfering_carrier): - """ Calculates eq. 123 from arXiv:1209.0394. - """ - alpha = self.fiber_params.alpha0() / 2 - beta2 = self.fiber_params.beta2 - asymptotic_length = 1 / (2 * alpha) - - if carrier.channel_number == interfering_carrier.channel_number: # SPM - psi = np.arcsinh(0.5 * np.pi**2 * asymptotic_length * abs(beta2) * carrier.baud_rate**2) - else: # XPM - delta_f = carrier.frequency - interfering_carrier.frequency - psi = np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * - carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate)) - psi -= np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * - carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) - return psi - # Methods for computing the GGN-model def _generalized_spectrally_separated_spm(self, carrier): f_cut_resolution = self.nli_params.f_cut_resolution['delta_0'] @@ -817,3 +801,16 @@ def _frequency_offset_threshold(self, symbol_rate): rs_ref = 32e9 freq_offset_th = ((k_ref * delta_f_ref) * rs_ref * beta2_ref) / (self.fiber_params.beta2 * symbol_rate) return freq_offset_th + +def _psi(carrier, interfering_carrier, beta2, asymptotic_length): + """Calculates eq. 123 from `arXiv:1209.0394 `__""" + + if carrier.channel_number == interfering_carrier.channel_number: # SCI, SPM + psi = np.arcsinh(0.5 * np.pi**2 * asymptotic_length * abs(beta2) * carrier.baud_rate**2) + else: # XCI, XPM + delta_f = carrier.frequency - interfering_carrier.frequency + psi = np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * + carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate)) + psi -= np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) * + carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) + return psi From 182929cc96653007389d5a551121f33d844c267b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 8 Aug 2019 12:15:09 +0200 Subject: [PATCH 099/117] Unify transmission_main_example and the Raman wrapper There were just these substantial differences: - the Raman code showed a per-channel SNR summary, this is now controlled via the `--show-channels` option - when a Raman fiber is used the `--sim` option for specifying input simulation parameters is now mandatory I'm therefore merging these two files even though we've rpeviously decided not to do this -- consult the review comment at https://github.com/Telecominfraproject/oopt-gnpy/pull/263#discussion_r310506082 and the discussion during this Tuesday's coders call). If this turns out to be a problem for autodesign, we can always revert this. One possible catch is that the final "SNR total" shows NaN for the default Raman example. That's just the way the simulation engine works right now, I'm afraid. The `--show-channels` options helps a lot. --- .travis.yml | 2 +- examples/transmission_main_example.py | 34 +- .../transmission_with_raman_main_example.py | 300 ------------------ 3 files changed, 29 insertions(+), 307 deletions(-) delete mode 100755 examples/transmission_with_raman_main_example.py diff --git a/.travis.yml b/.travis.yml index de6ea2159..94229aea0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ script: - rstcheck --ignore-roles cite --ignore-directives automodule --recursive --ignore-messages '(Duplicate explicit target name.*)' . - ./examples/transmission_main_example.py - ./examples/path_requests_run.py - - ./examples/transmission_with_raman_main_example.py + - ./examples/transmission_main_example.py examples/raman_edfa_example_network.json --sim examples/sim_params.json --show-channels - sphinx-build docs/ x-throwaway-location after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/examples/transmission_main_example.py b/examples/transmission_main_example.py index b5c0da59c..efc938879 100755 --- a/examples/transmission_main_example.py +++ b/examples/transmission_main_example.py @@ -18,12 +18,12 @@ from json import loads from collections import Counter from logging import getLogger, basicConfig, INFO, ERROR, DEBUG -from numpy import linspace, mean +from numpy import linspace, mean, log10, isnan from matplotlib.pyplot import show, axis, figure, title, text from networkx import (draw_networkx_nodes, draw_networkx_edges, draw_networkx_labels, dijkstra_path) -from gnpy.core.network import load_network, build_network, save_network -from gnpy.core.elements import Transceiver, Fiber, Edfa, Roadm +from gnpy.core.network import load_network, build_network, save_network, load_sim_params, configure_network +from gnpy.core.elements import Transceiver, Fiber, RamanFiber, Edfa, Roadm from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power, Pref from gnpy.core.request import Path_request, RequestParams, compute_constrained_path, propagate2 from gnpy.core.exceptions import ConfigurationError, EquipmentConfigError, NetworkTopologyError @@ -99,7 +99,7 @@ def hover(event): show() -def main(network, equipment, source, destination, req = None): +def main(network, equipment, source, destination, sim_params, req=None): result_dicts = {} network_data = [{ 'network_name' : str(args.filename), @@ -126,7 +126,13 @@ def main(network, equipment, source, destination, req = None): build_network(network, equipment, pref_ch_db, pref_total_db) path = compute_constrained_path(network, req) - spans = [s.length for s in path if isinstance(s, Fiber)] + if len([s.length for s in path if isinstance(s, RamanFiber)]): + if sim_params is None: + print(f'{ansi_escapes.red}Invocation error:{ansi_escapes.reset} RamanFiber requires passing simulation params via --sim-params') + exit(1) + configure_network(network, sim_params) + + spans = [s.length for s in path if isinstance(s, RamanFiber) or isinstance(s, Fiber)] print(f'\nThere are {len(spans)} fiber spans over {sum(spans):.0f}m between {source.uid} and {destination.uid}') print(f'\nNow propagating between {source.uid} and {destination.uid}:') @@ -183,6 +189,9 @@ def main(network, equipment, source, destination, req = None): parser = ArgumentParser() parser.add_argument('-e', '--equipment', type=Path, default=Path(__file__).parent / 'eqpt_config.json') +parser.add_argument('--sim-params', type=Path, + default=None, help='Path to the JSON containing simulation parameters (required for Raman)') +parser.add_argument('--show-channels', action='store_true', help='Show final per-channel OSNR summary') parser.add_argument('-pl', '--plot', action='store_true') parser.add_argument('-v', '--verbose', action='count', default=0, help='increases verbosity for each occurence') parser.add_argument('-l', '--list-nodes', action='store_true', help='list all transceiver nodes') @@ -201,6 +210,7 @@ def main(network, equipment, source, destination, req = None): try: equipment = load_equipment(args.equipment) network = load_network(args.filename, equipment, args.names_matching) + sim_params = load_sim_params(args.sim_params) if args.sim_params is not None else None except EquipmentConfigError as e: print(f'{ansi_escapes.red}Configuration error in the equipment library:{ansi_escapes.reset} {e}') exit(1) @@ -278,9 +288,21 @@ def main(network, equipment, source, destination, req = None): trx_params['power'] = db2lin(float(args.power))*1e-3 params.update(trx_params) req = Path_request(**params) - path, infos = main(network, equipment, source, destination, req) + path, infos = main(network, equipment, source, destination, sim_params, req) save_network(args.filename, network) + if args.show_channels: + print('\nThe total SNR per channel at the end of the line is:') + print('Ch. # \t Channel frequency (THz) \t SNR NL (signal bw, dB) \t SNR total (signal bw, dB)') + for final_carrier in infos[path[-1]][1].carriers: + ch_freq = final_carrier.frequency * 1e-12 + ch_power = 10 * log10(final_carrier.power.signal) + ch_snr_nl = ch_power - 10 * log10(final_carrier.power.nli) + ch_snr = ch_power - 10 * log10(final_carrier.power.nli + final_carrier.power.ase) + if not isnan(ch_snr): + print(f'{final_carrier.channel_number} \t\t {round(ch_freq, 2):.2f} \t\t\t {round(ch_snr_nl, 2):.2f} ' + f'\t\t\t\t {round(ch_snr, 2):.2f}') + if not args.source: print(f'\n(No source node specified: picked {source.uid})') elif not valid_source: diff --git a/examples/transmission_with_raman_main_example.py b/examples/transmission_with_raman_main_example.py deleted file mode 100755 index 0093e1eb5..000000000 --- a/examples/transmission_with_raman_main_example.py +++ /dev/null @@ -1,300 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -''' -transmission_with_raman_main_example.py -============================ - -Main example for transmission simulation. - -Reads from network JSON (by default, `raman_edfa_example_network.json`) -''' - -from gnpy.core.equipment import load_equipment, trx_mode_params -from gnpy.core.utils import db2lin, lin2db, write_csv -from argparse import ArgumentParser -from sys import exit -from pathlib import Path -from json import loads -from collections import Counter -from logging import getLogger, basicConfig, INFO, ERROR, DEBUG -from numpy import linspace, mean, log10, isnan -from matplotlib.pyplot import show, axis, figure, title, text -from networkx import (draw_networkx_nodes, draw_networkx_edges, - draw_networkx_labels, dijkstra_path) -from gnpy.core.network import load_network, build_network, save_network, load_sim_params, configure_network -from gnpy.core.elements import Transceiver, Fiber, RamanFiber, Edfa, Roadm -from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power, Pref -from gnpy.core.request import Path_request, RequestParams, compute_constrained_path, propagate2 - -logger = getLogger(__name__) - -def plot_baseline(network): - edges = set(network.edges()) - pos = {n: (n.lng, n.lat) for n in network.nodes()} - labels = {n: n.location.city for n in network.nodes() if isinstance(n, Transceiver)} - city_labels = set(labels.values()) - for n in network.nodes(): - if n.location.city and n.location.city not in city_labels: - labels[n] = n.location.city - city_labels.add(n.location.city) - label_pos = pos - - fig = figure() - kwargs = {'figure': fig, 'pos': pos} - plot = draw_networkx_nodes(network, nodelist=network.nodes(), node_color='#ababab', **kwargs) - draw_networkx_edges(network, edgelist=edges, edge_color='#ababab', **kwargs) - draw_networkx_labels(network, labels=labels, font_size=14, **{**kwargs, 'pos': label_pos}) - axis('off') - show() - -def plot_results(network, path, source, destination, infos): - path_edges = set(zip(path[:-1], path[1:])) - edges = set(network.edges()) - path_edges - pos = {n: (n.lng, n.lat) for n in network.nodes()} - nodes = {} - for k, (x, y) in pos.items(): - nodes.setdefault((round(x, 1), round(y, 1)), []).append(k) - labels = {n: n.location.city for n in network.nodes() if isinstance(n, Transceiver)} - city_labels = set(labels.values()) - for n in network.nodes(): - if n.location.city and n.location.city not in city_labels: - labels[n] = n.location.city - city_labels.add(n.location.city) - label_pos = pos - - fig = figure() - kwargs = {'figure': fig, 'pos': pos} - all_nodes = [n for n in network.nodes() if n not in path] - plot = draw_networkx_nodes(network, nodelist=all_nodes, node_color='#ababab', node_size=50, **kwargs) - draw_networkx_nodes(network, nodelist=path, node_color='#ff0000', node_size=55, **kwargs) - draw_networkx_edges(network, edgelist=edges, edge_color='#ababab', **kwargs) - draw_networkx_edges(network, edgelist=path_edges, edge_color='#ff0000', **kwargs) - draw_networkx_labels(network, labels=labels, font_size=14, **{**kwargs, 'pos': label_pos}) - title(f'Propagating from {source.loc.city} to {destination.loc.city}') - axis('off') - - heading = 'Spectral Information\n\n' - textbox = text(0.85, 0.20, heading, fontsize=14, fontname='Ubuntu Mono', - verticalalignment='top', transform=fig.axes[0].transAxes, - bbox={'boxstyle': 'round', 'facecolor': 'wheat', 'alpha': 0.5}) - - msgs = {(x, y): heading + '\n\n'.join(str(n) for n in ns if n in path) - for (x, y), ns in nodes.items()} - - def hover(event): - if event.xdata is None or event.ydata is None: - return - if fig.contains(event): - x, y = round(event.xdata, 1), round(event.ydata, 1) - if (x, y) in msgs: - textbox.set_text(msgs[x, y]) - else: - textbox.set_text(heading) - fig.canvas.draw_idle() - - fig.canvas.mpl_connect('motion_notify_event', hover) - show() - - -def main(network, equipment, source, destination, sim_params, req = None): - result_dicts = {} - network_data = [{ - 'network_name' : str(args.filename), - 'source' : source.uid, - 'destination' : destination.uid - }] - result_dicts.update({'network': network_data}) - design_data = [{ - '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 - }] - result_dicts.update({'design': design_data}) - simulation_data = [] - result_dicts.update({'simulation results': simulation_data}) - - 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 - 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) - build_network(network, equipment, pref_ch_db, pref_total_db) - configure_network(network, sim_params) - path = compute_constrained_path(network, req) - - spans = [s.length for s in path if isinstance(s, RamanFiber) or isinstance(s, Fiber)] - - print(f'\nThere are {len(spans)} fiber spans over {sum(spans):.0f}m between {source.uid} and {destination.uid}') - print(f'\nNow propagating between {source.uid} and {destination.uid}:') - - try: - p_start, p_stop, p_step = equipment['SI']['default'].power_range_db - p_num = abs(int(round((p_stop - p_start)/p_step))) + 1 if p_step != 0 else 1 - power_range = list(linspace(p_start, p_stop, p_num)) - except TypeError: - 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 - 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) - 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 - 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 - - -parser = ArgumentParser() -parser.add_argument('-e', '--equipment', type=Path, - default=Path(__file__).parent / 'eqpt_config.json') -parser.add_argument('-sim', '--sim-params', type=Path, - default=Path(__file__).parent / 'sim_params.json', help='Path to the json containing simulation parameters') -parser.add_argument('-pl', '--plot', action='store_true') -parser.add_argument('-v', '--verbose', action='count', default=0, help='increases verbosity for each occurence') -parser.add_argument('-l', '--list-nodes', action='store_true', help='list all transceiver nodes') -parser.add_argument('-po', '--power', default=0, help='channel ref power in dBm') -parser.add_argument('-names', '--names-matching', action='store_true', help='display network names that are closed matches') -parser.add_argument('filename', nargs='?', type=Path, - default=Path(__file__).parent / 'raman_edfa_example_network.json') -parser.add_argument('source', nargs='?', help='source node') -parser.add_argument('destination', nargs='?', help='destination node') - - -if __name__ == '__main__': - args = parser.parse_args() - basicConfig(level={0: ERROR, 1: INFO, 2: DEBUG}.get(args.verbose, DEBUG)) - - equipment = load_equipment(args.equipment) - network = load_network(args.filename, equipment, args.names_matching) - sim_params = load_sim_params(args.sim_params) - - if args.plot: - plot_baseline(network) - - transceivers = {n.uid: n for n in network.nodes() if isinstance(n, Transceiver)} - - if not transceivers: - exit('Network has no transceivers!') - if len(transceivers) < 2: - exit('Network has only one transceiver!') - - if args.list_nodes: - for uid in transceivers: - print(uid) - exit() - - #First try to find exact match if source/destination provided - if args.source: - source = transceivers.pop(args.source, None) - valid_source = True if source else False - else: - source = None - logger.info('No source node specified: picking random transceiver') - - if args.destination: - destination = transceivers.pop(args.destination, None) - valid_destination = True if destination else False - else: - destination = None - logger.info('No destination node specified: picking random transceiver') - - #If no exact match try to find partial match - if args.source and not source: - #TODO code a more advanced regex to find nodes match - source = next((transceivers.pop(uid) for uid in transceivers \ - if args.source.lower() in uid.lower()), None) - - if args.destination and not destination: - #TODO code a more advanced regex to find nodes match - destination = next((transceivers.pop(uid) for uid in transceivers \ - if args.destination.lower() in uid.lower()), None) - - #If no partial match or no source/destination provided pick random - if not source: - source = list(transceivers.values())[0] - del transceivers[source.uid] - - if not destination: - destination = list(transceivers.values())[0] - - logger.info(f'source = {args.source!r}') - logger.info(f'destination = {args.destination!r}') - - params = {} - params['request_id'] = 0 - params['trx_type'] = '' - params['trx_mode'] = '' - params['source'] = source.uid - params['destination'] = destination.uid - params['nodes_list'] = [destination.uid] - params['loose_list'] = ['strict'] - params['format'] = '' - params['path_bandwidth'] = 0 - trx_params = trx_mode_params(equipment) - if args.power: - trx_params['power'] = db2lin(float(args.power))*1e-3 - params.update(trx_params) - req = Path_request(**params) - path, infos = main(network, equipment, source, destination, sim_params, req) - save_network(args.filename, network) - - print('The total SNR per channel at the end of the line is:') - print('Ch. # \t Channel frequency (THz) \t SNR NL (signal bw, dB) \t SNR total (signal bw, dB)') - final_carriers = infos[path[-1]][1].carriers - for final_carrier in final_carriers: - ch_freq = final_carrier.frequency * 1e-12 - ch_power = 10 * log10(final_carrier.power.signal) - ch_snr_nl = ch_power - 10 * log10(final_carrier.power.nli) - ch_snr = ch_power - 10 * log10(final_carrier.power.nli + final_carrier.power.ase) - if not isnan(ch_snr): - print(f'{final_carrier.channel_number} \t\t {round(ch_freq, 2):.2f} \t\t\t {round(ch_snr_nl, 2):.2f} ' - f'\t\t\t\t {round(ch_snr, 2):.2f}') - - if not args.source: - print(f'\n(No source node specified: picked {source.uid})') - elif not valid_source: - print(f'\n(Invalid source node {args.source!r} replaced with {source.uid})') - - if not args.destination: - print(f'\n(No destination node specified: picked {destination.uid})') - elif not valid_destination: - print(f'\n(Invalid destination node {args.destination!r} replaced with {destination.uid})') - - if args.plot: - plot_results(network, path, source, destination, infos) From f1d0230dadf26573f61a4ac25f622e3dbe4d7c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 8 Aug 2019 12:58:16 +0200 Subject: [PATCH 100/117] docs: skeleton of Raman-specific properties What I'm including here are comments from Vittorio (thanks!). It seems that the majority of actual numeric parameters which drive the simulation are undocumented, so this one will have to wait for Alessio or someone else from the Polito team to fill the blanks. --- json_structure_description.rst | 114 ++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/json_structure_description.rst b/json_structure_description.rst index 93a1b3149..b8239d3a1 100644 --- a/json_structure_description.rst +++ b/json_structure_description.rst @@ -165,6 +165,52 @@ Fiber element with its parameters: } ] +RamanFiber element +****************** + +A special variant of the regular ``Fiber`` where the simulation engine accounts for the Raman effect. +The newly added parameters are nested in the ``raman_efficiency`` dictionary. +Its shape corresponds to typical properties of silica. +More details are available from :cite:`curri_merit_2016`. + +The ``cr`` property is the normailzed Raman efficiency, so it is is (almost) independent of the fiber type, while the coefficient actually giving Raman gain is g_R=C_R/Aeff. + +The ``frequency_offset`` represents the spectral difference between the pumping photon and the one receiving energy. + +.. code-block:: json-object + + "RamanFiber":[{ + "type_variety": "SSMF", + "dispersion": 1.67e-05, + "gamma": 0.00127, + "raman_efficiency": { + "cr":[ + 0, 9.4E-06, 2.92E-05, 4.88E-05, 6.82E-05, 8.31E-05, 9.4E-05, 0.0001014, 0.0001069, 0.0001119, + 0.0001217, 0.0001268, 0.0001365, 0.000149, 0.000165, 0.000181, 0.0001977, 0.0002192, 0.0002469, + 0.0002749, 0.0002999, 0.0003206, 0.0003405, 0.0003592, 0.000374, 0.0003826, 0.0003841, 0.0003826, + 0.0003802, 0.0003756, 0.0003549, 0.0003795, 0.000344, 0.0002933, 0.0002024, 0.0001158, 8.46E-05, + 7.14E-05, 6.86E-05, 8.5E-05, 8.93E-05, 9.01E-05, 8.15E-05, 6.67E-05, 4.37E-05, 3.28E-05, 2.96E-05, + 2.65E-05, 2.57E-05, 2.81E-05, 3.08E-05, 3.67E-05, 5.85E-05, 6.63E-05, 6.36E-05, 5.5E-05, 4.06E-05, + 2.77E-05, 2.42E-05, 1.87E-05, 1.6E-05, 1.4E-05, 1.13E-05, 1.05E-05, 9.8E-06, 9.8E-06, 1.13E-05, + 1.64E-05, 1.95E-05, 2.38E-05, 2.26E-05, 2.03E-05, 1.48E-05, 1.09E-05, 9.8E-06, 1.05E-05, 1.17E-05, + 1.25E-05, 1.21E-05, 1.09E-05, 9.8E-06, 8.2E-06, 6.6E-06, 4.7E-06, 2.7E-06, 1.9E-06, 1.2E-06, 4E-07, + 2E-07, 1E-07 + ], + "frequency_offset":[ + 0, 0.5e12, 1e12, 1.5e12, 2e12, 2.5e12, 3e12, 3.5e12, 4e12, 4.5e12, 5e12, 5.5e12, 6e12, 6.5e12, 7e12, + 7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 10e12, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12.75e12, + 13e12, 13.25e12, 13.5e12, 14e12, 14.5e12, 14.75e12, 15e12, 15.5e12, 16e12, 16.5e12, 17e12, + 17.5e12, 18e12, 18.25e12, 18.5e12, 18.75e12, 19e12, 19.5e12, 20e12, 20.5e12, 21e12, 21.5e12, + 22e12, 22.5e12, 23e12, 23.5e12, 24e12, 24.5e12, 25e12, 25.5e12, 26e12, 26.5e12, 27e12, 27.5e12, 28e12, + 28.5e12, 29e12, 29.5e12, 30e12, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12, + 35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 40e12, 40.5e12, 41e12, + 41.5e12, 42e12 + ] + } + } + ] + + 1.2.3 Roadm element ******************* @@ -406,8 +452,51 @@ Fiber element with its parameters. } } +2.2.5. RamanFiber element +************************* -2.2.5. EDFA element +.. code-block:: json + + { + "uid": "Span1", + "type": "RamanFiber", + "type_variety": "SSMF", + "operational": { + "temperature": 283, + "raman_pumps": [ + { + "power": 200e-3, + "frequency": 205e12, + "propagation_direction": "counterprop" + }, + { + "power": 206e-3, + "frequency": 201e12, + "propagation_direction": "counterprop" + } + ] + }, + "params": { + "type_variety": "SSMF", + "length": 80.0, + "loss_coef": 0.2, + "length_units": "km", + "att_in": 0, + "con_in": 0.5, + "con_out": 0.5 + }, + "metadata": { + "location": { + "latitude": 1, + "longitude": 0, + "city": null, + "region": "" + } + } + } + + +2.2.6. EDFA element ******************** EDFA element with its parameters. @@ -443,3 +532,26 @@ corresponding to element **”uid”** {"from_node": "roadm Site_C", "to_node": "trx Site_C" } + +************************ +3. Simulation Parameters +************************ + +Additional details of the simulation are controlled via ``sim_params.json``: + +.. code-block:: json + + { + "raman_computed_channels": [1, 18, 37, 56, 75], + "raman_parameters": { + "flag_raman": true, + "space_resolution": 10e3, + "tolerance": 1e-8 + }, + "nli_parameters": { + "nli_method_name": "ggn_spectrally_separated", + "wdm_grid_size": 50e9, + "dispersion_tolerance": 1, + "phase_shift_tollerance": 0.1 + } + } From 3771c13d32bdded9d818cb61d920a07c4d72f9d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 8 Aug 2019 13:08:10 +0200 Subject: [PATCH 101/117] docs: basic usage instructions for Raman --- README.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.rst b/README.rst index b26bffdee..3d050c062 100644 --- a/README.rst +++ b/README.rst @@ -475,6 +475,17 @@ Launch power can be overridden by using the ``--power`` argument. Spectrum information is not yet parametrized but can be modified directly in the ``eqpt_config.json`` (via the ``SpectralInformation`` -SI- structure) to accommodate any baud rate or spacing. The number of channel is computed based on ``spacing`` and ``f_min``, ``f_max`` values. +An experimental support for Raman amplification is available: + +.. code-block:: shell + + $ ./examples/transmission_main_example.py \ + examples/raman_edfa_example_network.json \ + --sim examples/sim_params.json --show-channels + +Configuration of Raman pumps (their frequencies, power and pumping direction) is done via the `RamanFiber element in the network topology `_. +General numeric parameters for simulaiton control are provided in the `examples/sim_params.json `_. + Use `examples/path_requests_run.py `_ to run multiple optimizations as follows: .. code-block:: shell From 54bf426472dc740a5d94e553398b33f3a2a6b017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Sun, 1 Sep 2019 21:59:35 +0200 Subject: [PATCH 102/117] Raman: linear interpolation of channel NLI The Raman engine computes NLI just for a subset of channels; this is an important speed optimization because the computation is rather CPU heavy. However, the code just left NaNs in place for NLI of those channels which were not explicitly simulated. This is wrong because these NaNs propagate all the way to the total input/output powers per the whole spectrum, etc, leading to NaNs being shown to the user. This patch uses a very simple linear approximation just in order to prevent these NaNs. fixes #288 --- gnpy/core/science_utils.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/gnpy/core/science_utils.py b/gnpy/core/science_utils.py index 6cdace5f0..ba7a2653f 100644 --- a/gnpy/core/science_utils.py +++ b/gnpy/core/science_utils.py @@ -191,17 +191,21 @@ def propagate_raman_fiber(fiber, *carriers): attenuation_out = db2lin(fiber.con_out) nli_solver = NliSolver(nli_params=nli_params, fiber_params=fiber_params) nli_solver.stimulated_raman_scattering = stimulated_raman_scattering - new_carriers = [] - for carrier, attenuation, rmn_ase in zip(carriers, fiber_attenuation, raman_ase): + + nli_frequencies = [] + computed_nli = [] + for carrier in (c for c in carriers if c.channel_number in sim_params.raman_computed_channels): resolution_param = frequency_resolution(carrier, carriers, sim_params, fiber_params) f_cut_resolution, f_pump_resolution, _, _ = resolution_param nli_params.f_cut_resolution = f_cut_resolution nli_params.f_pump_resolution = f_pump_resolution + nli_frequencies.append(carrier.frequency) + computed_nli.append(nli_solver.compute_nli(carrier, *carriers)) + + new_carriers = [] + for carrier, attenuation, rmn_ase in zip(carriers, fiber_attenuation, raman_ase): + carrier_nli = np.interp(carrier.frequency, nli_frequencies, computed_nli) pwr = carrier.power - if carrier.channel_number in sim_params.raman_computed_channels: - carrier_nli = nli_solver.compute_nli(carrier, *carriers) - else: - carrier_nli = np.nan pwr = pwr._replace(signal=pwr.signal/attenuation/attenuation_out, nli=(pwr.nli+carrier_nli)/attenuation/attenuation_out, ase=((pwr.ase/attenuation)+rmn_ase)/attenuation_out) From 6f93b64f842d549e319c8b666b61fbf0960dfdf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Mon, 2 Sep 2019 18:19:37 +0200 Subject: [PATCH 103/117] Simplify logic for showing per-channel results As Jonas pointed out, the code used to contain a check for non-nan values, effectively skipping channels where the Raman gain was not explicitly computed. Now that we do not introduce NaNs into some channels anymore, this shortcut no longer works. We could either add explicit filtering for only showing those channels which are covered by the Raman engine, but then the result would be rather confusing in the non-Raman case. One could also add another column with the simulated vs. approximated NLI, but when I tried this, the output looked a bit cluttered. I think that the best course of action for now is to just show info about all channels (if asked by the user). So this is just a cleanup for a condition which is now always on. --- examples/transmission_main_example.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/transmission_main_example.py b/examples/transmission_main_example.py index efc938879..e4510e01b 100755 --- a/examples/transmission_main_example.py +++ b/examples/transmission_main_example.py @@ -18,7 +18,7 @@ from json import loads from collections import Counter from logging import getLogger, basicConfig, INFO, ERROR, DEBUG -from numpy import linspace, mean, log10, isnan +from numpy import linspace, mean, log10 from matplotlib.pyplot import show, axis, figure, title, text from networkx import (draw_networkx_nodes, draw_networkx_edges, draw_networkx_labels, dijkstra_path) @@ -299,9 +299,8 @@ def main(network, equipment, source, destination, sim_params, req=None): ch_power = 10 * log10(final_carrier.power.signal) ch_snr_nl = ch_power - 10 * log10(final_carrier.power.nli) ch_snr = ch_power - 10 * log10(final_carrier.power.nli + final_carrier.power.ase) - if not isnan(ch_snr): - print(f'{final_carrier.channel_number} \t\t {round(ch_freq, 2):.2f} \t\t\t {round(ch_snr_nl, 2):.2f} ' - f'\t\t\t\t {round(ch_snr, 2):.2f}') + print(f'{final_carrier.channel_number} \t\t {round(ch_freq, 2):.2f} \t\t\t {round(ch_snr_nl, 2):.2f} ' + f'\t\t\t\t {round(ch_snr, 2):.2f}') if not args.source: print(f'\n(No source node specified: picked {source.uid})') From 2faf8d2cdd293307b168c1a744992f1b30fc64ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=A5rtensson?= Date: Tue, 3 Sep 2019 09:25:09 +0200 Subject: [PATCH 104/117] - Fix output per-channel SNR to include contribution from add_drop_sonr and tx_osnr - Avoid recalculating SNRs in transmission main example since they are already calculated and stored in the Transceiver class - Also include per channel power (useful for checking tilt) and OSNR ASE in the output --- examples/transmission_main_example.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/transmission_main_example.py b/examples/transmission_main_example.py index e4510e01b..12a417a84 100755 --- a/examples/transmission_main_example.py +++ b/examples/transmission_main_example.py @@ -293,14 +293,13 @@ def main(network, equipment, source, destination, sim_params, req=None): if args.show_channels: print('\nThe total SNR per channel at the end of the line is:') - print('Ch. # \t Channel frequency (THz) \t SNR NL (signal bw, dB) \t SNR total (signal bw, dB)') - for final_carrier in infos[path[-1]][1].carriers: + print('{:>5}{:>26}{:>26}{:>28}{:>28}{:>28}' \ + .format('Ch. #', 'Channel frequency (THz)', 'Channel power (dBm)', 'OSNR ASE (signal bw, dB)', 'SNR NLI (signal bw, dB)', 'SNR total (signal bw, dB)')) + for final_carrier, ch_osnr, ch_snr_nl, ch_snr in zip(infos[path[-1]][1].carriers, path[-1].osnr_ase, path[-1].osnr_nli, path[-1].snr): ch_freq = final_carrier.frequency * 1e-12 - ch_power = 10 * log10(final_carrier.power.signal) - ch_snr_nl = ch_power - 10 * log10(final_carrier.power.nli) - ch_snr = ch_power - 10 * log10(final_carrier.power.nli + final_carrier.power.ase) - print(f'{final_carrier.channel_number} \t\t {round(ch_freq, 2):.2f} \t\t\t {round(ch_snr_nl, 2):.2f} ' - f'\t\t\t\t {round(ch_snr, 2):.2f}') + ch_power = lin2db(final_carrier.power.signal*1e3) + print('{:5}{:26.2f}{:26.2f}{:28.2f}{:28.2f}{:28.2f}' \ + .format(final_carrier.channel_number, round(ch_freq, 2), round(ch_power, 2), round(ch_osnr, 2), round(ch_snr_nl, 2), round(ch_snr, 2))) if not args.source: print(f'\n(No source node specified: picked {source.uid})') From 25b4d0e755b6e462aae99e3ad8f061806d9d5ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 17 Sep 2019 20:37:45 +0200 Subject: [PATCH 105/117] Automatic building of Docker images A third, independent job in Travis CI just for building a container image (and testing it a little bit). Here's how to use it once it is built and published and pulled: $ docker run -it --rm --volume $(pwd):/shared telecominfraproject/oopt-gnpy One can also pass commands to it directly. Note that the *relative* path specifier given as `./` is required. One could possibly get around it by $PATH manipulation, but hey, I have to stop somewhere. $ docker run -it --rm --volume $(pwd):/shared telecominfraproject/oopt-gnpy ./transmission_main_example.py Thanks to Jonas for the --volume option trick and for that early non-overwriting copying for the example directory. The `install` phase in Travis CI is apparently common for all jobs in the build matrix (it's rather confusing what they call a "stage" and what is a "job" for them, and what their mutual relation is). Because there's no point in doing any kind of installation in case when we're building for Docker, let's just move the old `install` block into `script`. With just that, however, Travis adds an implicit `pip install -r requirements.txt` in an attempt at being helpful. In short, having completely different "stuff to be done as a check" is very painful with Travis. The individual "script lines" in a job's `script` section are always executed, all of them, even if a previous one failed. That's why I moved the actual Docker invocation into an external shell script. When they were not in a shell script and the script lines contained `set -e`, then a failure of a particular command would cause the build to be marked as "errored" instead of "failed" within Travis. Also, the multiline if conditionals were rather painful to write. One commit might end up in different branches. If that happens, the second build would overwrite the image tag in the docker registry, which is rather suboptimal. Let's try to fetch that image first, and only update the latest/stable tags if the image was already available beforehand. fixes #260 --- .docker-entry.sh | 3 +++ .docker-travis.sh | 47 +++++++++++++++++++++++++++++++++++++++++++++++ .travis.yml | 13 ++++++++++--- Dockerfile | 7 +++++++ 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100755 .docker-entry.sh create mode 100755 .docker-travis.sh create mode 100644 Dockerfile diff --git a/.docker-entry.sh b/.docker-entry.sh new file mode 100755 index 000000000..0a3c93454 --- /dev/null +++ b/.docker-entry.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cp -nr /oopt-gnpy/examples /shared +exec "$@" diff --git a/.docker-travis.sh b/.docker-travis.sh new file mode 100755 index 000000000..efc5a5137 --- /dev/null +++ b/.docker-travis.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +set -e + +IMAGE_NAME=telecominfraproject/oopt-gnpy +IMAGE_TAG=$(git describe --tags) + +ALREADY_FOUND=0 +docker pull ${IMAGE_NAME}:${IMAGE_TAG} && ALREADY_FOUND=1 + +if [[ $ALREADY_FOUND == 0 ]]; then + docker build . -t ${IMAGE_NAME} + docker tag ${IMAGE_NAME} ${IMAGE_NAME}:${IMAGE_TAG} + + # shared directory setup: do not clobber the real data + mkdir trash + cd trash + docker run -it --rm --volume $(pwd):/shared ${IMAGE_NAME} ./transmission_main_example.py +else + echo "Image ${IMAGE_NAME}:${IMAGE_TAG} already available, will just update the other tags" +fi + +docker images + +do_docker_login() { + echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin +} + +if [[ "${TRAVIS_PULL_REQUEST}" == "false" ]]; then + if [[ "${TRAVIS_BRANCH}" == "develop" || "${TRAVIS_BRANCH}" == "docker" ]]; then + echo "Publishing latest" + docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest + do_docker_login + if [[ $ALREADY_FOUND == 0 ]]; then + docker push ${IMAGE_NAME}:${IMAGE_TAG} + fi + docker push ${IMAGE_NAME}:latest + elif [[ "${TRAVIS_BRANCH}" == "master" ]]; then + echo "Publishing stable" + docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:stable + do_docker_login + if [[ $ALREADY_FOUND == 0 ]]; then + docker push ${IMAGE_NAME}:${IMAGE_TAG} + fi + docker push ${IMAGE_NAME}:stable + fi +fi diff --git a/.travis.yml b/.travis.yml index 94229aea0..680ed1888 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,14 @@ dist: xenial sudo: false language: python +services: docker python: - "3.6" - "3.7" -# command to install dependencies -install: +install: skip +script: - python setup.py install - pip install pytest-cov rstcheck -script: - pytest --cov-report=xml --cov=gnpy - rstcheck --ignore-roles cite --ignore-directives automodule --recursive --ignore-messages '(Duplicate explicit target name.*)' . - ./examples/transmission_main_example.py @@ -17,3 +17,10 @@ script: - sphinx-build docs/ x-throwaway-location after_success: - bash <(curl -s https://codecov.io/bash) +jobs: + include: + - stage: test + name: Docker image + script: + - ./.docker-travis.sh + - docker images diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..843b85a3a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.7-slim +COPY . /oopt-gnpy +WORKDIR /oopt-gnpy +RUN python setup.py install +WORKDIR /shared/examples +ENTRYPOINT ["/oopt-gnpy/.docker-entry.sh"] +CMD "/bin/bash" From dbe2bf560c5347337d1a5a110d601f57466e1170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 17 Sep 2019 21:03:58 +0200 Subject: [PATCH 106/117] Travis: Docker: Fix version numbers This is due to travis-ci/travis-ci@7422. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 680ed1888..858f3865b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,5 +22,6 @@ jobs: - stage: test name: Docker image script: + - git fetch --unshallow - ./.docker-travis.sh - docker images From 325721545eb39a6e6733a7458c82edff1c890467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 18 Sep 2019 13:15:20 +0200 Subject: [PATCH 107/117] Docker: array for CMD is more idiomatic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Jonas Mårtensson --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 843b85a3a..a063725db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,4 +4,4 @@ WORKDIR /oopt-gnpy RUN python setup.py install WORKDIR /shared/examples ENTRYPOINT ["/oopt-gnpy/.docker-entry.sh"] -CMD "/bin/bash" +CMD ["/bin/bash"] From acafc78456a2701cfed210a2d3037cc6a9e6e0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 18 Sep 2019 20:04:17 +0200 Subject: [PATCH 108/117] Remove debug printing from propagate() This is inspired by #293. The original issue was that the transponder OSNR was not accounted for. Rather than making the propagate() and propagate2() more complex, let's move the actual path printing to the demo code. There's no secret sauce in there, it's just a simple for-each-print thingy, so let's remove it from the library code. fixes #262 --- examples/path_requests_run.py | 4 ++-- examples/transmission_main_example.py | 5 ++++- gnpy/core/request.py | 12 +++--------- tests/test_automaticmodefeature.py | 2 +- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/examples/path_requests_run.py b/examples/path_requests_run.py index 32b28180f..e70fe0d2d 100755 --- a/examples/path_requests_run.py +++ b/examples/path_requests_run.py @@ -168,7 +168,7 @@ def compute_path(network, equipment, pathreqlist): print(f'Computed path (roadms):{[e.uid for e in total_path if isinstance(e, Roadm)]}\n') if total_path : - total_path = propagate(total_path,pathreq,equipment, show=False) + total_path = propagate(total_path,pathreq,equipment) else: total_path = [] # we record the last tranceiver object in order to have th whole @@ -205,7 +205,7 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist): # print(f'{pathreq.baud_rate} {pathreq.power} {pathreq.spacing} {pathreq.nb_channel}') if total_path : if pathreq.baud_rate is not None: - total_path = propagate(total_path,pathreq,equipment, show=False) + total_path = propagate(total_path,pathreq,equipment) temp_snr01nm = round(mean(total_path[-1].snr+lin2db(pathreq.baud_rate/(12.5e9))),2) if temp_snr01nm < pathreq.OSNR : msg = f'\tWarning! Request {pathreq.request_id} computed path from {pathreq.source} to {pathreq.destination} does not pass with {pathreq.tsp_mode}\n' +\ diff --git a/examples/transmission_main_example.py b/examples/transmission_main_example.py index 12a417a84..eab66bd2a 100755 --- a/examples/transmission_main_example.py +++ b/examples/transmission_main_example.py @@ -153,7 +153,10 @@ def main(network, equipment, source, destination, sim_params, req=None): 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) + infos = propagate2(path, req, equipment) + if len(power_range) == 1: + for elem in path[:-1]: + print(elem) if power_mode: print(f'\nTransmission result for input power = {lin2db(req.power*1e3):.2f}dBm :') else: diff --git a/gnpy/core/request.py b/gnpy/core/request.py index 7f2a1e350..ffbf071a7 100644 --- a/gnpy/core/request.py +++ b/gnpy/core/request.py @@ -393,18 +393,16 @@ def compute_constrained_path(network, req): return total_path -def propagate(path, req, equipment, show=False): +def propagate(path, req, equipment): si = create_input_spectral_information( 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['Roadm']['default'].add_drop_osnr) return path -def propagate2(path, req, equipment, show=False): +def propagate2(path, req, equipment): si = create_input_spectral_information( req.f_min, req.f_max, req.roll_off, req.baud_rate, req.power, req.spacing) @@ -413,12 +411,10 @@ def propagate2(path, req, equipment, show=False): before_si = si after_si = si = el(si) infos[el] = before_si, after_si - if show : - print(el) path[-1].update_snr(req.tx_osnr, equipment['Roadm']['default'].add_drop_osnr) return infos -def propagate_and_optimize_mode(path, req, equipment, show=False): +def propagate_and_optimize_mode(path, req, equipment): # 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,8 +438,6 @@ def propagate_and_optimize_mode(path, req, equipment, show=False): b, req.power, req.spacing) for el in path: si = el(si) - if show: - print(el) for m in modes_to_explore : if path[-1].snr is not None: path[-1].update_snr(m['tx_osnr'], equipment['Roadm']['default'].add_drop_osnr) diff --git a/tests/test_automaticmodefeature.py b/tests/test_automaticmodefeature.py index a5c34ea92..05cc9eaa5 100644 --- a/tests/test_automaticmodefeature.py +++ b/tests/test_automaticmodefeature.py @@ -72,7 +72,7 @@ def test_automaticmodefeature(net,eqpt,serv,expected_mode): if pathreq.baud_rate is not None: print(pathreq.format) path_res_list.append(pathreq.format) - total_path = propagate(total_path,pathreq,equipment, show=False) + total_path = propagate(total_path,pathreq,equipment) else: 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 From 07de489d6b267f96357541db0bae17a98649b6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Mon, 17 Jun 2019 19:26:15 +0200 Subject: [PATCH 109/117] doc: fiber length summary in km, not meters reading "80000m" is a bit more complex than just "80 km". Also let's add a space between the numebr and the unit for better readability. --- examples/transmission_main_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/transmission_main_example.py b/examples/transmission_main_example.py index eab66bd2a..f854cfaee 100755 --- a/examples/transmission_main_example.py +++ b/examples/transmission_main_example.py @@ -133,7 +133,7 @@ def main(network, equipment, source, destination, sim_params, req=None): configure_network(network, sim_params) spans = [s.length for s in path if isinstance(s, RamanFiber) or isinstance(s, Fiber)] - print(f'\nThere are {len(spans)} fiber spans over {sum(spans):.0f}m between {source.uid} and {destination.uid}') + print(f'\nThere are {len(spans)} fiber spans over {sum(spans)/1000:.0f} km between {source.uid} and {destination.uid}') print(f'\nNow propagating between {source.uid} and {destination.uid}:') try: From c817ef733520360f1f01aadf6145393ead4e3014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Mon, 17 Jun 2019 19:28:12 +0200 Subject: [PATCH 110/117] examples: color highlighting of the "most interesting output" I think that this SNR value represents the most important output of this example. There's plenty of debugging info on display already, so let's make this one more prominent. I was thinking about moving the highlighting to elements' each __str__() function, but that felt a bit like layering violation to me. I could have also changed just the transponder's __str__(). In the end, I think that repeating just the final GSNR at the link-end transponder makes most sense here. This is aligned with what we talked about at yesterday's (on 2019-09-18 -- note that this is a backport from #261) demo for Microsoft, after all. --- examples/transmission_main_example.py | 14 ++++++-------- gnpy/core/ansi_escapes.py | 1 + 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/transmission_main_example.py b/examples/transmission_main_example.py index f854cfaee..0ddfad084 100755 --- a/examples/transmission_main_example.py +++ b/examples/transmission_main_example.py @@ -150,19 +150,18 @@ def main(network, equipment, source, destination, sim_params, req=None): for dp_db in power_range: req.power = db2lin(pref_ch_db + dp_db)*1e-3 if power_mode: - print(f'\nPropagating with input power = {lin2db(req.power*1e3):.2f}dBm :') + print(f'\nPropagating with input power = {ansi_escapes.cyan}{lin2db(req.power*1e3):.2f} dBm{ansi_escapes.reset}:') else: - print(f'\nPropagating in gain mode: power cannot be set manually') + print(f'\nPropagating in {ansi_escapes.cyan}gain mode{ansi_escapes.reset}: power cannot be set manually') infos = propagate2(path, req, equipment) if len(power_range) == 1: - for elem in path[:-1]: + for elem in path: print(elem) if power_mode: - print(f'\nTransmission result for input power = {lin2db(req.power*1e3):.2f}dBm :') + 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' Final GSNR (signal bw): {ansi_escapes.cyan}{mean(destination.snr):.02f} dB{ansi_escapes.reset}') #print(f'\n !!!!!!!!!!!!!!!!! TEST POINT !!!!!!!!!!!!!!!!!!!!!') #print(f'carriers ase output of {path[1]} =\n {list(path[1].carriers("out", "nli"))}') @@ -183,8 +182,7 @@ def main(network, equipment, source, destination, sim_params, req=None): '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 diff --git a/gnpy/core/ansi_escapes.py b/gnpy/core/ansi_escapes.py index 16812a382..072d75cba 100644 --- a/gnpy/core/ansi_escapes.py +++ b/gnpy/core/ansi_escapes.py @@ -9,4 +9,5 @@ ''' red = '\x1b[1;31;40m' +cyan = '\x1b[1;36;40m' reset = '\x1b[0m' From d9f5ca9827919efc7c23ff0625916800c8701c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 19 Sep 2019 09:44:56 +0200 Subject: [PATCH 111/117] docs: Document Transceiver.mode.OSNR I am not changing the JSON key name now because of the plan to go with a full-blown YANG model (see #266). fixes #298 --- json_structure_description.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/json_structure_description.rst b/json_structure_description.rst index b8239d3a1..f144602ae 100644 --- a/json_structure_description.rst +++ b/json_structure_description.rst @@ -273,6 +273,8 @@ Spectral information with its parameters: Transceiver element with its parameters. **”mode”** can contain multiple Transceiver operation formats. +Note that ``OSNR`` parameter refers to the receiver's minimal OSNR threshold for a given mode. + .. code-block:: json-object "Transceiver":[{ From faa69917d91b5b8b636ce788fa5eb75f173b028b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Fri, 20 Sep 2019 17:11:42 +0200 Subject: [PATCH 112/117] example: Use "Total SNR", not "GSNR" Various presentations from Polito are slowly changing to use "GSNR" as a "Generalized SNR", but it's true that our code does not use this term anywhere, and that it is not properly explained. Let's wait a bit for this term to become a bit more mainstream and for updated docs on our side; then this commit can be safely reverted. Thanks to Jonas for reporting this. --- examples/transmission_main_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/transmission_main_example.py b/examples/transmission_main_example.py index 0ddfad084..340b3e07f 100755 --- a/examples/transmission_main_example.py +++ b/examples/transmission_main_example.py @@ -161,7 +161,7 @@ def main(network, equipment, source, destination, sim_params, req=None): print(f'\nTransmission result for input power = {lin2db(req.power*1e3):.2f} dBm:') else: print(f'\nTransmission results:') - print(f' Final GSNR (signal bw): {ansi_escapes.cyan}{mean(destination.snr):.02f} dB{ansi_escapes.reset}') + print(f' Final SNR total (signal bw): {ansi_escapes.cyan}{mean(destination.snr):.02f} dB{ansi_escapes.reset}') #print(f'\n !!!!!!!!!!!!!!!!! TEST POINT !!!!!!!!!!!!!!!!!!!!!') #print(f'carriers ase output of {path[1]} =\n {list(path[1].carriers("out", "nli"))}') From 33ff0910b892a0c0aa41f2ac72ece91a077f82a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Sun, 22 Sep 2019 12:20:11 +0200 Subject: [PATCH 113/117] Remove unused function This has been marked obsolete for 11 months. It's also one of the callers of the propagate(), so it's being touched by that change which removes the `show` parameter. This commit will make it easier to put debug path printing just where it's really needed. --- examples/path_requests_run.py | 38 ----------------------------------- 1 file changed, 38 deletions(-) diff --git a/examples/path_requests_run.py b/examples/path_requests_run.py index e70fe0d2d..d8dcbc338 100755 --- a/examples/path_requests_run.py +++ b/examples/path_requests_run.py @@ -144,44 +144,6 @@ def load_requests(filename,eqpt_filename): json_data = loads(f.read()) return json_data -def compute_path(network, equipment, pathreqlist): - - # This function is obsolete and not relevant with respect to network building: suggest either to correct - # or to suppress it - - path_res_list = [] - - for pathreq in pathreqlist: - #need to rebuid the network for each path because the total power - #can be different and the choice of amplifiers in autodesign is power dependant - #but the design is the same if the total power is the same - #TODO parametrize the total spectrum power so the same design can be shared - p_db = lin2db(pathreq.power*1e3) - p_total_db = p_db + lin2db(pathreq.nb_channel) - build_network(network, equipment, p_db, p_total_db) - pathreq.nodes_list.append(pathreq.destination) - #we assume that the destination is a strict constraint - pathreq.loose_list.append('strict') - print(f'Computing path from {pathreq.source} to {pathreq.destination}') - print(f'with path constraint: {[pathreq.source]+pathreq.nodes_list}') #adding first node to be clearer on the output - total_path = compute_constrained_path(network, pathreq) - print(f'Computed path (roadms):{[e.uid for e in total_path if isinstance(e, Roadm)]}\n') - - if total_path : - total_path = propagate(total_path,pathreq,equipment) - else: - total_path = [] - # we record the last tranceiver object in order to have th whole - # information about spectrum. Important Note: since transceivers - # attached to roadms are actually logical elements to simulate - # performance, several demands having the same destination may use - # the same transponder for the performance simaulation. This is why - # we use deepcopy: to ensure each propagation is recorded and not - # overwritten - - path_res_list.append(deepcopy(total_path)) - return path_res_list - def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist): # use a list but a dictionnary might be helpful to find path bathsed on request_id From d8c236bb44473b98cef1993a9692bff0c6c67593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Sun, 22 Sep 2019 16:44:00 +0100 Subject: [PATCH 114/117] examples: do not measure time taken This is an equivalent of 9c9e3be9675a78aefae555c32596e8faff3585f3 for the other example. --- examples/path_requests_run.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/path_requests_run.py b/examples/path_requests_run.py index d8dcbc338..a5fc979cd 100755 --- a/examples/path_requests_run.py +++ b/examples/path_requests_run.py @@ -35,7 +35,6 @@ from copy import copy, deepcopy from textwrap import dedent from math import ceil -import time #EQPT_LIBRARY_FILENAME = Path(__file__).parent / 'eqpt_config.json' @@ -263,7 +262,6 @@ def path_result_json(pathresult): if __name__ == '__main__': - start = time.time() args = parser.parse_args() basicConfig(level={2: DEBUG, 1: INFO, 0: CRITICAL}.get(args.verbose, DEBUG)) logger.info(f'Computing path requests {args.service_filename} into JSON format') @@ -331,8 +329,6 @@ def path_result_json(pathresult): print('\x1b[1;34;40m'+f'Propagating on selected path'+ '\x1b[0m') propagatedpths = compute_path_with_disjunction(network, equipment, rqs, pths) - end = time.time() - print(f'computation time {end-start}') print('\x1b[1;34;40m'+f'Result summary'+ '\x1b[0m') header = ['req id', ' demand',' snr@bandwidth',' snr@0.1nm',' Receiver minOSNR', ' mode', ' Gbit/s' , ' nb of tsp pairs'] From f8c8526045bc34924c569631cd6f48ec21a3e259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Mon, 23 Sep 2019 01:03:30 +0200 Subject: [PATCH 115/117] docs: remove duplicate `cd` This whole section should be nuked, but that will have to wait until after the next release for simplicity. fix #283 --- README.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/README.rst b/README.rst index 594b09d07..d7c76b12c 100644 --- a/README.rst +++ b/README.rst @@ -105,7 +105,6 @@ executes without a ``ModuleNotFoundError``, you have successfully installed $ python -c 'import gnpy' # attempt to import gnpy - $ cd oopt-gnpy $ pytest # run tests Instructions for First Use From ec9eb8d054f8809c4ef981fb1a3d90b5193faa6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Mon, 23 Sep 2019 00:14:56 +0100 Subject: [PATCH 116/117] examples: commented-out path printing for Esther I'm trying to clean up the core code while not causing too much trouble to our users. Esther pointed out that there's an internal use case at Orange where people are looking at the incremental results when computing paths. I strongly dislike commented-out debugging code, but for the sake of progress, let's put it in here. It's roughly as good as that unused `show` parameter, and it allowed a refactoring to make the code more readable. --- examples/path_requests_run.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/path_requests_run.py b/examples/path_requests_run.py index a5fc979cd..4b836e8a1 100755 --- a/examples/path_requests_run.py +++ b/examples/path_requests_run.py @@ -167,6 +167,7 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist): if total_path : if pathreq.baud_rate is not None: total_path = propagate(total_path,pathreq,equipment) + # for el in total_path: print(el) temp_snr01nm = round(mean(total_path[-1].snr+lin2db(pathreq.baud_rate/(12.5e9))),2) if temp_snr01nm < pathreq.OSNR : msg = f'\tWarning! Request {pathreq.request_id} computed path from {pathreq.source} to {pathreq.destination} does not pass with {pathreq.tsp_mode}\n' +\ From 328bd6ea713df66d999dce67ac90c885d34fac50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Mon, 23 Sep 2019 00:29:59 +0100 Subject: [PATCH 117/117] docs: Explain how to use Docker --- README.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.rst b/README.rst index d7c76b12c..a144712d4 100644 --- a/README.rst +++ b/README.rst @@ -41,6 +41,33 @@ Branches and Tagged Releases How to Install -------------- +Using prebuilt Docker images +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Our `Docker images `_ contain everything needed to run all examples from this guide. +Docker transparently fetches the image over the network upon first use. +On Linux and Mac, run: + + +.. code-block:: shell-session + + $ docker run -it --rm --volume $(pwd):/shared telecominfraproject/oopt-gnpy + root@bea050f186f7:/shared/examples# + +On Windows, launch from Powershell as: + +.. code-block:: powershell + + PS C:\> docker run -it --rm --volume ${PWD}:/shared telecominfraproject/oopt-gnpy + root@89784e577d44:/shared/examples# + +In both cases, a directory named ``examples/`` will appear in your current working directory. +GNPy automaticallly populates it with example files from the current release. +Remove that directory if you want to start from scratch. + +Using Python on your computer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + **Note**: `gnpy` supports Python 3 only. Python 2 is not supported. `gnpy` requires Python ≥3.6