Introduce multi band amps

Introduce a new multi-band element that contains a list of Edfa element:
- reads multiple amps out of the element config.
- deduces frequency band from the amp in the list.

no autodesign yet: multi-band amps must have type_variety.

- checks that type variety of individual EDFAs is consistent with multiband
type variety
- demux and mux spectrum when propagate in multiband
- don't add a preamp or booster if a multiband amp is already defined.

The print of channel number is removed from equipment, since the channel number
may now depend on the path's amplifiers. This changes invocation results layout.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I44e77ff82e622cdee4021a7984d660317cb90cf9
This commit is contained in:
EstherLerouzic
2021-12-10 17:01:30 +01:00
committed by Esther Le Rouzic
parent 920ac30aa5
commit 22fe9ead55
11 changed files with 503 additions and 27 deletions

View File

@@ -5,12 +5,12 @@
gnpy.core.elements
==================
Standard network elements which propagate optical spectrum
Standard network elements which propagate optical spectrum.
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
represents spectral information that is "propagated" by this network element.
Network elements must have only a local "view" of the network and propagate
:class:`.info.SpectralInformation` using only this information. They should be independent and
self-contained.
@@ -25,16 +25,16 @@ from numpy import abs, array, errstate, ones, interp, mean, pi, polyfit, polyval
from scipy.constants import h, c
from scipy.interpolate import interp1d
from collections import namedtuple
from typing import Union
from typing import Union, List
from logging import getLogger
import warnings
from gnpy.core.utils import lin2db, db2lin, arrange_frequencies, snr_sum, per_label_average, pretty_summary_print, \
watt2dbm, psd2powerdbm, calculate_absolute_min_or_zero
watt2dbm, psd2powerdbm, calculate_absolute_min_or_zero, nice_column_str
from gnpy.core.parameters import RoadmParams, FusedParams, FiberParams, PumpParams, EdfaParams, EdfaOperational, \
RoadmPath, RoadmImpairment
MultiBandParams, RoadmPath, RoadmImpairment, find_band_name, FrequencyBand
from gnpy.core.science_utils import NliSolver, RamanSolver
from gnpy.core.info import SpectralInformation, demuxed_spectral_information
from gnpy.core.info import SpectralInformation, muxed_spectral_information, demuxed_spectral_information
from gnpy.core.exceptions import NetworkTopologyError, SpectrumError, ParametersError
@@ -1211,3 +1211,119 @@ class Edfa(_Node):
self.propagate(spectral_info)
return spectral_info
raise ValueError(f'Amp {self.uid} Defined propagation band does not match amplifiers band.')
class Multiband_amplifier(_Node):
"""Represents a multiband amplifier that manages multiple amplifiers across different frequency bands.
This class allows for the initialization and management of amplifiers, each associated with a specific
frequency band. It provides methods for signal propagation through the amplifiers and for exporting
to JSON format.
param: amplifiers: list of dict. A list of dictionaries, each containing parameters for setting an
individual amplifier.
param: params : dict. A dictionary of parameters for the multiband amplifier, which must include
necessary configuration settings.
param: args, kwargs: Additional positional and keyword arguments passed to the parent class `_Node`.
Attributes:
-----------
variety_list : A list of varieties associated with the amplifier.
amplifiers : A dictionary mapping band names to their corresponding amplifier instances.
Methods:
--------
__call__(spectral_info):
Propagates the input spectral information through each amplifier and returns the multiplexed spectrum.
to_json:
Converts the amplifier's state to a JSON-compatible dictionary.
__repr__():
Returns a string representation of the multiband amplifier instance.
__str__():
Returns a formatted string representation of the multiband amplifier and its amplifiers.
Raises:
-------
ParametersError: If there are conflicting amplifier definitions for the same frequency band during initialization.
ValueError: If the input spectral information does not match any defined amplifier bands during propagation.
"""
# separate the top level type_variety from kwargs to avoid having multiple type_varieties on each element processing
def __init__(self, *args, amplifiers: List[dict], params: dict, **kwargs):
self.variety_list = kwargs.pop('variety_list', None)
try:
super().__init__(params=MultiBandParams(**params), **kwargs)
except ParametersError as e:
raise ParametersError(f'{kwargs["uid"]}: {e}')
self.amplifiers = {}
if 'type_variety' in kwargs:
kwargs.pop('type_variety')
self.passive = False
for amp_dict in amplifiers:
# amplifiers dict uses default names as key to represent the band
amp = Edfa(**amp_dict, **kwargs)
band = next(b for b in amp.params.bands)
band_name = find_band_name(FrequencyBand(f_min=band["f_min"], f_max=band["f_max"]))
if band_name not in self.amplifiers.keys() and band not in self.params.bands:
self.params.bands.append(band)
self.amplifiers[band_name] = amp
elif band_name not in self.amplifiers.keys() and band in self.params.bands:
self.amplifiers[band_name] = amp
else:
raise ParametersError(f'{kwargs["uid"]}: has more than one amp defined for the same band')
def __call__(self, spectral_info: SpectralInformation):
"""propagates in each amp and returns the muxed spectrum
"""
out_si = []
for _, amp in self.amplifiers.items():
si = demuxed_spectral_information(spectral_info, amp.params.bands[0])
# if spectral_info frequencies are outside amp band, si is None
if si:
si = amp(si)
out_si.append(si)
if not out_si:
raise ValueError('Defined propagation band does not match amplifiers band.')
return muxed_spectral_information(out_si)
@property
def to_json(self):
return {'uid': self.uid,
'type': type(self).__name__,
'type_variety': self.type_variety,
'amplifiers': [{
'type_variety': amp.type_variety,
'operational': {
'gain_target': round(amp.effective_gain, 6),
'delta_p': amp.delta_p,
'tilt_target': amp.tilt_target,
'out_voa': amp.out_voa
}} for amp in self.amplifiers.values()
],
'metadata': {
'location': self.metadata['location']._asdict()
}
}
def __repr__(self):
return (f'{type(self).__name__}(uid={self.uid!r}, '
f'type_variety={self.type_variety!r}, ')
def __str__(self):
amp_str = [f'{type(self).__name__} {self.uid}',
f' type_variety: {self.type_variety}']
multi_str_data = []
max_width = 0
for amp in self.amplifiers.values():
lines = amp.__str__().split('\n')
# start at index 1 to remove uid from each amp list of strings
# records only if amp is used ie si has frequencies in amp) otherwise there is no other string than the uid
if len(lines) > 1:
max_width = max(max_width, max([len(line) for line in lines[1:]]))
multi_str_data.append(lines[1:])
# multi_str_data contains lines with each amp str, instead we want to print per column: transpose the string
transposed_data = list(map(list, zip(*multi_str_data)))
return '\n'.join(amp_str) + '\n' + nice_column_str(data=transposed_data, max_length=max_width + 2, padding=3)

