Channels#

class PhaseNoise(sigma2: float, *, per: Literal['pair', 'row', 'signal'] = 'pair', seed: int | None = None, name: str = 'phase noise')[source]#

Wiener (random-walk) phase noise channel.

Signal Model#

\[y[n] = x[n] \, e^{j \phi[n]}, \qquad \phi[n] = \sum_{k=0}^{n} \delta\phi_k, \qquad \delta\phi_k \sim \mathcal{N}\left(0, \sigma^2\right)\]

where \(\sigma^2\) is the variance of the independent phase increments \(\delta\phi_k\) (a Wiener process accumulated along the samples). The magnitude \(|y[n]| = |x[n]|\) is preserved.

Axes: axis -1 – the phase random walk accumulates along the samples. What shares one walk is declared by per, which names the event – the slice of the array that sees one laser:

  • "pair" (default): one walk per trailing (2, N) block – the two rows of a polarization pair come from one laser, and every leading axis is batch: independent draws per trial. A 2-D or deeper input whose axis -2 is not 2 is refused, because it cannot be read both ways: use "row" for independent rows or "signal" for one shared walk.

  • "row": one independent walk per 1-D row.

  • "signal": one walk broadcast over the whole array.

A 1-D input sees one walk under every setting.

param sigma2:

Variance \(\sigma^2\) of the per-sample phase increments, in rad^2.

type sigma2:

float

param per:

The event that shares one laser. Default is "pair".

type per:

{“pair”, “row”, “signal”}, optional, keyword-only

param seed:

Local RNG seed.

type seed:

int, optional, keyword-only

param name:

Name of the channel instance. Default is "phase noise".

type name:

str, optional, keyword-only

References

T. Pfau, S. Hoffmann, R. Noe, “Hardware-Efficient Coherent Digital Receiver Concept With Feedforward Carrier Recovery for M-QAM Constellations,” Journal of Lightwave Technology, vol. 27, no. 8, pp. 989-999, 2009.

Examples

>>> x = np.ones(3, dtype=complex)
>>> y = PhaseNoise(0.01, seed=0)(x)
>>> print(np.round(np.abs(y), 6))
[1. 1. 1.]
noise_rvs(X: ndarray) → ndarray[source]#

Draw one Wiener walk per event declared by per.

class ChromaticDispersion(z: float, *, fs: float = 1, alpha_dB: float = 0, direction: int = 1, name: str = 'cd', lamb: float = 1550, D: float = 17, c: float = 299792458)[source]#

Chromatic dispersion (CD) of a fiber span, applied exactly in the frequency domain.

Signal Model#

The block solves the linear part of the nonlinear Schroedinger equation (NLSE) for the complex field envelope \(A(z, t)\):

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

whose exact solution over a fiber of length \(z\) is applied in the frequency domain:

\[\hat{A}(z, \omega) = \hat{A}(0, \omega) \, e^{j \frac{\beta_2}{2} z \omega^2} \, e^{-\frac{\alpha}{2} z}\]

with the group velocity dispersion \(\beta_2\) (ps^2/km) derived from the dispersion coefficient \(D\), the wavelength \(\lambda\) and the speed of light \(c\), and the attenuation \(\alpha = \frac{\ln 10}{10} \, \alpha_{dB}\) (Np/km):

\[\beta_2 = -\frac{10^3 \, D \lambda^2}{2 \pi c}\]

With direction=-1 the phase shift and the attenuation are both inverted (backward propagation, used for CD compensation).

Axes: axis -1 – the transfer function is applied along the last axis; leading axes are batch, every row through the same fibre. \(\beta_2\) is given in ps^2/km and converted internally to s^2/km (factor \(10^{-24}\)), so the exponent is dimensionless.

param z:

Fiber length \(z\) in km.

type z:

float

param fs:

Sampling frequency in Hz; sets the discrete pulsation grid \(\omega\). Default is 1.

type fs:

float, keyword-only

param alpha_dB:

Attenuation \(\alpha_{dB}\) in dB/km. Default is 0 (lossless).

type alpha_dB:

float, keyword-only

param direction:

Propagation direction, 1 for forward and -1 for backward. Default is 1.

type direction:

int, keyword-only

param lamb:

Wavelength \(\lambda\) in nm. Default is WAVELENGTH.

type lamb:

float, keyword-only

param D:

Dispersion coefficient \(D\) in ps/nm/km. Default is CD_COEFFICIENT.

type D:

float, keyword-only

param c:

Speed of light \(c\) in m/s. Default is SPEED_OF_LIGHT.

type c:

float, keyword-only

param name:

Identifier for the dispersion instance. Default is "cd".

type name:

str, optional, keyword-only

References

  • G. P. Agrawal, Nonlinear Fiber Optics, 5th ed., Academic Press, 2013, Section 3.2.

  • S. J. Savory, “Digital filters for coherent optical receivers,” Optics Express, vol. 16, no. 2, pp. 804-817, 2008.

  • A. Shahkarami, “Complexity reduction over bi-RNN-based Kerr nonlinearity equalization in dual-polarization fiber-optic communications via a CRNN-based approach,” Dissertation, Institut polytechnique de Paris, 2022. URL: https://www.theses.fr/2022IPPAT034.

Examples

