Add tx_osnr in spectral information

This change enables to use a different tx_osnr per carrier.

If tx_osnr is defined via spectrum then use it to define a tx_osnr per
carrier in si else use the tx_osnr of request to set tx_osnr of si.

Then, the propagate function for requests is changed to update OSNR with
tx_OSNR per carrier defined in si.

TODO: The tx_osnr defined in spectrum is not yet taken into account for
the propagate_and_optimize function, because the loop that optimizes
the choice for the mode only loops on baudrate.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0fcdf559d4f1f8f0047faa257076084ec7adcc77
This commit is contained in:
EstherLerouzic
2022-08-18 11:20:17 +02:00
parent e143d25339
commit bd6b278dd1
7 changed files with 46 additions and 25 deletions

View File

@@ -55,7 +55,7 @@ class SpectralInformation(object):
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, pdl: array, delta_pdb_per_channel: array,
ref_power: Pref):
tx_osnr: array, ref_power: Pref):
indices = argsort(frequency)
self._frequency = frequency[indices]
self._df = outer(ones(frequency.shape), frequency) - outer(frequency, ones(frequency.shape))
@@ -81,6 +81,7 @@ class SpectralInformation(object):
self._pmd = pmd[indices]
self._pdl = pdl[indices]
self._delta_pdb_per_channel = delta_pdb_per_channel[indices]
self._tx_osnr = tx_osnr[indices]
self._pref = ref_power
@property
@@ -178,6 +179,14 @@ class SpectralInformation(object):
def delta_pdb_per_channel(self, delta_pdb_per_channel):
self._delta_pdb_per_channel = delta_pdb_per_channel
@property
def tx_osnr(self):
return self._tx_osnr
@tx_osnr.setter
def tx_osnr(self, tx_osnr):
self._tx_osnr = tx_osnr
@property
def channel_number(self):
return self._channel_number
@@ -226,6 +235,7 @@ class SpectralInformation(object):
pdl=append(self.pdl, other.pdl),
delta_pdb_per_channel=append(self.delta_pdb_per_channel,
other.delta_pdb_per_channel),
tx_osnr=append(self.tx_osnr, other.tx_osnr),
ref_power=Pref(self.pref.p_span0, self.pref.p_spani))
except SpectrumError:
raise SpectrumError('Spectra cannot be summed: channels overlapping.')
@@ -245,6 +255,7 @@ class SpectralInformation(object):
def create_arbitrary_spectral_information(frequency: Union[ndarray, Iterable, int, float],
signal: Union[int, float, ndarray, Iterable],
baud_rate: Union[int, float, ndarray, Iterable],
tx_osnr: Union[int, float, ndarray, Iterable],
delta_pdb_per_channel: Union[int, float, ndarray, Iterable] = 0.,
slot_width: Union[int, float, ndarray, Iterable] = None,
roll_off: Union[int, float, ndarray, Iterable] = 0.,
@@ -268,12 +279,14 @@ def create_arbitrary_spectral_information(frequency: Union[ndarray, Iterable, in
nli = zeros(number_of_channels)
ase = zeros(number_of_channels)
delta_pdb_per_channel = full(number_of_channels, delta_pdb_per_channel)
tx_osnr = full(number_of_channels, tx_osnr)
return SpectralInformation(frequency=frequency, slot_width=slot_width,
signal=signal, nli=nli, ase=ase,
baud_rate=baud_rate, roll_off=roll_off,
chromatic_dispersion=chromatic_dispersion,
pmd=pmd, pdl=pdl,
delta_pdb_per_channel=delta_pdb_per_channel,
tx_osnr=tx_osnr,
ref_power=ref_power)
except ValueError as e:
if 'could not broadcast' in str(e):
@@ -282,7 +295,7 @@ def create_arbitrary_spectral_information(frequency: Union[ndarray, Iterable, in
raise
def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing):
def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing, tx_osnr):
""" Creates a fixed slot width spectral information with flat power.
all arguments are scalar values"""
number_of_channels = automatic_nch(f_min, f_max, spacing)
@@ -292,14 +305,14 @@ def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power,
delta_pdb_per_channel = zeros(number_of_channels)
return create_arbitrary_spectral_information(frequency, slot_width=spacing, signal=power, baud_rate=baud_rate,
roll_off=roll_off, delta_pdb_per_channel=delta_pdb_per_channel,
ref_power=Pref(p_span0=p_span0, p_spani=p_spani))
tx_osnr=tx_osnr, ref_power=Pref(p_span0=p_span0, p_spani=p_spani))
def carriers_to_spectral_information(initial_spectrum: dict[Union[int, float], Carrier],
ref_carrier: ReferenceCarrier) -> SpectralInformation:
"""Initial spectrum is a dict with key = carrier frequency, and value a Carrier object.
:param initial_spectrum: indexed by frequency in Hz, with power offset (delta_pdb), baudrate, slot width
and roll off.
:param initial_spectrum: indexed by frequency in Hz, with power offset (delta_pdb), baudrate, slot width,
tx_osnr and roll off.
:param ref_carrier: reference carrier (baudrate and power) used for the reference channel
"""
frequency = list(initial_spectrum.keys())
@@ -308,11 +321,12 @@ def carriers_to_spectral_information(initial_spectrum: dict[Union[int, float], C
baud_rate = [c.baud_rate for c in initial_spectrum.values()]
delta_pdb_per_channel = array([c.delta_pdb for c in initial_spectrum.values()])
slot_width = [c.slot_width for c in initial_spectrum.values()]
tx_osnr = [c.tx_osnr for c in initial_spectrum.values()]
p_span0 = watt2dbm(ref_carrier.req_power)
p_spani = watt2dbm(ref_carrier.req_power)
return create_arbitrary_spectral_information(frequency=frequency, signal=signal, baud_rate=baud_rate,
slot_width=slot_width, roll_off=roll_off,
delta_pdb_per_channel=delta_pdb_per_channel,
delta_pdb_per_channel=delta_pdb_per_channel, tx_osnr=tx_osnr,
ref_power=Pref(p_span0=p_span0, p_spani=p_spani))
@@ -326,6 +340,7 @@ class Carrier:
baud_rate: float
slot_width: float
roll_off: float
tx_osnr: float
@dataclass

View File

@@ -356,19 +356,19 @@ def propagate(path, req, equipment):
ref_carrier=ref_carrier(req.power, equipment))
else:
si = create_input_spectral_information(
req.f_min, req.f_max, req.roll_off, req.baud_rate,
req.power, req.spacing)
f_min=req.f_min, f_max=req.f_max, roll_off=req.roll_off, baud_rate=req.baud_rate,
power=req.power, spacing=req.spacing, tx_osnr=req.tx_osnr)
for i, el in enumerate(path):
if isinstance(el, Roadm):
si = el(si, degree=path[i+1].uid)
else:
si = el(si)
path[0].update_snr(req.tx_osnr)
path[0].update_snr(si.tx_osnr)
path[0].calc_penalties(req.penalties)
if any(isinstance(el, Roadm) for el in path):
path[-1].update_snr(req.tx_osnr, equipment['Roadm']['default'].add_drop_osnr)
path[-1].update_snr(si.tx_osnr, equipment['Roadm']['default'].add_drop_osnr)
else:
path[-1].update_snr(req.tx_osnr)
path[-1].update_snr(si.tx_osnr)
path[-1].calc_penalties(req.penalties)
return si
@@ -389,9 +389,8 @@ def propagate_and_optimize_mode(path, req, equipment):
float(this_mode['min_spacing']) <= req.spacing]
modes_to_explore = sorted(modes_to_explore,
key=lambda x: x['bit_rate'], reverse=True)
# print(modes_to_explore)
# step2: computes propagation for each baudrate: stop and select the first that passes
# TODO: the case of roll of is not included: for now use SI one
# TODO: the case of roll off is not included: for now use SI one
# TODO: if the loop in mode optimization does not have a feasible path, then bugs
if req.initial_spectrum is not None:
# this case is not yet handled: spectrum can not be defined for the path-request-run function
@@ -401,7 +400,8 @@ def propagate_and_optimize_mode(path, req, equipment):
raise ServiceError(msg)
spc_info = create_input_spectral_information(f_min=req.f_min, f_max=req.f_max,
roll_off=equipment['SI']['default'].roll_off,
baud_rate=this_br, power=req.power, spacing=req.spacing)
baud_rate=this_br, power=req.power, spacing=req.spacing,
tx_osnr=req.tx_osnr)
for i, el in enumerate(path):
if isinstance(el, Roadm):
spc_info = el(spc_info, degree=path[i+1].uid)