View File

@@ -7,8 +7,11 @@ gnpy.core.equipment
This module contains functionality for specifying equipment.
"""
from collections import defaultdict
from functools import reduce
from typing import List
from gnpy.core.exceptions import EquipmentConfigError
from gnpy.core.exceptions import EquipmentConfigError, ConfigurationError
def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=False):
@@ -80,3 +83,50 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
trx_params = {**default_trx_params}
return trx_params
def find_type_variety(amps: List[str], equipment: dict) -> str:
"""Returns the multiband type_variety associated with a list of single band type_varieties
Args:
amps (List[str]): A list of single band type_varieties.
equipment (dict): A dictionary containing equipment information.
Returns:
str: an amplifier type variety
"""
listes = find_type_varieties(amps, equipment)
_found_type = list(reduce(lambda x, y: set(x) & set(y), listes))
# Given a list of single band amplifiers, find the multiband amplifier whose multi_band group
# matches. For example, if amps list contains ["a1_LBAND", "a2_CBAND"], with a1.multi_band = [a1_LBAND, a1_CBAND]
# and a2.multi_band = [a1_LBAND, a2_CBAND], then:
# possible_type_varieties = {"a1_LBAND": ["a1", "a2"], "a2_CBAND": ["a2"]}
# listes = [["a1", "a2"], ["a2"]]
# and _found_type = [a2]
if not _found_type:
msg = f'{amps} amps do not belong to the same amp type {listes}'
raise ConfigurationError(msg)
return _found_type[0]
def find_type_varieties(amps: List[str], equipment: dict) -> List[List[str]]:
"""Returns the multiband list of type_varieties associated with a list of single band type_varieties
Args:
amps (List[str]): A list of single band type_varieties.
equipment (dict): A dictionary containing equipment information.
Returns:
List[List[str]]: A list of lists containing the multiband type_varieties
associated with each single band type_variety.
"""
possible_type_varieties = defaultdict(list)
for amp_name, amp in equipment['Edfa'].items():
if amp.multi_band is not None:
for elem in amp.multi_band:
# possible_type_varieties stores the list of multiband amp names that list this elem as
# a possible amplifier of the multiband group. For example, if "std_medium_gain_multiband"
# and "std_medium_gain_multiband_new" contain "std_medium_gain_C" in their "multi_band" list, then:
# possible_type_varieties["std_medium_gain_C"] =
# ["std_medium_gain_multiband", "std_medium_gain_multiband_new"]
possible_type_varieties[elem].append(amp_name)
return [possible_type_varieties[a] for a in amps]

View File

@@ -422,7 +422,7 @@ def set_egress_amplifier(network, this_node, equipment, pref_ch_db, pref_total_d
restrictions = next_node.restrictions['preamp_variety_list']
else:
restrictions = None
edfa_eqpt = {n: a for n, a in equipment['Edfa'].items()}
edfa_eqpt = {n: a for n, a in equipment['Edfa'].items() if a.type_def != 'multi_band'}
edfa_variety, power_reduction = \
select_edfa(raman_allowed, gain_target, power_target, edfa_eqpt,
node.uid,
@@ -480,6 +480,35 @@ def set_egress_amplifier(network, this_node, equipment, pref_ch_db, pref_total_d
node.target_pch_out_dbm = None
elif isinstance(node, elements.RamanFiber):
_ = span_loss(network, node, equipment, input_power=pref_ch_db + dp)
if isinstance(node, elements.Multiband_amplifier):
for amp in node.amplifiers.values():
node_loss = span_loss(network, prev_node, equipment)
voa = amp.out_voa if amp.out_voa else 0
if amp.delta_p is None:
dp = target_power(network, next_node, equipment) + voa
else:
dp = amp.delta_p
if amp.effective_gain is None or power_mode:
gain_target = node_loss + dp - prev_dp + prev_voa
else: # gain mode with effective_gain
gain_target = amp.effective_gain
dp = prev_dp - node_loss - prev_voa + gain_target
power_target = pref_total_db + dp
amp.delta_p = dp if power_mode else None
amp.effective_gain = gain_target
set_amplifier_voa(amp, power_target, power_mode)
amp._delta_p = amp.delta_p if power_mode else dp
# target_pch_out_dbm records target power for design: If user defines one, then this is displayed,
# else display the one computed during design
if amp.delta_p is not None and amp.operational.delta_p is not None:
# use the user defined target
amp.target_pch_out_dbm = round(amp.operational.delta_p + pref_ch_db, 2)
elif amp.delta_p is not None:
# use the design target if no target were set
amp.target_pch_out_dbm = round(amp.delta_p + pref_ch_db, 2)
elif amp.delta_p is None:
amp.target_pch_out_dbm = None
prev_dp = dp
prev_voa = voa
prev_node = node
@@ -549,6 +578,10 @@ def set_roadm_input_powers(network, roadm, equipment, pref_ch_db):
node.get_per_degree_ref_power(degree=previous_node.uid) - loss
elif isinstance(node, elements.Transceiver):
roadm.ref_pch_in_dbm[element.uid] = pref_ch_db - loss
elif isinstance(node, elements.Multiband_amplifier):
# use the worst (min) value among amps
roadm.ref_pch_in_dbm[element.uid] = min([pref_ch_db + amp._delta_p - amp.out_voa - loss
for amp in node.amplifiers.values()])
# check if target power can be met
temp = []
if roadm.per_degree_pch_out_dbm:
@@ -598,6 +631,9 @@ def set_fiber_input_power(network, fiber, equipment, pref_ch_db):
fiber.ref_pch_in_dbm = pref_ch_db + node._delta_p - node.out_voa - loss
elif isinstance(node, elements.Transceiver):
fiber.ref_pch_in_dbm = pref_ch_db - loss
elif isinstance(node, elements.Multiband_amplifier):
# use the worst (min) value among amps
fiber.ref_pch_in_dbm = min([pref_ch_db + amp._delta_p - amp.out_voa - loss for amp in node.amplifiers.values()])
def set_roadm_internal_paths(roadm, network):
@@ -658,7 +694,8 @@ def set_roadm_internal_paths(roadm, network):
def add_roadm_booster(network, roadm):
next_nodes = [n for n in network.successors(roadm)
if not isinstance(n, (elements.Transceiver, elements.Fused, elements.Edfa))]
if not isinstance(n, (elements.Transceiver, elements.Fused, elements.Edfa,
elements.Multiband_amplifier))]
# no amplification for fused spans or TRX
for next_node in next_nodes:
network.remove_edge(roadm, next_node)
@@ -684,7 +721,8 @@ def add_roadm_booster(network, roadm):
def add_roadm_preamp(network, roadm):
prev_nodes = [n for n in network.predecessors(roadm)
if not isinstance(n, (elements.Transceiver, elements.Fused, elements.Edfa))]
if not isinstance(n, (elements.Transceiver, elements.Fused, elements.Edfa,
elements.Multiband_amplifier))]
# no amplification for fused spans or TRX
for prev_node in prev_nodes:
network.remove_edge(prev_node, roadm)

View File

@@ -8,7 +8,8 @@ gnpy.core.parameters
This module contains all parameters to configure standard network elements.
"""
from collections import namedtuple
from copy import deepcopy
from dataclasses import dataclass
from scipy.constants import c, pi
from numpy import asarray, array, exp, sqrt, log, outer, ones, squeeze, append, flip, linspace, full
@@ -593,7 +594,7 @@ class EdfaParams:
def update_params(self, kwargs):
for k, v in kwargs.items():
setattr(self, k, self.update_params(**v) if isinstance(v, dict) else v)
setattr(self, k, v)
class EdfaOperational:
@@ -616,3 +617,56 @@ class EdfaOperational:
return (f'{type(self).__name__}('
f'gain_target={self.gain_target!r}, '
f'tilt_target={self.tilt_target!r})')
class MultiBandParams:
default_values = {
'bands': [],
'type_variety': '',
'type_def': None,
'allowed_for_design': False
}
def __init__(self, **params):
try:
self.update_attr(params)
except KeyError as e:
raise ParametersError(f'Multiband configurations json must include {e}. Configuration: {params}')
def update_attr(self, kwargs):
clean_kwargs = {k: v for k, v in kwargs.items() if v != ''}
for k, v in self.default_values.items():
# use deepcopy to avoid sharing same object amongst all instance when v is a list or a dict!
if isinstance(v, (list, dict)):
setattr(self, k, clean_kwargs.get(k, deepcopy(v)))
else:
setattr(self, k, clean_kwargs.get(k, v))
@dataclass
class FrequencyBand:
"""Frequency band
"""
f_min: float
f_max: float
DEFAULT_BANDS_DEFINITION = {
"LBAND": FrequencyBand(f_min=187e12, f_max=189e12),
"CBAND": FrequencyBand(f_min=191.3e12, f_max=196.0e12)
}
# use this definition to index amplifiers'element of a multiband amplifier.
# this is not the design band
def find_band_name(band: FrequencyBand) -> str:
"""return the default band name (CBAND, LBAND, ...) that corresponds to the band frequency range
Use the band center frequency: if center frequency is inside the band then returns CBAND.
This is to flexibly encompass all kind of bands definitions.
returns the first matching band name.
"""
for band_name, frequency_range in DEFAULT_BANDS_DEFINITION.items():
center_frequency = (band.f_min + band.f_max) / 2
if center_frequency >= frequency_range.f_min and center_frequency <= frequency_range.f_max:
return band_name
return 'unknown_band'

