Skip to main content
Ctrl+K

Comnumpy

  • Getting Started
  • Tutorials
  • Documentation
  • GitHub
  • Getting Started
  • Tutorials
  • Documentation
  • GitHub

Section Navigation

Contents:

  • Core
    • Generics
    • Generators
    • Mappers
    • Processors
    • Devices
    • Filters
    • Channels
    • Fading channels
    • Impairments
    • Compensators
    • Frames
    • Synchronization sequences
    • Capacity
    • Achievable information rates
    • Shaping
    • Metrics
    • Utilities
    • Validators
    • Visualizers
  • FEC
    • Convolutional codes
    • LDPC codes
    • Code analysis
  • MIMO
    • Channels
    • Space-Time Coding
    • Detectors
    • Compensators
    • Utilities
    • Validators
  • OFDM
    • Carrier allocation
    • Processors
    • Compensators
    • Metrics
    • Predistorders
    • Chains
    • Utilities
    • Visualizers
  • Optical
    • Fiber
    • Channels
    • Compensators
    • Digital Back Propagation
    • Devices
    • Fiber Links
    • GN model
    • Wavelength-division multiplexing
    • Raman amplification
    • Utils
    • Physical constants
  • Chain services
    • Exceptions
    • Serialization
    • Parameter sweeps
    • Data display
    • Plot style
  • Documentation
  • Optical
  • Fiber Links

Fiber Links#

class FiberLink(N_spans: int = 1, *, L_span: float = 80, StPS: int = 1, fs: float = 1, fiber: ~comnumpy.optical.fiber.FiberSpec = <factory>, NF_dB: float = 4, noise_scaling: float = 1, step_type: ~typing.Literal['linear', 'logarithmic'] = 'linear', step_method: ~typing.Literal['symmetric', 'asymetric'] = 'symmetric', use_only_linear: bool = False, step_log_factor: float = 0.4, name: str = 'fiber link', callbacks: ~typing.Dict[str, ~typing.Callable[[...], None]] = <factory>, raman: ~comnumpy.optical.raman.RamanSolution | None = None, seed: int | None = None)[source]#

Multi-span fiber link simulated with the split-step Fourier method (SSFM).

Signal Model#

Each span integrates the nonlinear Schroedinger equation (NLSE) for the complex field envelope \(A(z, t)\):

\[\frac{\partial A(z, t)}{\partial z} = -\frac{\alpha}{2} A(z, t) - j \frac{\beta_2}{2} \frac{\partial^2 A(z, t)}{\partial t^2} + j \gamma |A(z, t)|^2 A(z, t)\]

where \(\alpha = \frac{\ln 10}{10} \alpha_{dB}\) is the attenuation (Np/km), \(\beta_2 = -10^3 D \lambda^2 / (2 \pi c)\) the group velocity dispersion (ps^2/km) and \(\gamma\) the Kerr coefficient (rad/W/km). The NLSE is integrated numerically with StPS split steps per span (symmetric scheme: \(D(\delta z/2)\,N(\delta z)\,D(\delta z/2)\); asymmetric scheme: \(N(\delta z)\,D(\delta z)\)). After each span, an EDFA compensates the loss with power gain \(G\) and adds circular ASE noise of variance \(\sigma^2_{\mathrm{ase}}\):

\[G = 10^{\alpha_{dB} L_{\mathrm{span}} / 10}, \qquad \sigma^2_{\mathrm{ase}} = \kappa \, f_s \, (G - 1) \, h \nu \, n_{sp}, \qquad n_{sp} = \frac{\mathrm{NF} / 2}{1 - 1/G}\]

with \(\mathrm{NF} = 10^{\mathrm{NF_{dB}} / 10}\) the amplifier noise figure and \(\kappa\) a noise scaling factor.

Two polarizations (Manakov). A field shaped (..., 2, N) – the antenna/channel axis of D2 – is propagated with the Manakov equation instead:

\[\frac{\partial E_{x,y}}{\partial z} = -\frac{\alpha}{2} E_{x,y} - j \frac{\beta_2}{2} \frac{\partial^2 E_{x,y}}{\partial t^2} + j \frac{8\gamma}{9} \left(|E_x|^2 + |E_y|^2\right) E_{x,y}\]

