Skip to content

Commit

Permalink
Only propagates carriers that belong to Amp bandwidth
Browse files Browse the repository at this point in the history
The commit introduces mux/demux functions in amps and ensures that the
propagation is only done on carriers that are in the Amp bandwitdh, ie
with all their spectrum including slot width is in bandwidth.

For consistency, default amp f_min is changed:
Objective is to use amplifiers' band to bound the possible frequencies
to be propagated. Since the current default f_min of Amp in json_io.py is
higher than the SI one, this would result in a different nb of channels
than currently used in tests, and a change in all tests. In order to
avoid this, I preferred to change this value and have consistency
between SI f_min and Amp f_min.

The commits adds a set of functions to make amps band the useable
spectrum on each OMS. Thee OMS generation is changed to use the amp band.

The commit adds filtering functions (demux and mux) to filter out spectrum
which is not in the amplifier band.

Spectrum assignment is also corrected to correctly match the amp bandwidth
constraint with guardband: center frequency index must be within the
usable part of the amp band. This changes a bit the notion of freq_index
and guardband in the functions, but this is transparent to user:
f_min, f_max represent the amp band, while self.freq_index_min/max
represent the center frequency boundary for a reference 50GHz channel.

Signed-off-by: EstherLerouzic <[email protected]>
Change-Id: I225b2b2dc0e1f1992c0460f6e08fa9c9bc641edf
  • Loading branch information
EstherLerouzic committed Sep 13, 2024
1 parent 4d1282d commit 55ff3cf
Show file tree
Hide file tree
Showing 13 changed files with 274 additions and 73 deletions.
11 changes: 8 additions & 3 deletions gnpy/core/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from gnpy.core.parameters import RoadmParams, FusedParams, FiberParams, PumpParams, EdfaParams, EdfaOperational, \
RoadmPath, RoadmImpairment
from gnpy.core.science_utils import NliSolver, RamanSolver
from gnpy.core.info import SpectralInformation
from gnpy.core.info import SpectralInformation, demuxed_spectral_information
from gnpy.core.exceptions import NetworkTopologyError, SpectrumError, ParametersError


Expand Down Expand Up @@ -1204,5 +1204,10 @@ def propagate(self, spectral_info):
self.propagated_labels = spectral_info.label

def __call__(self, spectral_info):
self.propagate(spectral_info)
return spectral_info
# filter out carriers outside the amplifier band
band = next(b for b in self.params.bands)
spectral_info = demuxed_spectral_information(spectral_info, band)
if spectral_info.carriers:
self.propagate(spectral_info)
return spectral_info
raise ValueError(f'Amp {self.uid} Defined propagation band does not match amplifiers band.')
52 changes: 51 additions & 1 deletion gnpy/core/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from __future__ import annotations
from collections import namedtuple
from collections.abc import Iterable
from typing import Union
from typing import Union, List
from dataclasses import dataclass
from numpy import argsort, mean, array, append, ones, ceil, any, zeros, outer, full, ndarray, asarray

Expand Down Expand Up @@ -317,6 +317,56 @@ def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, spacing
tx_osnr=tx_osnr, tx_power=tx_power, label=label)


def is_in_band(frequency: float, band: dict) -> bool:
"""band has {"f_min": value, "f_max": value} format
"""
if frequency >= band['f_min'] and frequency <= band['f_max']:
return True
return False