View File

@@ -461,6 +461,26 @@ def calculate_absolute_min_or_zero(x: array) -> array:
return (abs(x) - x) / 2
def nice_column_str(data: List[List[str]], max_length: int = 30, padding: int = 1) -> str:
"""data is a list of rows, creates strings with nice alignment per colum and padding with spaces
letf justified
>>> table_data = [['aaa', 'b', 'c'], ['aaaaaaaa', 'bbb', 'c'], ['a', 'bbbbbbbbbb', 'c']]
>>> print(nice_column_str(table_data))
aaa b c
aaaaaaaa bbb c
a bbbbbbbbbb c
"""
# transpose data to determine size of columns
transposed_data = list(map(list, zip(*data)))
column_width = [max(len(word) for word in column) + padding for column in transposed_data]
nice_str = []
for row in data:
column = ''.join(word[0:max_length].ljust(min(width, max_length)) for width, word in zip(column_width, row))
nice_str.append(f'{column}')
return '\n'.join(nice_str)
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

View File

@@ -228,7 +228,7 @@ def transmission_main_example(args=None):
print(f'Input optical power reference in span = {ansi_escapes.cyan}{power_dbm:.2f} '
+ f'dBm{ansi_escapes.reset}:')
else:
print('\nPropagating in gain mode: power cannot be set manually')
print('\nPropagating in {ansi_escapes.cyan}gain mode{ansi_escapes.reset}: power cannot be set manually')
if len(powers_dbm) == 1:
for elem in path:
print(elem)

