mirror of
https://github.com/Telecominfraproject/oopt-gnpy.git
synced 2025-10-29 17:22:42 +00:00
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:
committed by
Esther Le Rouzic
parent
920ac30aa5
commit
22fe9ead55
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"""
|
||||
|
||||
Reference in New Issue
Block a user