def demuxed_spectral_information(input_si: SpectralInformation, band: dict) -> SpectralInformation:
"""extract a si based on band
"""
filtered_indices = [i for i, f in enumerate(input_si.frequency)
if is_in_band(f - input_si.slot_width[i] / 2, band)
and is_in_band(f + input_si.slot_width[i] / 2, band)]
if filtered_indices:
frequency = input_si.frequency[filtered_indices]
baud_rate = input_si.baud_rate[filtered_indices]
slot_width = input_si.slot_width[filtered_indices]
signal = input_si.signal[filtered_indices]
nli = input_si.nli[filtered_indices]
ase = input_si.ase[filtered_indices]
roll_off = input_si.roll_off[filtered_indices]
chromatic_dispersion = input_si.chromatic_dispersion[filtered_indices]
pmd = input_si.pmd[filtered_indices]
pdl = input_si.pdl[filtered_indices]
latency = input_si.latency[filtered_indices]
delta_pdb_per_channel = input_si.delta_pdb_per_channel[filtered_indices]
tx_osnr = input_si.tx_osnr[filtered_indices]
tx_power = input_si.tx_power[filtered_indices]
label = input_si.label[filtered_indices]

return SpectralInformation(frequency=frequency, baud_rate=baud_rate, slot_width=slot_width, signal=signal,
nli=nli, ase=ase, roll_off=roll_off, chromatic_dispersion=chromatic_dispersion,
pmd=pmd, pdl=pdl, latency=latency, delta_pdb_per_channel=delta_pdb_per_channel,
tx_osnr=tx_osnr, tx_power=tx_power, label=label)
return None


def muxed_spectral_information(input_si_list: List[SpectralInformation]) -> SpectralInformation:
"""return the assembled spectrum
"""
if input_si_list and len(input_si_list) > 1:
si = input_si_list[0] + muxed_spectral_information(input_si_list[1:])
return si
elif input_si_list and len(input_si_list) == 1:
return input_si_list[0]
else:
raise ValueError('liste vide')


def carriers_to_spectral_information(initial_spectrum: dict[float, Carrier],
power: float) -> SpectralInformation:
"""Initial spectrum is a dict with key = carrier frequency, and value a Carrier object.
Expand Down
12 changes: 7 additions & 5 deletions gnpy/core/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,10 +463,10 @@ def asdict(self):

class EdfaParams:
default_values = {
'f_min': 191.3e12,
'f_max': 196.1e12,
'f_min': None,
'f_max': None,
'multi_band': None,
'bands': [],
'bands': None,
'type_variety': '',
'type_def': '',
'gain_flatmax': None,
Expand Down Expand Up @@ -502,9 +502,11 @@ def __init__(self, **params):
# Bandwidth
self.f_min = params['f_min']
self.f_max = params['f_max']
self.bandwidth = self.f_max - self.f_min
self.f_cent = (self.f_max + self.f_min) / 2
self.bandwidth = self.f_max - self.f_min if self.f_max and self.f_min else None
self.f_cent = (self.f_max + self.f_min) / 2 if self.f_max and self.f_min else None
self.f_ripple_ref = params['f_ripple_ref']
self.bands = [{'f_min': params['f_min'],
'f_max': params['f_max']}]

# Gain
self.gain_flatmax = params['gain_flatmax']
Expand Down
35 changes: 35 additions & 0 deletions gnpy/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from numpy import pi, cos, sqrt, log10, linspace, zeros, shape, where, logical_and, mean, array
from scipy import constants
from copy import deepcopy
from typing import List

from gnpy.core.exceptions import ConfigurationError

Expand Down Expand Up @@ -469,3 +470,37 @@ def calculate_absolute_min_or_zero(x: array) -> array:
array([1., 0., 3.])
"""
return (abs(x) - x) / 2