View File

@@ -14,14 +14,15 @@ from pathlib import Path
import json
from collections import namedtuple
from numpy import arange
from copy import deepcopy
from gnpy.core import elements
from gnpy.core.equipment import trx_mode_params
from gnpy.core.equipment import trx_mode_params, find_type_variety
from gnpy.core.exceptions import ConfigurationError, EquipmentConfigError, NetworkTopologyError, ServiceError
from gnpy.core.science_utils import estimate_nf_model
from gnpy.core.info import Carrier
from gnpy.core.utils import automatic_nch, automatic_fmax, merge_amplifier_restrictions, dbm2watt
from gnpy.core.parameters import DEFAULT_RAMAN_COEFFICIENT, EdfaParams
from gnpy.core.parameters import DEFAULT_RAMAN_COEFFICIENT, EdfaParams, MultiBandParams
from gnpy.topology.request import PathRequest, Disjunction, compute_spectrum_slot_vs_bandwidth
from gnpy.topology.spectrum_assignment import mvalue_to_slots
from gnpy.tools.convert import xls_to_json_data
@@ -197,6 +198,7 @@ class Amp(_JsonThing):
type_def = kwargs.get('type_def', 'variable_gain') # default compatibility with older json eqpt files
nf_def = None
dual_stage_def = None
amplifiers = None
if type_def == 'fixed_gain':
try:
@@ -241,16 +243,25 @@ class Amp(_JsonThing):
preamp_variety = kwargs.pop('preamp_variety')
booster_variety = kwargs.pop('booster_variety')
except KeyError:
msg = f'missing preamp/booster variety input for amplifier: {type_variety} in equipment config'
raise EquipmentConfigError(msg)
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)
elif type_def == 'multi_band':
amplifiers = kwargs['amplifiers']
else:
raise EquipmentConfigError(f'Edfa type_def {type_def} does not exist')
json_data = load_json(config)
# raise an error if config does not contain f_min, f_max
if 'f_min' not in json_data or 'f_max' not in json_data:
raise EquipmentConfigError('default Edfa config does not contain f_min and f_max values.'
+ ' Please correct file.')
# use f_min, f_max from kwargs
if 'f_min' in kwargs:
json_data.pop('f_min', None)
json_data.pop('f_max', None)
return cls(**{**kwargs, **json_data,
'nf_model': nf_def, 'dual_stage_model': dual_stage_def})
'nf_model': nf_def, 'dual_stage_model': dual_stage_def, 'multi_band': amplifiers})
def _automatic_spacing(baud_rate):
@@ -490,6 +501,8 @@ def _cls_for(equipment_type):
return elements.Fiber
elif equipment_type == 'RamanFiber':
return elements.RamanFiber
elif equipment_type == 'Multiband_amplifier':
return elements.Multiband_amplifier
else:
raise ConfigurationError(f'Unknown network equipment "{equipment_type}"')
@@ -503,7 +516,53 @@ def network_from_json(json_data, equipment):
typ = el_config.pop('type')
variety = el_config.pop('type_variety', 'default')
cls = _cls_for(typ)
if typ == 'Fused':
if typ == 'Multiband_amplifier':
if variety in ['default', '']:
extra_params = None
temp = el_config.setdefault('params', {})
temp = merge_amplifier_restrictions(temp, deepcopy(MultiBandParams.default_values))
el_config['params'] = temp
else:
extra_params = equipment['Edfa'][variety]
temp = el_config.setdefault('params', {})
# use config params preferably to library params, only use library params to fill in
# the missing attribute
temp = merge_amplifier_restrictions(temp, deepcopy(extra_params.__dict__))
el_config['params'] = temp
el_config['type_variety'] = variety
# if config does not contain any amp list create one
amps = el_config.setdefault('amplifiers', [])
for amp in amps:
amp_variety = amp['type_variety'] # juste pour essayer
amp_extra_params = equipment['Edfa'][amp_variety]
temp = amp.setdefault('params', {})
temp = merge_amplifier_restrictions(temp, amp_extra_params.__dict__)
amp['params'] = temp
amp['type_variety'] = amp_variety
# check type_variety consistant with amps type_variety
if amps:
try:
multiband_type_variety = find_type_variety([a['type_variety'] for a in amps], equipment)
except ConfigurationError as e:
msg = f'Node {el_config["uid"]}: {e}'
raise ConfigurationError(msg)
if variety is not None and variety != multiband_type_variety:
raise ConfigurationError(f'In node {el_config["uid"]}: multiband amplifier type_variety is not '
+ 'consistent with its amps type varieties.')
if not amps and extra_params is not None:
# the amp config does not contain the amplifiers operational settings, but has a type_variety
# defined so that it is possible to create the template of amps for design for each band. This
# defines the default design bands.
# This lopp populates each amp with default values, for each band
for band in extra_params.bands:
params = {k: v for k, v in Amp.default_values.items()}
# update frequencies with band values
params['f_min'] = band['f_min']
params['f_max'] = band['f_max']
amps.append({'params': params})
# without type_variety, it is not possible to set the amplifier dict at this point: need to wait
# for design, and use user defined design-bands
elif typ == 'Fused':
# well, there's no variety for the 'Fused' node type
pass
elif variety in equipment[typ]:

View File

@@ -23,7 +23,7 @@ from networkx import (dijkstra_path, NetworkXNoPath,
from networkx.utils import pairwise
from numpy import mean, argmin
from gnpy.core.elements import Transceiver, Roadm, Edfa
from gnpy.core.elements import Transceiver, Roadm, Edfa, Multiband_amplifier
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
@@ -1255,5 +1255,5 @@ 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))]
amp_bands = [n.params.bands for n in el_list if isinstance(n, (Edfa, Multiband_amplifier))]
return find_common_range(amp_bands, equipment['SI']['default'].f_min, equipment['SI']['default'].f_max)

View File

@@ -15,7 +15,8 @@ element/oms correspondace
from collections import namedtuple
from logging import getLogger
from gnpy.core.elements import Roadm, Transceiver, Edfa
from gnpy.core.elements import Roadm, Transceiver, Edfa, Multiband_amplifier
from gnpy.core.exceptions import ServiceError, SpectrumError
from gnpy.core.utils import order_slots, restore_order
from gnpy.topology.request import compute_spectrum_slot_vs_bandwidth, find_elements_common_range
@@ -230,7 +231,7 @@ def align_grids(oms_list):
def find_network_freq_range(network, equipment):
"""Find the lowest freq from amps and highest freq among all amps to determine the resulting bitmap
"""
amp_bands = [band for n in network.nodes() if isinstance(n, Edfa) for band in n.params.bands]
amp_bands = [band for n in network.nodes() if isinstance(n, (Edfa, Multiband_amplifier)) for band in n.params.bands]
min_frequencies = [a['f_min'] for a in amp_bands]
max_frequencies = [a['f_max'] for a in amp_bands]
return min(min_frequencies), max(max_frequencies)

