mirror of
https://github.com/Telecominfraproject/oopt-gnpy.git
synced 2025-11-01 18:47:48 +00:00
The idea behind this change is to reproduce the exact same behaviour as with the scalar, but accounting for variable levels of powers. - delete the neq_ch: equivalent channel count in dB because with mixed rates and power such a value has limited utility - instead creates a vector that records the 'user defined' distribution of power. This vector is used as a reference for channel equalization out of the ROADM. If target_power_per_channel has some channels power above input power, then the whole target is reduced. For example, if user specifies delta_pdb_per_channel: freq1: 1dB, freq2: 3dB, freq3: -3dB, and target is -20dBm out of the ROADM, then the target power for each channel uses the specified delta_pdb_per_channel. target_power_per_channel[f1, f2, f3] = -19, -17, -23 However if input_signal = -23, -16, -26, then the target can not be applied, because -23 < -19dBm and -26 < -23dBm, and a reduction must be applied (ROADM can not amplify). Then the target is only applied to signals whose power is above the threshold. others are left unchanged and unequalized. the new target is [-23, -17, -26] and the attenuation to apply is [-23, -16, -26] - [-23, -17, -26] = [0, 1, 0] Important note: This changes the previous behaviour that equalized all identical channels based on the one that had the min power !! TODO: in coming refactor where transmission and design will be properly separated, the initial behaviour may be set again as a design choice. This change corresponds to a discussion held during coders call. Please look at this document for a reference: https://telecominfraproject.atlassian.net/wiki/spaces/OOPT/pages/669679645/PSE+Meeting+Minutes - in amplifier: the saturation is computed based on this vector delta_pdb_per_channel, instead of the nb of channels. The target of the future refactor will be to use the effective carrier's power. I prefer to have this first step, because this is how it is implemented today (ie based on the noiseless reference), and I would like first to add more behaviour tests before doing this refactor (would it be needed). - in spectralInfo class, change pref to a Pref object to enable both p_span0 and p_spani to be conveyed during propagation of spectral_information in elements. No refactor of them at this point. Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com> Change-Id: I591027cdd08e89098330c7d77d6f50212f4d4724
197 lines
6.8 KiB
Python
197 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# @Author: Jean-Luc Auge
|
|
# @Date: 2018-02-02 14:06:55
|
|
|
|
from numpy import zeros, array
|
|
from gnpy.core.elements import Transceiver, Edfa
|
|
from gnpy.core.utils import automatic_fmax, lin2db, db2lin, merge_amplifier_restrictions
|
|
from gnpy.core.info import create_input_spectral_information, Pref
|
|
from gnpy.core.network import build_network
|
|
from gnpy.tools.json_io import load_network, load_equipment
|
|
from pathlib import Path
|
|
import pytest
|
|
|
|
TEST_DIR = Path(__file__).parent
|
|
DATA_DIR = TEST_DIR / 'data'
|
|
test_network = DATA_DIR / 'test_network.json'
|
|
eqpt_library = DATA_DIR / 'eqpt_config.json'
|
|
|
|
# TODO in elements.py code: pytests doesn't pass with 1 channel: interpolate fail
|
|
|
|
|
|
@pytest.fixture(
|
|
params=[(96, 0.05e12), (60, 0.075e12), (45, 0.1e12), (2, 0.1e12)],
|
|
ids=['50GHz spacing', '75GHz spacing', '100GHz spacing', '2 channels'])
|
|
def nch_and_spacing(request):
|
|
"""parametrize channel count vs channel spacing (Hz)"""
|
|
yield request.param
|
|
|
|
|
|
@pytest.fixture()
|
|
def bw():
|
|
"""parametrize signal bandwidth (Hz)"""
|
|
return 45e9
|
|
|
|
|
|
@pytest.fixture()
|
|
def setup_edfa_variable_gain():
|
|
"""init edfa class by reading test_network.json file
|
|
remove all gain and nf ripple"""
|
|
equipment = load_equipment(eqpt_library)
|
|
network = load_network(test_network, equipment)
|
|
build_network(network, equipment, 0, 20)
|
|
edfa = [n for n in network.nodes() if isinstance(n, Edfa)][0]
|
|
edfa.gain_ripple = zeros(96)
|
|
edfa.interpol_nf_ripple = zeros(96)
|
|
yield edfa
|
|
|
|
|
|
@pytest.fixture()
|
|
def setup_edfa_fixed_gain():
|
|
"""init edfa class by reading the 2nd edfa in test_network.json file"""
|
|
equipment = load_equipment(eqpt_library)
|
|
network = load_network(test_network, equipment)
|
|
build_network(network, equipment, 0, 20)
|
|
edfa = [n for n in network.nodes() if isinstance(n, Edfa)][1]
|
|
yield edfa
|
|
|
|
|
|
@pytest.fixture()
|
|
def setup_trx():
|
|
"""init transceiver class to access snr and osnr calculations"""
|
|
equipment = load_equipment(eqpt_library)
|
|
network = load_network(test_network, equipment)
|
|
build_network(network, equipment, 0, 20)
|
|
trx = [n for n in network.nodes() if isinstance(n, Transceiver)][0]
|
|
return trx
|
|
|
|
|
|
@pytest.fixture()
|
|
def si(nch_and_spacing, bw):
|
|
"""parametrize a channel comb with nb_channel, spacing and signal bw"""
|
|
nb_channel, spacing = nch_and_spacing
|
|
f_min = 191.3e12
|
|
f_max = automatic_fmax(f_min, spacing, nb_channel)
|
|
return create_input_spectral_information(f_min, f_max, 0.15, bw, 1e-3, spacing)
|
|
|
|
|
|
@pytest.mark.parametrize("gain, nf_expected", [(10, 15), (15, 10), (25, 5.8)])
|
|
def test_variable_gain_nf(gain, nf_expected, setup_edfa_variable_gain, si):
|
|
"""=> unitary test for variable gain model Edfa._calc_nf() (and Edfa.interpol_params)"""
|
|
edfa = setup_edfa_variable_gain
|
|
si.signal /= db2lin(gain)
|
|
si.nli /= db2lin(gain)
|
|
si.ase /= db2lin(gain)
|
|
edfa.operational.gain_target = gain
|
|
si.pref = si.pref._replace(p_span0=0, p_spani=-gain)
|
|
edfa.interpol_params(si)
|
|
result = edfa.nf
|
|
assert pytest.approx(nf_expected, abs=0.01) == result[0]
|
|
|
|
|
|
@pytest.mark.parametrize("gain, nf_expected", [(15, 10), (20, 5), (25, 5)])
|
|
def test_fixed_gain_nf(gain, nf_expected, setup_edfa_fixed_gain, si):
|
|
"""=> unitary test for fixed gain model Edfa._calc_nf() (and Edfa.interpol_params)"""
|
|
edfa = setup_edfa_fixed_gain
|
|
si.signal /= db2lin(gain)
|
|
si.nli /= db2lin(gain)
|
|
si.ase /= db2lin(gain)
|
|
edfa.operational.gain_target = gain
|
|
si.pref = si.pref._replace(p_span0=0, p_spani=-gain)
|
|
edfa.interpol_params(si)
|
|
assert pytest.approx(nf_expected, abs=0.01) == edfa.nf[0]
|
|
|
|
|
|
def test_si(si, nch_and_spacing):
|
|
"""basic total power check of the channel comb generation"""
|
|
nb_channel = nch_and_spacing[0]
|
|
p_tot = sum(si.signal + si.ase + si.nli)
|
|
expected_p_tot = si.signal[0] * nb_channel
|
|
assert pytest.approx(expected_p_tot, abs=0.01) == p_tot
|
|
|
|
|
|
@pytest.mark.parametrize("gain", [17, 19, 21, 23])
|
|
def test_compare_nf_models(gain, setup_edfa_variable_gain, si):
|
|
""" compare the 2 amplifier models (polynomial and estimated from nf_min and max)
|
|
=> nf_model vs nf_poly_fit for intermediate gain values:
|
|
between gain_min and gain_flatmax some discrepancy is expected but target < 0.5dB
|
|
=> unitary test for Edfa._calc_nf (and Edfa.interpol_params)"""
|
|
edfa = setup_edfa_variable_gain
|
|
si.signal /= db2lin(gain)
|
|
si.nli /= db2lin(gain)
|
|
si.ase /= db2lin(gain)
|
|
edfa.operational.gain_target = gain
|
|
# edfa is variable gain type
|
|
si.pref = si.pref._replace(p_span0=0, p_spani=-gain)
|
|
edfa.interpol_params(si)
|
|
nf_model = edfa.nf[0]
|
|
|
|
# change edfa type variety to a polynomial
|
|
el_config = {
|
|
"uid": "Edfa1",
|
|
"operational": {
|
|
"gain_target": gain,
|
|
"tilt_target": 0
|
|
},
|
|
"metadata": {
|
|
"location": {
|
|
"region": "",
|
|
"latitude": 2,
|
|
"longitude": 0
|
|
}
|
|
}
|
|
}
|
|
equipment = load_equipment(eqpt_library)
|
|
extra_params = equipment['Edfa']['CienaDB_medium_gain']
|
|
temp = el_config.setdefault('params', {})
|
|
temp = merge_amplifier_restrictions(temp, extra_params.__dict__)
|
|
el_config['params'] = temp
|
|
edfa = Edfa(**el_config)
|
|
|
|
# edfa is variable gain type
|
|
edfa.interpol_params(si)
|
|
nf_poly = edfa.nf[0]
|
|
print(nf_poly, nf_model)
|
|
assert pytest.approx(nf_model, abs=0.5) == nf_poly
|
|
|
|
|
|
@pytest.mark.parametrize("gain", [13, 15, 17, 19, 21, 23, 25, 27])
|
|
def test_ase_noise(gain, si, setup_trx, bw):
|
|
"""testing 3 different ways of calculating osnr:
|
|
1-pin-edfa.nf+58 vs
|
|
2-pout/pase afet propagate
|
|
3-Transceiver osnr_ase_01nm
|
|
=> unitary test for Edfa.noise_profile (Edfa.interpol_params, Edfa.propagate)"""
|
|
equipment = load_equipment(eqpt_library)
|
|
network = load_network(test_network, equipment)
|
|
edfa = next(n for n in network.nodes() if n.uid == 'Edfa1')
|
|
span = next(n for n in network.nodes() if n.uid == 'Span1')
|
|
# update span1 and Edfa1 according to new gain before building network
|
|
# updating span 1 avoids to overload amp
|
|
span.params.length = gain * 1e3 / 0.2
|
|
edfa.operational.gain_target = gain
|
|
build_network(network, equipment, 0, 20)
|
|
edfa.gain_ripple = zeros(96)
|
|
edfa.interpol_nf_ripple = zeros(96)
|
|
# propagate in span1 to have si with the correct power level
|
|
si = span(si)
|
|
print(span)
|
|
|
|
si.pref = si.pref._replace(p_span0=0, p_spani=-gain)
|
|
edfa.interpol_params(si)
|
|
nf = edfa.nf
|
|
print('nf', nf)
|
|
pin = lin2db((si.signal[0] + si.ase[0] + si.nli[0]) * 1e3)
|
|
osnr_expected = pin - nf[0] + 58
|
|
|
|
si = edfa(si)
|
|
print(edfa)
|
|
osnr = lin2db(si.signal[0] / si.ase[0]) - lin2db(12.5e9 / bw)
|
|
assert pytest.approx(osnr_expected, abs=0.01) == osnr
|
|
|
|
trx = setup_trx
|
|
si = trx(si)
|
|
osnr = trx.osnr_ase_01nm[0]
|
|
assert pytest.approx(osnr_expected, abs=0.01) == osnr
|