GGN approximation formula defined

An approximated version of the GGN is implemented to reduce the computational time enabling fast multi-band transmission simulations

Change-Id: I2951a878aa04b5eb4a33ba86d626a788c4cbb100
This commit is contained in:
AndreaDAmico
2024-06-24 00:08:05 -04:00
committed by Andrea D'Amico
parent 29f5dd1dc4
commit 42a8f018cd
3 changed files with 135 additions and 1 deletions

View File

@@ -523,6 +523,10 @@ See ``delta_power_range_db`` for more explaination.
| | | ``ggn_spectrally_separated`` (see eq. 21 |
| | | from `arXiv:1710.02225 |
| | | <https://arxiv.org/abs/1710.02225>`_). |
| | | ``ggn_approx`` (see eq. 24-25 |
| | | from `jlt:9741324 |
| | | <https://eeexplore.ieee.org/document/ |
| | | 9741324>`_). |
+---------------------------------------------+-----------+---------------------------------------------+
| ``dispersion_tolerance`` | (number) | Optional. Pure number. Tuning parameter for |
| | | ggn model solution. Default value is 1. |

View File

@@ -12,7 +12,7 @@ The solvers take as input instances of the spectral information, the fiber and t
from numpy import interp, pi, zeros, cos, array, append, ones, exp, arange, sqrt, trapz, arcsinh, clip, abs, sum, \
concatenate, flip, outer, inner, transpose, max, format_float_scientific, diag, sort, unique, argsort, cumprod, \
polyfit
polyfit, log, reshape, swapaxes, full, nan
from logging import getLogger
from scipy.constants import k, h
from scipy.interpolate import interp1d
@@ -276,6 +276,7 @@ class NliSolver:
List of implemented methods:
'gn_model_analytic': eq. 120 from arXiv:1209.0394
'ggn_spectrally_separated': eq. 21 from arXiv: 1710.02225
'ggn_approx': eq. 24-25 jlt:9741324
"""
SPM_WEIGHT = (16.0 / 27.0)
@@ -324,6 +325,28 @@ class NliSolver:
g_nli = sum(g_nli, 1)
g_nli = interp(spectral_info.frequency, cut_frequency, g_nli)
nli = spectral_info.baud_rate * g_nli # Local white noise
elif 'ggn_approx' in sim_params.nli_params.method:
if sim_params.nli_params.computed_channels is not None:
cut_indices = array(sim_params.nli_params.computed_channels) - 1
elif sim_params.nli_params.computed_number_of_channels is not None:
nb_ch_computed = sim_params.nli_params.computed_number_of_channels
nb_ch = len(spectral_info.channel_number)
cut_indices = array([round(i * (nb_ch - 1) / (nb_ch_computed - 1)) for i in range(0, nb_ch_computed)])
else:
cut_indices = array(spectral_info.channel_number) - 1
eta = NliSolver._ggn_approx(cut_indices, spectral_info, fiber, srs)
# Interpolation over the channels not indicated as computed channels in simulation parameters
cut_power = outer(spectral_info.signal[cut_indices], ones(spectral_info.number_of_channels))
cut_frequency = spectral_info.frequency[cut_indices]
pump_power = outer(ones(cut_indices.size), spectral_info.signal)
cut_baud_rate = outer(spectral_info.baud_rate[cut_indices], ones(spectral_info.number_of_channels))
g_nli = eta * cut_power * pump_power ** 2 / cut_baud_rate
g_nli = sum(g_nli, 1)
g_nli = interp(spectral_info.frequency, cut_frequency, g_nli)
nli = spectral_info.baud_rate * g_nli # Local white noise
else:
raise ValueError(f'Method {sim_params.nli_params.method} not implemented.')
@@ -526,6 +549,89 @@ class NliSolver:
freq_offset_th = ((k_ref * delta_f_ref) * rs_ref * beta2_ref) / (beta2 * symbol_rate)
return freq_offset_th
@staticmethod
def _ggn_approx(cut_indices, spectral_info: SpectralInformation, fiber, srs, spm_weight=SPM_WEIGHT,
xpm_weight=XPM_WEIGHT):
"""Computes the nonlinear interference power evaluated at the fiber input.
The method uses eq. 24-25 of https://ieeexplore.ieee.org/document/9741324
"""
# Spectral Features
nch = spectral_info.number_of_channels
frequency = spectral_info.frequency
baud_rate = spectral_info.baud_rate
slot_width = spectral_info.slot_width
roll_off = spectral_info.roll_off
df = spectral_info.df + diag(full(nch, nan))
# Physical fiber parameters
alpha = fiber.alpha(frequency)
beta2 = fiber.beta2(frequency)
gamma = outer(fiber.gamma(frequency[cut_indices]), ones(nch))
identity = diag(ones(nch))
weight = spm_weight * identity + xpm_weight * (ones([nch, nch]) - identity)
weight = weight[cut_indices, :]
dispersion_tolerance = sim_params.nli_params.dispersion_tolerance
phase_shift_tolerance = sim_params.nli_params.phase_shift_tolerance
max_slot_width = max(slot_width)
max_beta2 = max(abs(beta2))
delta_z = sim_params.raman_params.result_spatial_resolution
# Approximation psi
loss_profile = srs.loss_profile[:nch]
z = srs.z
psi = NliSolver._approx_psi(df=df, frequency=frequency, beta2=beta2, baud_rate=baud_rate,
loss_profile=loss_profile, z=z)
# GGN for SPM
for cut_index in cut_indices:
dn = 0
cut_frequency = frequency[cut_index]
cut_baud_rate = baud_rate[cut_index]
cut_roll_off = roll_off[cut_index]
cut_beta2 = beta2[cut_index]
cut_alpha = alpha[cut_index]
k_tol = dispersion_tolerance * abs(cut_alpha)
phi_tol = phase_shift_tolerance / delta_z
f_cut_resolution = min(k_tol, phi_tol) / abs(max_beta2) / (4 * pi ** 2 * (1 + dn) * max_slot_width)
f_pump_resolution = min(k_tol, phi_tol) / abs(max_beta2) / (4 * pi ** 2 * max_slot_width)
psi[cut_index, cut_index] = NliSolver._generalized_psi(cut_frequency, cut_frequency, cut_baud_rate,
cut_roll_off, cut_frequency, cut_baud_rate,
cut_roll_off, f_cut_resolution, f_pump_resolution,
srs, cut_alpha, cut_beta2, 0, cut_frequency)
psi = psi[cut_indices, :]
cut_baud_rate = outer(baud_rate[cut_indices], ones(nch))
pump_baud_rate = outer(ones(cut_indices.size), baud_rate)
eta_cut_central_frequency = \
gamma ** 2 * weight * psi / (cut_baud_rate * pump_baud_rate ** 2)
eta = cut_baud_rate * eta_cut_central_frequency # Local white noise
return eta
@staticmethod
def _approx_psi(df, frequency, baud_rate, beta2, loss_profile, z):
"""Computes the approximated psi function similarly to the one used in the GN model.
The method uses eq. 25 of https://ieeexplore.ieee.org/document/9741324"""
pump_baud_rate = outer(ones(frequency.size), baud_rate)
cut_beta = outer(beta2, ones(frequency.size))
pump_beta = outer(ones(frequency.size), beta2)
delta_z = abs(z[:-1] - z[1:])
loss_lin = log(loss_profile)
pump_alpha = (loss_lin[:, 1:] - loss_lin[:, :-1]) / delta_z
leff = abs((loss_profile[:, 1:] - loss_profile[:, :-1]) / sqrt(abs(pump_alpha))) * pump_alpha / abs(pump_alpha)
leff = reshape(outer(leff, ones(z.size - 1)), newshape=[leff.shape[0], leff.shape[1], leff.shape[1]])
leff2 = leff * swapaxes(leff, 2, 1)
leff2 = sum(leff2, axis=(1, 2))
z_int = outer(ones(frequency.size), leff2)
delta_beta = (cut_beta + pump_beta) / 2
psi = z_int * pump_baud_rate / (4 * pi * abs(delta_beta * df))
return psi
def estimate_nf_model(type_variety, gain_min, gain_max, nf_min, nf_max):
if nf_min < -10:

View File

@@ -10,6 +10,7 @@ from pathlib import Path
from pandas import read_csv
from numpy.testing import assert_allclose
from numpy import array
from copy import deepcopy
import pytest
from gnpy.core.info import create_input_spectral_information, create_arbitrary_spectral_information
@@ -132,3 +133,26 @@ def test_fiber_lumped_losses_srs(set_sim_params):
stimulated_raman_scattering = RamanSolver.calculate_attenuation_profile(spectral_info_input, fiber)
power_profile = stimulated_raman_scattering.power_profile
assert_allclose(power_profile, expected_power_profile, rtol=1e-3)
@pytest.mark.usefixtures('set_sim_params')
def test_nli_solver():
"""Test the accuracy of NLI solver."""
fiber = Fiber(**load_json(TEST_DIR / 'data' / 'test_science_utils_fiber_config.json'))
fiber.ref_pch_in_dbm = 0.0
# 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, spacing=50e9, tx_osnr=40.0,
tx_power=1e-3)
SimParams.set_params(load_json(TEST_DIR / 'data' / 'sim_params.json'))
sim_params = SimParams()
spectral_info_output = fiber(deepcopy(spectral_info_input))
sim_params.nli_params.method = 'ggn_approx'
sim_params.raman_params.result_spatial_resolution = 10e3
spectral_info_output_approx = fiber(deepcopy(spectral_info_input))
assert_allclose(spectral_info_output.nli, spectral_info_output_approx.nli, rtol=1e-1)