View File

@@ -9,7 +9,7 @@ from gnpy.core.elements import Transceiver, Edfa, Fiber
from gnpy.core.utils import automatic_fmax, lin2db, db2lin, merge_amplifier_restrictions, dbm2watt, watt2dbm
from gnpy.core.info import create_input_spectral_information, create_arbitrary_spectral_information
from gnpy.core.network import build_network, set_amplifier_voa
from gnpy.tools.json_io import load_network, load_equipment, network_from_json
from gnpy.tools.json_io import load_network, load_equipment, load_json, _equipment_from_json, network_from_json
from pathlib import Path
import pytest
@@ -365,3 +365,141 @@ def test_set_out_voa():
assert amp.out_voa == 4.0
assert amp.effective_gain == 20.0 + 4.0
assert amp.delta_p == -3.0 + 4.0
def test_multiband():
equipment_json = load_json(eqpt_library)
# add some multiband amplifiers
amps = [
{
"type_variety": "std_medium_gain_C",
"f_min": 191.25e12,
"f_max": 196.15e12,
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": False,
"allowed_for_design": True},
{
"type_variety": "std_medium_gain_L",
"f_min": 186.55e12,
"f_max": 190.05e12,
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": False,
"allowed_for_design": True},
{
"type_variety": "std_medium_gain_multiband",
"type_def": "multi_band",
"amplifiers": [
"std_medium_gain_C",
"std_medium_gain_L"
],
"allowed_for_design": False
}
]
equipment_json['Edfa'].extend(amps)
equipment = _equipment_from_json(equipment_json, eqpt_library)
el_config = {
"uid": "Edfa1",
"type": "Multiband_amplifier",
"type_variety": "std_medium_gain_multiband",
"amplifiers": [
{
"type_variety": "std_medium_gain_C",
"operational": {
"gain_target": 22.55,
"delta_p": 0.9,
"out_voa": 3.0,
"tilt_target": 0.0,
}
},
{
"type_variety": "std_medium_gain_L",
"operational": {
"gain_target": 21,
"delta_p": 3.0,
"out_voa": 3.0,
"tilt_target": 0.0,
}
}
]
}
fused_config = {
"uid": "[83/WR-2-4-SIG=>930/WRT-1-2-SIG]-Tl/9300",
"type": "Fused",
"params": {
"loss": 20
}
}
json_data = {
"elements": [
el_config,
fused_config
],
"connections": []
}
network = network_from_json(json_data, equipment)
amp = next(n for n in network.nodes() if n.uid == 'Edfa1')
fused = next(n for n in network.nodes() if n.uid == '[83/WR-2-4-SIG=>930/WRT-1-2-SIG]-Tl/9300')
si = create_input_spectral_information(f_min=186e12, f_max=196e12, roll_off=0.15, baud_rate=32e9, tx_power=1e-3,
spacing=50e9, tx_osnr=40.0)
assert si.number_of_channels == 200
si = fused(si)
si = amp(si)
# assert nb of channel after mux/demux
assert si.number_of_channels == 164 # computed based on amp bands
# Check that multiband amp is correctly created with correct __str__
actual_c_amp = amp.amplifiers["CBAND"].__str__()
expected_c_amp = '\n'.join([
'Edfa Edfa1',
' type_variety: std_medium_gain_C',
' effective gain(dB): 21.22',
' (before att_in and before output VOA)',
' noise figure (dB): 6.32',
' (including att_in)',
' pad att_in (dB): 0.00',
' Power In (dBm): -0.22',
' Power Out (dBm): 21.01',
' Delta_P (dB): 0.90',
' target pch (dBm): None',
' actual pch out (dBm): -1.77',
' output VOA (dB): 3.00'])
assert actual_c_amp == expected_c_amp
actual_l_amp = amp.amplifiers["LBAND"].__str__()
expected_l_amp = '\n'.join([
'Edfa Edfa1',
' type_variety: std_medium_gain_L',
' effective gain(dB): 21.00',
' (before att_in and before output VOA)',
' noise figure (dB): 6.36',
' (including att_in)',
' pad att_in (dB): 0.00',
' Power In (dBm): -1.61',
' Power Out (dBm): 19.40',
' Delta_P (dB): 3.00',
' target pch (dBm): None',
' actual pch out (dBm): -1.99',
' output VOA (dB): 3.00'])
assert actual_l_amp == expected_l_amp
# check that f_min, f_max of si are within amp band
assert amp.amplifiers["LBAND"].params.f_min == 186.55e12
assert si.frequency[0] >= amp.amplifiers["LBAND"].params.f_min
assert amp.amplifiers["CBAND"].params.f_max == 196.15e12
assert si.frequency[-1] <= amp.amplifiers["CBAND"].params.f_max
for freq in si.frequency:
if freq > 190.05e12:
assert freq >= 191.25e12
if freq < 191.25e12:
assert freq <= 190.25e12

View File

@@ -36,7 +36,7 @@ SRC_ROOT = Path(__file__).parent.parent
('transmission_long_psd', None, transmission_main_example,
['-e', 'tests/data/eqpt_config_psd.json', 'tests/data/test_long_network.json', '--spectrum', 'gnpy/example-data/initial_spectrum2.json', ]),
('transmission_long_psw', None, transmission_main_example,
['-e', 'tests/data/eqpt_config_psw.json', 'tests/data/test_long_network.json', '--spectrum', 'gnpy/example-data/initial_spectrum2.json', ]),
['-e', 'tests/data/eqpt_config_psw.json', 'tests/data/test_long_network.json', '--spectrum', 'gnpy/example-data/initial_spectrum2.json', ])
))
def test_example_invocation(capfd, caplog, output, log, handler, args):
"""Make sure that our examples produce useful output"""