>>> rng = np.random.default_rng(0)
>>> x = rng.normal(size=64) + 1j * rng.normal(size=64)
>>> cd = ChromaticDispersion(80.0, fs=10e9)
>>> cd_back = ChromaticDispersion(80.0, fs=10e9, direction=-1)
>>> bool(np.allclose(cd_back(cd(x)), x))
True
class KerrNonLinearity(z: float, *, direction: int = 1, name: str = 'nl', gamma: float = 1.3, gain: float = 1)[source]#

Kerr nonlinearity (self-phase modulation) of a fiber segment.

Signal Model#

The block solves the nonlinear part of the nonlinear Schroedinger equation (NLSE) for the complex field envelope \(A(z, t)\):

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

whose exact solution over a segment of length \(z\) is the intensity-dependent phase rotation

\[A(z, t) = g \, A(0, t) \, e^{j \gamma z |A(0, t)|^2}\]

where \(\gamma\) is the Kerr coefficient (rad/W/km) and \(g\) an optional amplitude gain. With direction=-1 the phase rotation is conjugated (backward propagation, used in DBP).

Axes: element-wise – the rotation is pointwise, leading axes are batch, and the shape is not validated. The signal must be the full field: a pointwise Kerr step applied to separate channels would model SPM only (no XPM, no FWM).

param z:

Segment length \(z\) in km.

type z:

float

param direction:

Propagation direction, 1 for forward and -1 for backward. Default is 1.

type direction:

int, keyword-only

param gamma:

Kerr coefficient \(\gamma\) in rad/W/km. Default is KERR_COEFFICIENT.

type gamma:

float, keyword-only

param gain:

Amplitude gain \(g\) (linear). Default is 1.

type gain:

float, keyword-only

param name:

Identifier for the nonlinearity instance. Default is "nl".

type name:

str, optional, keyword-only

References

  • G. P. Agrawal, Nonlinear Fiber Optics, 5th ed., Academic Press, 2013, Section 4.1.

  • C. Häger and H. D. Pfister, “Physics-based deep learning for fiber-optic communication systems,” IEEE Journal on Selected Areas in Communications, vol. 39, no. 1, pp. 280-294, 2021.

Examples

>>> x = np.array([1.0 + 0j])
>>> y = KerrNonLinearity(10, gamma=1.3)(x)
>>> print(round(float(np.angle(y[0])), 4))
0.4336
class PMDEmulator(dgd: float, *, n_sections: int = 8, fs: float = 1.0, seed: int | None = None, name: str = 'pmd')[source]#

Polarization rotation and first-order PMD, as a section emulator.

Signal Model#

The fibre’s random birefringence is emulated by \(K\) sections, each a random unitary Jones rotation followed by a differential group delay (DGD) of \(\tau / \sqrt{K}\) between the two principal states, applied exactly in the frequency domain:

\[\mathbf{Y}(\omega) = \prod_{k=1}^{K} \mathbf{D}_k(\omega) \, \mathbf{R}_k \; \mathbf{X}(\omega), \qquad \mathbf{D}_k(\omega) = \mathrm{diag}\!\left( e^{+j \omega \tau / 2\sqrt{K}}, \; e^{-j \omega \tau / 2\sqrt{K}} \right)\]

Each \(\mathbf{R}_k\) is drawn Haar-uniform from the seeded local generator, so the emulator is unitary – it conserves energy exactly – and reproducible. With n_sections=1 and dgd=0 it reduces to one random rotation of the state of polarization, which is the part every polarization demultiplexer must undo even before PMD enters.

Randomly oriented sections add their DGDs in quadrature, hence the \(1/\sqrt{K}\) per section: the declared dgd is the RMS DGD of the ensemble. Over section draws the DGD is Maxwellian, with mean \(\sqrt{8/3\pi}\,\tau \approx 0.921\,\tau\) (Poole & Wagner). With n_sections=1 the DGD is deterministic and equals \(\tau\) exactly. The concatenation of sections also makes the DGD frequency-dependent, which is what distinguishes PMD from a wavelength-flat rotation.

Axes: polarization pair – expects (..., 2, N); the Jones matrices act on the polarization axis, the delays on the last one.

param dgd:

RMS differential group delay \(\tau\) in seconds.

type dgd:

float

param n_sections:

Number of concatenated sections \(K\). Default is 8.

type n_sections:

int, optional, keyword-only

param fs:

Sampling frequency in Hz. Default is 1.0.

type fs:

float, optional, keyword-only

param seed:

Local RNG seed for the section rotations. Same seed, same fibre.

type seed:

int, optional, keyword-only

param name:

Block name. Default "pmd".

type name:

str, optional, keyword-only

raises ShapeError:

If the input does not carry a polarization pair (..., 2, N).

References

C. D. Poole and R. E. Wagner, “Phenomenological approach to polarisation dispersion in long single-mode fibres,” Electronics Letters, vol. 22, no. 19, pp. 1029-1030, 1986. S. J. Savory, “Digital filters for coherent optical receivers,” Optics Express, vol. 16, no. 2, pp. 804-817, 2008.

Examples

>>> x = np.zeros((2, 8), dtype=complex); x[0] = 1.0
>>> y = PMDEmulator(0.0, n_sections=1, seed=1)(x)
>>> bool(np.abs(np.sum(np.abs(y) ** 2) - 8.0) < 1e-9)   # unitary
True
>>> bool(np.abs(y[1, 0]) > 0)      # the rotation mixes the two rows
True