View File

@@ -73,7 +73,8 @@ def si(nch_and_spacing, 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)
return create_input_spectral_information(f_min=f_min, f_max=f_max, roll_off=0.15, baud_rate=bw, power=1e-3,
spacing=spacing, tx_osnr=40.0)
@pytest.mark.parametrize("gain, nf_expected", [(10, 15), (15, 10), (25, 5.8)])

View File

@@ -12,6 +12,7 @@ def test_create_arbitrary_spectral_information():
si = create_arbitrary_spectral_information(frequency=[193.25e12, 193.3e12, 193.35e12],
baud_rate=32e9, signal=[1, 1, 1],
delta_pdb_per_channel=[1, 1, 1],
tx_osnr=40.0,
ref_power=Pref(1, 1))
assert_array_equal(si.baud_rate, array([32e9, 32e9, 32e9]))
assert_array_equal(si.slot_width, array([37.5e9, 37.5e9, 37.5e9]))
@@ -25,6 +26,7 @@ def test_create_arbitrary_spectral_information():
assert_array_equal(si.channel_number, array([1, 2, 3]))
assert_array_equal(si.number_of_channels, 3)
assert_array_equal(si.df, array([[0, 50e9, 100e9], [-50e9, 0, 50e9], [-100e9, -50e9, 0]]))
assert_array_equal(si.tx_osnr, array([40.0, 40.0, 40.0]))
with pytest.raises(SpectrumError, match='Spectra cannot be summed: channels overlapping.'):
si += si
@@ -32,6 +34,7 @@ def test_create_arbitrary_spectral_information():
si = create_arbitrary_spectral_information(frequency=array([193.35e12, 193.3e12, 193.25e12]),
slot_width=array([50e9, 50e9, 50e9]),
baud_rate=32e9, signal=array([1, 2, 3]),
tx_osnr=40.0,
ref_power=Pref(1, 1))
assert_array_equal(si.signal, array([3, 2, 1]))
@@ -39,17 +42,18 @@ def test_create_arbitrary_spectral_information():
r'larger than the slot width for channels: \[1, 3\].'):
create_arbitrary_spectral_information(frequency=[193.25e12, 193.3e12, 193.35e12], signal=1,
baud_rate=[64e9, 32e9, 64e9], slot_width=50e9,
tx_osnr=40.0,
ref_power=Pref(1, 1))
with pytest.raises(SpectrumError, match='Spectrum required slot widths larger than the frequency spectral '
r'distances between channels: \[\(1, 2\), \(3, 4\)\].'):
create_arbitrary_spectral_information(frequency=[193.26e12, 193.3e12, 193.35e12, 193.39e12], signal=1,
baud_rate=32e9, slot_width=50e9, ref_power=Pref(1, 1))
tx_osnr=40.0, baud_rate=32e9, slot_width=50e9, ref_power=Pref(1, 1))
with pytest.raises(SpectrumError, match='Spectrum required slot widths larger than the frequency spectral '
r'distances between channels: \[\(1, 2\), \(2, 3\)\].'):
create_arbitrary_spectral_information(frequency=[193.25e12, 193.3e12, 193.35e12], signal=1, baud_rate=49e9,
roll_off=0.1, ref_power=Pref(1, 1))
tx_osnr=40.0, roll_off=0.1, ref_power=Pref(1, 1))
with pytest.raises(SpectrumError,
match='Dimension mismatch in input fields.'):
create_arbitrary_spectral_information(frequency=[193.25e12, 193.3e12, 193.35e12], signal=[1, 2], baud_rate=49e9,
ref_power=Pref(1, 1))
tx_osnr=40.0, ref_power=Pref(1, 1))

