Add a -spectrum option to input external file to define spectrum

The option is only set for gnpy-transmission-main.

The spectrum file is a list of of spectrum objects, each defining
fmin, fmax and spectrum attributes using the same definition as SI
in eqpt_config.json. the object list should be contiguous spectrum
definitions and not overlapping. For now, I have not integrated the
possibility to directly use transceivers type and mode in the list.
user can define sets of contiguous channels and a label to identify
the spectrum bands. If no label are defined, th program justs uses
the index + baud rate of the spectrum bands as label.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ibc01e59e461e5e933e95d23dacbc5289e275ccf7
This commit is contained in:
EstherLerouzic
2021-08-05 14:34:13 +02:00
parent e851220f81
commit 4b94481ecb
4 changed files with 82 additions and 11 deletions

View File

@@ -66,7 +66,6 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
trx_params['min_spacing'] = None
nch = automatic_nch(trx_params['f_min'], trx_params['f_max'], trx_params['spacing'])
trx_params['nb_channel'] = nch
print(f'There are {nch} channels propagating')
trx_params['power'] = db2lin(default_si_data.power_dbm) * 1e-3

View File

@@ -54,7 +54,7 @@ class SpectralInformation(object):
""" Class containing the parameters of the entire WDM comb."""
def __init__(self, frequency: array, baud_rate: array, slot_width: array, signal: array, nli: array, ase: array,
roll_off: array, chromatic_dispersion: array, pmd: array, ref_power: Pref):
roll_off: array, chromatic_dispersion: array, pmd: array, ref_power: Pref, label: str):
indices = argsort(frequency)
self._frequency = frequency[indices]
self._df = outer(ones(frequency.shape), frequency) - outer(frequency, ones(frequency.shape))
@@ -80,6 +80,7 @@ class SpectralInformation(object):
self._pmd = pmd[indices]
self._channel_number = [*range(1, self._number_of_channels + 1)]
self._pref = ref_power
self._label = label
@property
def pref(self):
@@ -156,6 +157,10 @@ class SpectralInformation(object):
def pmd(self):
return self._pmd
@property
def label(self):
return self._label
@pmd.setter
def pmd(self, pmd):
self._pmd = pmd
@@ -201,7 +206,8 @@ class SpectralInformation(object):
chromatic_dispersion=append(self.chromatic_dispersion,
other.chromatic_dispersion),
pmd=append(self.pmd, other.pmd),
ref_power=pref)
ref_power=pref,
label=append(self.label, other.label))
except SpectrumError:
raise SpectrumError('Spectra cannot be summed: channels overlapping.')
@@ -223,7 +229,8 @@ def create_arbitrary_spectral_information(frequency: Union[ndarray, Iterable, in
roll_off: Union[int, float, ndarray, Iterable] = 0.,
chromatic_dispersion: Union[int, float, ndarray, Iterable] = 0.,
pmd: Union[int, float, ndarray, Iterable] = 0.,
ref_power: Pref = None):
ref_power: Pref = None,
label: string = None):
"""This is just a wrapper around the SpectralInformation.__init__() that simplifies the creation of
a non-uniform spectral information with NLI and ASE powers set to zero."""
frequency = asarray(frequency)
@@ -242,7 +249,7 @@ def create_arbitrary_spectral_information(frequency: Union[ndarray, Iterable, in
signal=signal, nli=nli, ase=ase,
baud_rate=baud_rate, roll_off=roll_off,
chromatic_dispersion=chromatic_dispersion, pmd=pmd,
ref_power=ref_power)
ref_power=ref_power, label=label)
except ValueError as e:
if 'could not broadcast' in str(e):
raise SpectrumError('Dimension mismatch in input fields.')
@@ -258,11 +265,13 @@ def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power,
p_span0 = watt2dbm(power)
p_spani = watt2dbm(power)
p_span0_per_channel = watt2dbm(power) * ones(nb_channel)
label = ["0" for i in range(nb_channel)]
return create_arbitrary_spectral_information(frequency, slot_width=spacing, signal=power, baud_rate=baud_rate,
roll_off=roll_off,
ref_power=Pref(p_span0=p_span0, p_spani=p_spani,
p_span0_per_channel=p_span0_per_channel,
ref_carrier=ref_carrier))
ref_carrier=ref_carrier),
label=label)
def use_initial_spectrum(initial_spectrum, ref_carrier):
@@ -275,6 +284,7 @@ def use_initial_spectrum(initial_spectrum, ref_carrier):
roll_off = [s['roll_off'] for s in initial_spectrum.values()]
baud_rate = [s['baud_rate'] for s in initial_spectrum.values()]
slot_width = [s['spacing'] for s in initial_spectrum.values()]
label = [s['label'] for s in initial_spectrum.values()]
p_span0 = watt2dbm(ref_carrier['req_power'])
p_spani = watt2dbm(ref_carrier['req_power'])
p_span0_per_channel = [watt2dbm(s['power']) for s in initial_spectrum.values()]
@@ -282,4 +292,5 @@ def use_initial_spectrum(initial_spectrum, ref_carrier):
slot_width=slot_width, roll_off=roll_off,
ref_power=Pref(p_span0=p_span0, p_spani=p_spani,
p_span0_per_channel=p_span0_per_channel,
ref_carrier=ref_carrier))
ref_carrier=ref_carrier),
label=label)

View File