The two polarizations share the total intensity, and the \(8/9\) comes from averaging the nonlinearity over the fast random birefringence of a real fibre. It belongs to the model, not to the fibre: fiber.gamma stays the fibre’s Kerr coefficient in both modes, and which equation is integrated is read off the shape of the field, exactly as which pumps are on is read off their powers (D41). A (..., 1, N) field is a single polarization and keeps the scalar NLSE with \(\gamma\).

Axes: declared axis – a full-field signal (N,) or (..., P, N) with P in {1, 2} polarizations. The leading axes are a batch: (B, P, N) propagates B realizations in one call, which is 1.8x faster than the same B calls at 12288 samples and 3.1x at 1024 – the split-step loop, the per-span amplifier and the parameter precomputation are paid once instead of B times. A batch of single-polarization fields is (B, 1, N); a bare (B, N) reads B as polarizations – refused for \(B \ge 3\), while (2, N) is read as one polarization pair by convention.

param N_spans:

Number of spans in the fiber link. Default is 1.

type N_spans:

int

param L_span:

Span length \(L_{\mathrm{span}}\) in km. Default is 80.

type L_span:

float, keyword-only

param StPS:

Number of split steps \(\delta z\) per span. Default is 1.

type StPS:

int, keyword-only

param fs:

Sampling frequency \(f_s\) in Hz. Default is 1.

type fs:

float, keyword-only

param NF_dB:

EDFA noise figure \(\mathrm{NF_{dB}}\) in dB. Default is 4.

type NF_dB:

float, keyword-only

param noise_scaling:

ASE noise scaling factor \(\kappa\) (0 disables the ASE noise). Default is 1.

type noise_scaling:

float, keyword-only

param step_type:

Step-size distribution within a span. Default is "linear".

type step_type:

{“linear”, “logarithmic”}, keyword-only

param step_method:

Split-step scheme. Default is "symmetric".

type step_method:

{“symmetric”, “asymetric”}, keyword-only

param use_only_linear:

If True, only the linear effects (CD and attenuation) are simulated, in a single exact step per span. Default is False.

type use_only_linear:

bool, keyword-only

param fiber:

The physical fibre (D46): loss \(\alpha_{dB}\), Kerr coefficient \(\gamma\), dispersion \(D\) and the wavelength the carrier frequency is derived from. Default is a standard single-mode fibre, FiberSpec().

type fiber:

FiberSpec, keyword-only

param step_log_factor:

Adjustment factor of the logarithmic step-size distribution. Default is 0.4.

type step_log_factor:

float, keyword-only

param name:

Name of the link instance. Default is "fiber link".

type name:

str, optional, keyword-only

param callbacks:

Hooks called during propagation; the key "post_span" is called after each span as callback(y, num_span=...).

type callbacks:

dict of str to callable, optional, keyword-only

param raman:

Distributed Raman gain profile, from solve_raman() over this span length (decision D45). Its on-off gain \(G_\mathrm{on\text{-}off}(z)\) is applied step by step inside the split-step loop, so the Kerr term sees the power the fibre actually carries – which is the whole reason a profile is used rather than a lumped gain. The EDFA is reduced by the same amount, so a span stays transparent whether or not it is pumped, and the ASE the solver integrated is added once per span, scaled from its reference bandwidth to fs.

type raman:

RamanSolution, optional, keyword-only

param seed:

Seed of the local generator, which feeds both the Raman ASE and the per-span EDFA seed. Overridden by chain seeding (Sequential.seed, D6).

type seed:

int, optional, keyword-only

raman_step_gain_#

Amplitude gain applied at each split-step, read off the Raman profile at the step boundaries. Derived from the inputs, hence the trailing underscore (D23).

Type:

np.ndarray

raman_sigma2_#

Variance of the Raman ASE added at the end of each span, in the simulated bandwidth \(f_s\).

Type:

float

raises ShapeError:

If the input is neither a full-field 1D signal (N,) nor a polarization pair (..., P, N) with P in {1, 2} (validated in prepare()): a pointwise Kerr step on a multi-channel array would silently produce SPM only (no XPM, no FWM).