View File

@@ -45,7 +45,8 @@ def propagation(input_power, con_in, con_out, dest):
p = input_power
p = db2lin(p) * 1e-3
spacing = 50e9 # THz
si = create_input_spectral_information(191.3e12, 191.3e12 + 79 * spacing, 0.15, 32e9, p, spacing)
si = create_input_spectral_information(f_min=191.3e12, f_max=191.3e12 + 79 * spacing, roll_off=0.15,
baud_rate=32e9, power=p, spacing=spacing, tx_osnr=None)
source = next(transceivers[uid] for uid in transceivers if uid == 'trx A')
sink = next(transceivers[uid] for uid in transceivers if uid == dest)
path = dijkstra_path(network, source, sink)

View File

@@ -252,8 +252,8 @@ def test_roadm_target_power(prev_node_type, effective_pch_out_db, power_dbm):
req.power = db2lin(power_dbm - 30)
path = compute_constrained_path(network, req)
si = create_input_spectral_information(
req.f_min, req.f_max, req.roll_off, req.baud_rate,
req.power, req.spacing)
f_min=req.f_min, f_max=req.f_max, roll_off=req.roll_off, baud_rate=req.baud_rate,
power=req.power, spacing=req.spacing, tx_osnr=req.tx_osnr)
for i, el in enumerate(path):
if isinstance(el, Roadm):
power_in_roadm = si.signal + si.ase + si.nli

