Devices#
- class Laser(P_dBm: float = -10, *, linewidth: float = 0, theta0: float | None = None, seed: int | None = None, fs: float = 1000000000.0, freq_offset: float = 0, name: str = 'Laser')[source]#
Continuous-wave laser envelope generator with Wiener phase noise.
Signal Model#
The generated complex envelope is
\[E[n] = \sqrt{P_s} \, e^{j \left(2 \pi \Delta f \, n T_s + \theta_0 + \theta[n]\right)}, \qquad \theta[n] = \sum_{k=0}^{n} \delta\theta_k, \qquad \delta\theta_k \sim \mathcal{N}\left(0, \tfrac{2 \pi \Delta\nu}{f_s}\right)\]where \(P_s = 10^{(P_{dBm} - 30)/10}\) is the emitted power in W, \(T_s = 1/f_s\) the sampling period, \(\Delta f\) the carrier frequency offset and \(\theta[n]\) a Wiener phase noise of linewidth \(\Delta\nu\).
Axes: generator – consumes a sample count
nb_samplesand produces a 1D complex envelope (N,).- param P_dBm:
Laser power \(P_{dBm}\) in dBm. Default is -10.
- type P_dBm:
float
- param linewidth:
Laser linewidth \(\Delta\nu\) in Hz. Default is 0 (no phase noise).
- type linewidth:
float, keyword-only
- param theta0:
Initial phase \(\theta_0\) in rad. When None, drawn uniformly on \([-\pi, \pi)\) from the local generator.
- type theta0:
float, optional, keyword-only
- param seed:
Local RNG seed (for \(\theta_0\) and the phase noise).
- type seed:
int, optional, keyword-only
- param fs:
Sampling frequency \(f_s\) in Hz. Default is 1e9.
- type fs:
float, keyword-only
- param freq_offset:
Carrier frequency offset \(\Delta f\) in Hz, relative to the nominal laser wavelength. Default is 0.
- type freq_offset:
float, keyword-only
- param name:
Name of the laser instance. Default is
"Laser".- 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
>>> laser = Laser(0, linewidth=0, theta0=0.0, fs=1e9) >>> E = laser(3) >>> print(np.round(np.abs(E) ** 2, 6)) [0.001 0.001 0.001]
- class Optical90HybridCircuit(is_ideal: bool = True, *, sensitivity: float = 0.6, laser_in: Callable[[...], ndarray] | None = None, name: str = 'Optical90HybridCircuit')[source]#
Optical 90-degree hybrid front-end for coherent detection.
Signal Model#
In the ideal mode, the optic-electro conversion is transparent:
\[y[n] = x[n]\]In the non-ideal mode, the incoming field is mixed with the local oscillator laser \(E_{lo}[n]\) and scaled by the receiver sensitivity \(\rho\):
\[y[n] = \rho \, x[n] \, E_{lo}^*[n]\]where \(E_{lo}[n]\) is generated by
laser_in(unit amplitude when no laser is provided).Axes: element-wise – applied pointwise; one local-oscillator draw per array element, whatever the shape.
- param is_ideal:
If True, ideal conversion (identity). If False, mixing with the local oscillator and sensitivity scaling. Default is True.
- type is_ideal:
bool
- param sensitivity:
Sensitivity factor \(\rho\) of the circuit (non-ideal mode). Default is 0.6.
- type sensitivity:
float, keyword-only
- param laser_in:
Local oscillator laser generating \(E_{lo}[n]\). Default is None (unit-amplitude local oscillator).
- type laser_in:
Laser, optional, keyword-only
- param name:
Name of the device instance. Default is
"Optical90HybridCircuit".- type name:
str, optional, keyword-only
References
K. Kikuchi, “Fundamentals of coherent optical fiber communications,” Journal of Lightwave Technology, vol. 34, no. 1, pp. 157-179, 2016.
Examples
>>> x = np.array([1 + 1j, -1 + 0.5j]) >>> y = Optical90HybridCircuit()(x) >>> bool(np.array_equal(y, x)) True
- class PowerControl(P_moy: float = 1, *, unit: Literal['natural', 'dBm'] = 'natural', name: str = 'power_control')[source]#
Rescales a signal to a target power level.
Signal Model#
\[\begin{split}y[n] = g \, x[n], \qquad g = \begin{cases} P_{target} / \sqrt{P_x} & \text{(natural)} \\[4pt] \sqrt{10^{(P_{target} - 30)/10}} / \sqrt{P_x} & \text{(dBm)} \end{cases}\end{split}\]where \(P_x = \mathbb{E}\left[|x[n]|^2\right]\) is the measured input power and \(P_{target}\) the target level. In
"dBm"mode the output power is \(10^{(P_{target} - 30)/10}\) W; in"natural"mode the output RMS amplitude is \(P_{target}\).Axes: element-wise – pointwise scaling; the gain \(g\) is estimated from the whole array.
- param P_moy:
Target level \(P_{target}\); interpreted according to
unit. Default is 1.- type P_moy:
float
- param unit:
Unit of
P_moy. Default is"natural".- type unit:
{“natural”, “dBm”}, keyword-only
- param name:
Name of the device instance. Default is
"power_control".- type name:
str, optional, keyword-only
Examples
>>> x = np.array([2.0, -2.0, 2.0, -2.0]) >>> y = PowerControl(1)(x) >>> print(y) [ 1. -1. 1. -1.]
- class ErbiumDopedFiberAmplifier(gain: float, N_ase: float, *, name: str = 'ErbiumDopedFiberAmplifier', seed: int | None = None)[source]#
Erbium-doped fiber amplifier (EDFA): flat gain plus additive ASE noise.
Signal Model#
\[y[n] = \sqrt{G} \, x[n] + b[n], \qquad b[n] \sim \mathcal{CN}\left(0, N_{\mathrm{ase}}\right)\]where \(\sqrt{G}\) is the amplitude gain of the amplifier and \(N_{\mathrm{ase}}\) the variance of the circular amplified spontaneous emission (ASE) noise, per state of polarization.
Axes: element-wise – pointwise gain plus additive noise; one independent complex ASE draw per array element, whatever the shape – a polarization pair
(..., 2, N)gets independent noise per polarization.- param gain:
Amplitude gain \(\sqrt{G}\) (linear).
- type gain:
float
- param N_ase:
ASE noise variance \(N_{\mathrm{ase}}\) (in W), per state of polarization.
- type N_ase:
float
- param seed:
Local RNG seed.
- type seed:
int, optional, keyword-only
- param name:
Name of the amplifier instance. Default is
"ErbiumDopedFiberAmplifier".- type name:
str, optional, keyword-only
References
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.
https://www.sciencedirect.com/topics/engineering/spontaneous-emission-factor
Examples
>>> x = np.ones(3, dtype=complex) >>> y = ErbiumDopedFiberAmplifier(2.0, 0.0)(x) >>> print(y) [2.+0.j 2.+0.j 2.+0.j]
- class MachZehnderModulator(is_ideal: bool = False, *, Vpi: float = 6, k: float = 1, gI: float = 1, Phi: float = 1.5707963267948966, laser_in: Callable[[...], ndarray] | None = None, name: str = 'MachZehnderModulator')[source]#
IQ Mach-Zehnder modulator (MZM).
Signal Model#
In the ideal mode, the modulation is linear:
\[y[n] = -\frac{\pi V_{pp}}{4 V_\pi} \, E_{laser}[n] \, x[n]\]In the non-ideal mode, each branch applies its cosine transfer function to the drive voltages:
\[y[n] = \left[ \cos\left(\frac{\pi \, u_I[n]}{2 V_\pi}\right) + e^{j \Phi} \cos\left(\frac{\pi \, u_Q[n]}{2 V_\pi}\right) \right] E_{laser}[n]\]\[u_I[n] = V_\pi + \frac{g_I}{2} \frac{V_{pp}}{m} \, \Re(x[n]), \qquad u_Q[n] = V_\pi + \frac{g_Q}{2} \frac{V_{pp}}{m} \, \Im(x[n])\]where \(V_\pi\) is the half-wave voltage, \(V_{pp} = k V_\pi\) the peak-to-peak drive voltage, \(g_I\) and \(g_Q = 2 - g_I\) the branch gains, \(\Phi\) the I/Q phase offset, \(m = \max(\max|\Re(x)|, \max|\Im(x)|)\) a normalization coefficient and \(E_{laser}[n]\) the input laser field (unit amplitude when no laser is provided).
Axes: element-wise – applied pointwise; the normalization \(m\) is estimated from the whole array (batch rows pooled) and one laser draw is made per array element.
- param is_ideal:
If True, ideal linear modulation without imbalance. Default is False.
- type is_ideal:
bool
- param Vpi:
Half-wave voltage \(V_\pi\) in V. Default is 6.
- type Vpi:
float, keyword-only
- param k:
Peak-to-peak drive ratio \(k = V_{pp} / V_\pi\). Default is 1.
- type k:
float, keyword-only
- param gI:
Gain \(g_I\) of the I branch (the Q branch gets \(g_Q = 2 - g_I\)). Default is 1.
- type gI:
float, keyword-only
- param Phi:
Phase offset \(\Phi\) between the I and Q branches in rad. Default is
np.pi / 2.- type Phi:
float, keyword-only
- param laser_in:
Input laser field generator producing \(E_{laser}[n]\). Default is None (unit-amplitude field).
- type laser_in:
callable, optional, keyword-only
- param name:
Name of the modulator instance. Default is
"MachZehnderModulator".- type name:
str, optional, keyword-only
References
C. Peucheret, “Generation and Detection of Optical Modulation Formats,” Technical University of Denmark, 2012.
Examples
>>> mzm = MachZehnderModulator(is_ideal=True, Vpi=6.0, k=1.0) >>> y = mzm(np.array([1.0 + 0j])) >>> print(round(float(np.real(y[0])), 4)) -0.7854