@@ -15,6 +15,8 @@ import sys
from math import ceil
from numpy import linspace, mean
from pathlib import Path
from copy import deepcopy
import gnpy.core.ansi_escapes as ansi_escapes
from gnpy.core.elements import Transceiver, Fiber, RamanFiber
from gnpy.core.equipment import trx_mode_params
@@ -25,10 +27,11 @@ from gnpy.core.utils import db2lin, lin2db, automatic_nch
from gnpy.topology.request import (ResultElement, jsontocsv, compute_path_dsjctn, requests_aggregation,
BLOCKING_NOPATH, correct_json_route_list,
deduplicate_disjunctions, compute_path_with_disjunction,
PathRequest, compute_constrained_path, propagate)
PathRequest, compute_constrained_path, propagate, update_spectrum_power)
from gnpy.topology.spectrum_assignment import build_oms_list, pth_assign_spectrum
from gnpy.tools.json_io import load_equipment, load_network, load_json, load_requests, save_network, \
requests_from_json, disjunctions_from_json, save_json
requests_from_json, disjunctions_from_json, save_json, load_initial_spectrum,\
_spectrum_from_json
from gnpy.tools.plots import plot_baseline, plot_results
_logger = logging.getLogger(__name__)
@@ -118,6 +121,7 @@ def transmission_main_example(args=None):
parser.add_argument('-pl', '--plot', action='store_true')
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('-spectrum', '--mixed-rate-spectrum-file', help='user defined mixed rate spectrum json file')
parser.add_argument('source', nargs='?', help='source node')
parser.add_argument('destination', nargs='?', help='destination node')
@@ -195,7 +199,13 @@ def transmission_main_example(args=None):
trx_params['power'] = db2lin(float(args.power)) * 1e-3
params.update(trx_params)
req = PathRequest(**params)
if args.mixed_rate_spectrum_file:
req.initial_spectrum = load_initial_spectrum(args.mixed_rate_spectrum_file, equipment)
print('warning: user input for spectrum used for propagation instead of SI')
nb_channels = len(req.initial_spectrum)
else:
nb_channels = req.nb_channel
print(f'There are {nb_channels} channels propagating')
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']))
@@ -226,8 +236,20 @@ def transmission_main_example(args=None):
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]')
if hasattr(req, 'initial_spectrum'):
record_intial_spectrum = req.initial_spectrum
for dp_db in power_range:
req.power = db2lin(pref_ch_db + dp_db) * 1e-3
# if initial spectrum did not contain any power, now we need to use this one.
# note the initial power defines a differential wrt req.power so that if req.power is set to 2mW (3dBm)
# and initial spectrum was set to 0, this sets a initial per channel delta power to -3dB, so that
# whatever the equalization, -3 dB is applied on all channels (ie initial power in initial spectrum pre-empts
# pow option)
if hasattr(req, 'initial_spectrum'):
# without deepcopy, the previous dp setting is recorded as a user defined and spectrum is not properly
# updated for the power sweep dp_db
req.initial_spectrum = deepcopy(record_intial_spectrum)
update_spectrum_power(req)
if power_mode:
print(f'\nPropagating with input power = {ansi_escapes.cyan}{lin2db(req.power*1e3):.2f} dBm{ansi_escapes.reset}:')
else:

View File

@@ -17,7 +17,7 @@ from gnpy.core import ansi_escapes, elements
from gnpy.core.equipment import trx_mode_params
from gnpy.core.exceptions import ConfigurationError, EquipmentConfigError, NetworkTopologyError, ServiceError
from gnpy.core.science_utils import estimate_nf_model
from gnpy.core.utils import automatic_nch, automatic_fmax, merge_amplifier_restrictions
from gnpy.core.utils import automatic_nch, automatic_fmax, merge_amplifier_restrictions, dbm2watt
from gnpy.topology.request import PathRequest, Disjunction, compute_spectrum_slot_vs_bandwidth
from gnpy.tools.convert import xls_to_json_data
from gnpy.tools.service_sheet import read_service_sheet
@@ -236,11 +236,50 @@ def _automatic_spacing(baud_rate):
return min((s[1] for s in spacing_list if s[0] > baud_rate), default=baud_rate * 1.2)
def _spectrum_from_json(json_data, equipment):
""" json_data is a list of spectrum partitions each with {fmin, fmax, baudrate, roll_off, power and tx_osnr}
creates the per freq dict of carrier's dict
"""
spectrum = {}
# min freq is fmin - spacing/2 (numbering starts at 0)
previous_part_max_freq = json_data[0]['f_min'] - json_data[0]['spacing'] / 2
for index, part in enumerate(json_data):
# add a label to the partition for the printings
if 'label' not in part.keys():
part['label'] = f'{index}-{round(part["baud_rate"] * 1e-9, 2)}'
index = 1 # starting freq is exactly f_min + spacing to be consistent with utils.automatic_nch
# first partition min frequency is f_min + spacing - spacing/2
current_part_min_freq = part['f_min'] + part['spacing'] / 2 # supposes that carriers are centered on frequency
if 'power_dbm' in part:
# user defined partition power.
part['power'] = dbm2watt(part['power_dbm'])
else:
part['power'] = None
if previous_part_max_freq <= current_part_min_freq:
# check that previous part last channel does not overlap on next part first channel
# TODO use functions from andrea to check consistency of spectrum instead
current_freq = part['f_min'] + index * part['spacing']
while current_freq <= part['f_max']:
spectrum[current_freq] = part
index += 1
current_freq = part['f_min'] + index * part['spacing']
previous_part_max_freq = current_freq - part['spacing'] / 2
else:
raise ValueError('not a valid initial spectrum definition')
return spectrum
def load_equipment(filename):
json_data = load_json(filename)
return _equipment_from_json(json_data, filename)
def load_initial_spectrum(filename, equipment):
json_data = load_json(filename)
return _spectrum_from_json(json_data['SI'], equipment)
def _update_dual_stage(equipment):
edfa_dict = equipment['Edfa']
for edfa in edfa_dict.values():