View File

@@ -28,7 +28,7 @@ def test_fiber():
# fix grid spectral information generation
spectral_info_input = create_input_spectral_information(f_min=191.3e12, f_max=196.1e12, roll_off=0.15,
baud_rate=32e9, power=1e-3, spacing=50e9)
baud_rate=32e9, power=1e-3, spacing=50e9, tx_osnr=40.0)
# propagation
spectral_info_out = fiber(spectral_info_input)
@@ -49,7 +49,7 @@ def test_fiber():
spectral_info_input = create_arbitrary_spectral_information(frequency=frequency, slot_width=slot_width,
signal=signal, baud_rate=baud_rate, roll_off=0.15,
delta_pdb_per_channel=delta_pdb_per_channel,
ref_power=pref)
tx_osnr=40.0, ref_power=pref)
# propagation
spectral_info_out = fiber(spectral_info_input)
@@ -67,7 +67,7 @@ def test_raman_fiber():
""" Test the accuracy of propagating the RamanFiber."""
# spectral information generation
spectral_info_input = create_input_spectral_information(f_min=191.3e12, f_max=196.1e12, roll_off=0.15,
baud_rate=32e9, power=1e-3, spacing=50e9)
baud_rate=32e9, power=1e-3, spacing=50e9, tx_osnr=40.0)
SimParams.set_params(load_json(TEST_DIR / 'data' / 'sim_params.json'))
fiber = RamanFiber(**load_json(TEST_DIR / 'data' / 'test_science_utils_fiber_config.json'))
@@ -104,7 +104,7 @@ def test_fiber_lumped_losses_srs(set_sim_params):
""" Test the accuracy of Fiber with lumped losses propagation."""
# spectral information generation
spectral_info_input = create_input_spectral_information(f_min=191.3e12, f_max=196.1e12, roll_off=0.15,
baud_rate=32e9, power=1e-3, spacing=50e9)
baud_rate=32e9, power=1e-3, spacing=50e9, tx_osnr=40.0)
SimParams.set_params(load_json(TEST_DIR / 'data' / 'sim_params.json'))
fiber = Fiber(**load_json(TEST_DIR / 'data' / 'test_lumped_losses_raman_fiber_config.json'))