References

  • G. P. Agrawal, Nonlinear Fiber Optics, 5th ed., Academic Press, 2013, Sections 3.2 (CD) and 4.1 (SPM).

  • J. Shao, X. Liang and S. Kumar, “Comparison of Split-Step Fourier Schemes for Simulating Fiber Optic Communication Systems,” IEEE Photonics Journal, vol. 6, no. 4, pp. 1-15, Aug. 2014, Art no. 7200515, doi: 10.1109/JPHOT.2014.2340993.

  • O. V. Sinkin, R. Holzlohner, J. Zweck and C. R. Menyuk, “Optimization of the split-step Fourier method in modeling optical-fiber communications systems,” Journal of Lightwave Technology, vol. 21, no. 1, pp. 61-68, Jan. 2003.

  • R.-J. Essiambre, G. Kramer, P. J. Winzer, G. J. Foschini and B. Goebel, “Capacity limits of optical fiber networks,” Journal of Lightwave Technology, vol. 28, no. 4, pp. 662-701, 2010.

Examples

>>> rng = np.random.default_rng(0)
>>> x = 1e-3 * (rng.normal(size=128) + 1j * rng.normal(size=128))
>>> link = FiberLink(2, L_span=80.0, StPS=4, fs=10e9, noise_scaling=0)
>>> y = link(x)
>>> print(y.shape, y.dtype)
(128,) complex128
budget(bandwidth_Hz: float, *, polarizations: int = 1) → Dict[str, Any][source]#

The amplifier noise this link accumulates, in closed form.

Signal Model#

Each of the \(N_s\) spans adds one amplifier’s worth of spontaneous emission, and the noise a receiver sees is that density integrated over the bandwidth it keeps – the symbol rate, for a matched filter:

\[P_{\mathrm{ASE}} = n_{\mathrm{pol}} \, N_s \, N_{\mathrm{ASE}} \, B, \qquad N_{\mathrm{ASE}} = (G - 1) h \nu \, n_{sp}, \qquad n_{sp} = \frac{\mathrm{NF}/2}{1 - 1/G}\]

with \(n_{\mathrm{pol}}\) the number of polarizations (\(P\) denotes power everywhere else in this module). That factor is the whole reason this method exists. \(N_{\mathrm{ASE}}\) is a density per polarization, and a dual-polarization link carries two independent copies of it; quoting a channel power against a single-polarization budget is a 3 dB error that looks exactly like a modelling disagreement. Writing the product by hand at each call site is how the two conventions drift apart.

The result is the link’s own prediction: it owes nothing to a simulation, so it is the reference a measured effective SNR is checked against. noise_scaling is applied, so a link with its noise switched off budgets zero.

param bandwidth_Hz:

Bandwidth the receiver collects, in Hz. For a matched filter this is the symbol rate, not the simulated fs.

type bandwidth_Hz:

float

param polarizations:

1 (default) for a single-polarization link, 2 for the dual-polarization link a coherent system uses – the same convention as gn_model_nli_power().

type polarizations:

int, optional, keyword-only

returns:

dict – ase_power_W and ase_power_dBm, the accumulated noise; ase_density_W_per_Hz, one span’s density per polarization; and the inputs it was built from (n_spans, NF_dB, span_gain_dB, bandwidth_Hz, polarizations).

raises ValueError:

If the bandwidth is not positive, or if polarizations is neither 1 nor 2: a fibre carries one or two.

Examples

Ten 100 km spans with a 5 dB noise figure, one polarization, read in a 32 GBd matched filter:

>>> link = FiberLink(10, L_span=100.0, NF_dB=5.0, fs=192e9)
>>> budget = link.budget(32e9)
>>> print(f"{budget['ase_power_dBm']:.2f} dBm")
-21.88 dBm

The second polarization carries its own noise, and nothing else changes:

>>> print(f"{link.budget(32e9, polarizations=2)['ase_power_dBm']:.2f} dBm")
-18.87 dBm

previous

Devices

next

GN model

On this page
  • FiberLink
    • FiberLink.raman_step_gain_
    • FiberLink.raman_sigma2_
    • FiberLink.budget()
Show Source

© Copyright 2025, V. Choqueuse.

Created using Sphinx 9.1.0.

Built with the PyData Sphinx Theme 0.20.0.