def find_common_range(amp_bands: List[List[dict]], default_band_f_min: float, default_band_f_max: float) \
-> List[dict]:
"""Find the common frequency range of bands
If there are no amplifiers in the path, then use default band
>>> amp_bands = [[{'f_min': 191e12, 'f_max' : 195e12}, {'f_min': 186e12, 'f_max' : 190e12} ], \
[{'f_min': 185e12, 'f_max' : 189e12}, {'f_min': 192e12, 'f_max' : 196e12}], \
[{'f_min': 186e12, 'f_max': 193e12}]]
>>> find_common_range(amp_bands, 190e12, 195e12)
[{'f_min': 186000000000000.0, 'f_max': 189000000000000.0}, {'f_min': 192000000000000.0, 'f_max': 193000000000000.0}]
>>> amp_bands = [[{'f_min': 191e12, 'f_max' : 195e12}, {'f_min': 186e12, 'f_max' : 190e12} ], \
[{'f_min': 185e12, 'f_max' : 189e12}, {'f_min': 192e12, 'f_max' : 196e12}], \
[{'f_min': 186e12, 'f_max': 192e12}]]
>>> find_common_range(amp_bands, 190e12, 195e12)
[{'f_min': 186000000000000.0, 'f_max': 189000000000000.0}]
"""
amp_bands = [sorted(amp, key=lambda x: x['f_min']) for amp in amp_bands]
# remove duplicate
unique_amp_bands = []
for amp in amp_bands:
if amp not in unique_amp_bands:
unique_amp_bands.append(amp)
if unique_amp_bands:
common_range = unique_amp_bands[0]
else:
common_range = [{'f_min': default_band_f_min, 'f_max': default_band_f_max}]
for bands in unique_amp_bands:
common_range = [{'f_min': max(first['f_min'], second['f_min']), 'f_max': min(first['f_max'], second['f_max'])}
for first in common_range for second in bands
if max(first['f_min'], second['f_min']) < min(first['f_max'], second['f_max'])]
return sorted(common_range, key=lambda x: x['f_min'])
4 changes: 2 additions & 2 deletions gnpy/example-data/default_edfa_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"gain_ripple": [
0.0
],
"f_min": 191.35e12,
"f_max": 196.1e12,
"f_min": 191.275e12,
"f_max": 196.125e12,
"dgt": [
1.0,
1.017807767853702,
Expand Down
4 changes: 2 additions & 2 deletions gnpy/example-data/std_medium_gain_advanced_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
0.0359549,
5.82851
],
"f_min": 191.35e12,
"f_max": 196.1e12,
"f_min": 191.275e12,
"f_max": 196.125e12,
"nf_ripple": [
0.4372876328262819,
0.4372876328262819,
Expand Down
28 changes: 27 additions & 1 deletion gnpy/tools/json_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def __init__(self, **kwargs):
@classmethod
def from_json(cls, filename, **kwargs):
config = Path(filename).parent / 'default_edfa_config.json'

# default_edfa_config.json assumes a DGT profile independantly from fmin/fmax, that's a generic profile
type_variety = kwargs['type_variety']
type_def = kwargs.get('type_def', 'variable_gain') # default compatibility with older json eqpt files
nf_def = None
Expand Down Expand Up @@ -367,6 +367,31 @@ def _update_dual_stage(equipment):
return equipment


def _update_band(equipment: dict) -> dict:
"""Creates a list of bands for this amplifier, and remove other parameters which are not applicable
"""
amp_dict = equipment['Edfa']
for amplifier in amp_dict.values():
if amplifier.type_def != 'multi_band':
amplifier.bands = [{'f_min': amplifier.f_min,
'f_max': amplifier.f_max}]
# updates band parameter
else:
_bands = [{'f_min': amp_dict[a].f_min,
'f_max': amp_dict[a].f_max} for a in amp_dict[amplifier.type_variety].multi_band]
# remove duplicates
amplifier.bands = []
for b in _bands:
if b not in amplifier.bands:
amplifier.bands.append(b)
# remove non applicable parameters
for key in ['f_min', 'f_max', 'gain_flatmax', 'gain_min', 'p_max', 'nf_model', 'dual_stage_model',
'nf_fit_coeff', 'nf_ripple', 'dgt', 'gain_ripple']:
delattr(amplifier, key)

return equipment


def _roadm_restrictions_sanity_check(equipment):
"""verifies that booster and preamp restrictions specified in roadm equipment are listed in the edfa."""
for roadm_type, roadm_eqpt in equipment['Roadm'].items():
Expand Down Expand Up @@ -428,6 +453,7 @@ def _equipment_from_json(json_data, filename):
raise EquipmentConfigError(f'Unrecognized network element type "{key}"')
_check_fiber_vs_raman_fiber(equipment)
equipment = _update_dual_stage(equipment)
equipment = _update_band(equipment)
_roadm_restrictions_sanity_check(equipment)
return equipment

Expand Down
40 changes: 36 additions & 4 deletions gnpy/topology/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
"""

from collections import namedtuple, OrderedDict
from typing import List
from logging import getLogger
from networkx import (dijkstra_path, NetworkXNoPath,
all_simple_paths, shortest_simple_paths)
from networkx.utils import pairwise
from numpy import mean, argmin
from gnpy.core.elements import Transceiver, Roadm
from gnpy.core.utils import lin2db
from gnpy.core.info import create_input_spectral_information, carriers_to_spectral_information

from gnpy.core.elements import Transceiver, Roadm, Edfa
from gnpy.core.utils import lin2db, find_common_range
from gnpy.core.info import create_input_spectral_information, carriers_to_spectral_information, \
demuxed_spectral_information, muxed_spectral_information, SpectralInformation
from gnpy.core import network as network_module
from gnpy.core.exceptions import ServiceError, DisjunctionError
from copy import deepcopy
Expand Down Expand Up @@ -332,14 +335,34 @@ def compute_constrained_path(network, req):
return total_path


def filter_si(path: list, equipment: dict, si: SpectralInformation) -> SpectralInformation:
"""Filter spectral information based on the amplifiers common range"""
# First retrieve f_min, f_max spectrum according to amplifiers' spectrum on the path
common_range = find_elements_common_range(path, equipment)
# filter out frequencies that should not be created
filtered_si = []
for band in common_range:
temp = demuxed_spectral_information(si, band)
if temp:
filtered_si.append(temp)
if not filtered_si:
raise ValueError('Defined propagation band does not match amplifiers band.')
return muxed_spectral_information(filtered_si)


def propagate(path, req, equipment):
"""propagates signals in each element according to initial spectrum set by user"""
"""propagates signals in each element according to initial spectrum set by user
Spectrum is specified in request through f_min, f_max and spacing, or initial_spectrum
and amps frequency band on the path is used to filter out frequencies"""
# generates spectrum based on request
if req.initial_spectrum is not None:
si = carriers_to_spectral_information(initial_spectrum=req.initial_spectrum, power=req.power)
else:
si = create_input_spectral_information(
f_min=req.f_min, f_max=req.f_max, roll_off=req.roll_off, baud_rate=req.baud_rate,
spacing=req.spacing, tx_osnr=req.tx_osnr, tx_power=req.tx_power, delta_pdb=req.offset_db)
# filter out frequencies that should not be created
si = filter_si(path, equipment, si)
roadm_osnr = []
for i, el in enumerate(path):
if isinstance(el, Roadm):
Expand Down Expand Up @@ -385,6 +408,7 @@ def propagate_and_optimize_mode(path, req, equipment):
baud_rate=this_br, spacing=req.spacing,
delta_pdb=this_offset, tx_osnr=req.tx_osnr,
tx_power=req.tx_power)
spc_info = filter_si(path, equipment, spc_info)
roadm_osnr = []
for i, el in enumerate(path):
if isinstance(el, Roadm):
Expand Down Expand Up @@ -1225,3 +1249,11 @@ def _penalty_msg(total_path, msg, min_ind):
else:
msg += f'\n\t{pretty} penalty not evaluated'
return msg


def find_elements_common_range(el_list: list, equipment: dict) -> List[dict]:
"""Find the common frequency range of amps of a given list of elements (for example an OMS or a path)
If there are no amplifiers in the path, then use the SI
"""
amp_bands = [n.params.bands for n in el_list if isinstance(n, (Edfa))]
return find_common_range(amp_bands, equipment['SI']['default'].f_min, equipment['SI']['default'].f_max)
Loading

0 comments on commit 55ff3cf

Please sign in to comment.