Compensators#

class ChromaticDispersionFIRCompensator(z: float, *, fs: float = 1.0, name: str = 'fir cd compensator')[source]#

Static FIR compensator of chromatic dispersion (truncated inverse impulse response, Savory).

Signal Model#

A fiber span of length \(z\) acts on the complex envelope as the all-pass dispersive filter

\[H_{\mathrm{CD}}(\omega) = e^{j \frac{\beta_2}{2} z \omega^2}\]

where \(\omega\) is the angular frequency in rad/s and \(\beta_2\) the group velocity dispersion. The compensator approximates its inverse. Sampling at \(f_s\) and using the normalized pulsation \(\Omega = \omega / f_s \in [-\pi, \pi]\) (rad/sample), the target response is the quadratic-phase all-pass

\[H_c\!\left(e^{j\Omega}\right) = e^{-j \frac{\beta_2}{2} z f_s^2 \Omega^2} = e^{j K \Omega^2}, \qquad K = -\frac{\beta_2 z f_s^2}{2}\]

with the dimensionless dispersion \(K > 0\). Its inverse DTFT is the Fresnel (chirped) sequence

\[h[n] = \sqrt{\frac{j}{4 \pi K}} \, e^{-j \frac{n^2}{4 K}}\]

truncated to the span imposed by the maximum group delay \(2 \pi K\) of the all-pass, that is \(|n| \le \lfloor 2 \pi K \rfloor\), hence \(N = 2 \lfloor 2 \pi K \rfloor + 1\) taps. The block applies the full linear convolution

\[y[n] = \sum_{l} h[l] \, x[n - l]\]

so the output is \(N - 1\) samples longer than the input and carries a group delay of \((N - 1) / 2\) samples.

Units follow the module convention: \(\beta_2 = -10^3 D \lambda^2 / (2 \pi c)\) is returned in ps^2/km by comnumpy.optical.utils.compute_beta2() and converted internally to s^2/km (factor \(10^{-24}\)), \(z\) is in km and \(f_s\) in Hz, which makes \(K\) dimensionless.

Axes: axis -1 – the convolution runs along the last axis (scipy.signal.convolve in "full" mode); leading axes are batch, every row through the same taps.

param z:

Fiber length \(z\) in km.

type z:

float

param fs:

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

type fs:

float, optional, keyword-only

param name:

Name of the processor instance. Default is "fir cd compensator".

type name:

str, optional, keyword-only

h#

Filter taps \(h[n]\) for \(|n| \le \lfloor 2 \pi K \rfloor\), built in __post_init__.

Type:

np.ndarray

K#

Dimensionless dispersion \(K = -\beta_2 z f_s^2 / 2\).

Type:

float

lamb#

Class-level wavelength \(\lambda\) in nm (WAVELENGTH).

Type:

float

D#

Class-level dispersion coefficient \(D\) in ps/nm/km (CD_COEFFICIENT).

Type:

float

c#

Class-level speed of light \(c\) in m/s (SPEED_OF_LIGHT).

Type:

float

References

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

Examples

Dispersion spreads an isolated pulse; the compensator recompresses it.

>>> from comnumpy.optical.channels import ChromaticDispersion
>>> x = np.zeros(512, dtype=complex)
>>> x[256] = 1.0
>>> y = ChromaticDispersion(1000.0, fs=20e9)(x)
>>> comp = ChromaticDispersionFIRCompensator(1000.0, fs=20e9)
>>> delay = (len(comp.h) - 1) // 2
>>> x_hat = comp(y)[delay:delay + 512]
>>> print(len(comp.h), round(float(np.abs(y).max()), 3), round(float(np.abs(x_hat[256])), 3))
55 0.162 0.975
class ChromaticDispersionLSFIRCompensator(z: float, N: int, *, fs: float = 1.0, w_vect: List[float] = <factory>, name: str = 'optimal')[source]#

Least-squares FIR compensator of chromatic dispersion (optimal design over a frequency band).

Signal Model#

The target response is the same quadratic-phase all-pass as in ChromaticDispersionFIRCompensator, i.e. the inverse of the dispersive response \(e^{j (\beta_2 / 2) z \omega^2}\) of the fiber, written with the normalized pulsation \(\Omega = \omega / f_s \in [-\pi, \pi]\):

\[H_c\!\left(e^{j\Omega}\right) = e^{j K \Omega^2}, \qquad K = -\frac{\beta_2 z f_s^2}{2}\]

Rather than truncating the inverse DTFT of \(H_c\), the \(N\) taps \(h[n]\), \(|n| \le (N-1)/2\), minimize the integrated squared error over the design band \([\Omega_1, \Omega_2]\):

\[\min_{h} \int_{\Omega_1}^{\Omega_2} \left| \sum_{l} h[l] e^{-j l \Omega} - e^{j K \Omega^2} \right|^2 d\Omega\]

The normal equations of this quadratic problem read \(Q h = d\), with the Hermitian Toeplitz Gram matrix and the cross-correlation vector

\[Q_{m,l} = q(l - m), \quad q(p) = \frac{1}{2\pi} \int_{\Omega_1}^{\Omega_2} e^{-j p \Omega} d\Omega, \qquad d[n] = \frac{1}{2\pi} \int_{\Omega_1}^{\Omega_2} e^{j K \Omega^2} e^{j n \Omega} d\Omega\]

Only \(q(p)\) for \(p \ge 0\) is evaluated, the negative lags following from the Hermitian symmetry \(q(-p) = q(p)^*\) that makes \(Q = Q^H\). Completing the square in the integrand of \(d[n]\) and mapping \(j K \Omega^2\) onto \(-t^2\) gives the closed form on an arbitrary band, in terms of the complex error function:

\[d[n] = \frac{e^{j \pi / 4}}{4 \sqrt{\pi K}} e^{-j \frac{n^2}{4 K}} \left[ \mathrm{erf}\!\left(\gamma \left(\Omega_2 + \frac{n}{2K}\right)\right) - \mathrm{erf}\!\left(\gamma \left(\Omega_1 + \frac{n}{2K}\right)\right) \right], \qquad \gamma = \sqrt{K} \, e^{-j \pi / 4}\]

(the band enters only through the two integration bounds; at \([\Omega_1, \Omega_2] = [-\pi, \pi]\) this collapses, using the oddness of \(\mathrm{erf}\), onto the usual full-band expression \(\mathrm{erf}(\kappa (2\pi K - n)) + \mathrm{erf}(\kappa (2\pi K + n))\) with \(\kappa = e^{j 3 \pi / 4} / (2 \sqrt{K})\)).

The ridge-regularized system \((Q + \epsilon I) h = d\) is then solved rather than inverted. This matters away from the full band: \(Q\) is a prolate matrix whose eigenvalues collapse to zero beyond the \(\approx N (\Omega_2 - \Omega_1) / (2\pi)\) degrees of freedom the band can constrain, so the explicit inverse carries entries of order \(1/\epsilon\) that cancel catastrophically against \(d\). The taps in that numerical null space are not individually determined – they do not affect the response inside the band, which is the quantity the design pins down.

On the full band \([-\pi, \pi]\) the degeneracy disappears: \(q(p) = \mathrm{sinc}(p) = 0\) for \(p \neq 0\) makes \(Q = I\) exactly and \(h = d\). For taps well inside the group-delay span, \(|n| \ll 2 \pi K\), both erf arguments saturate and \(d[n] \to \sqrt{j / (4 \pi K)} e^{-j n^2 / (4K)}\): there the design falls back on the truncated (Savory) filter. The two keep differing near \(|n| \approx 2 \pi K\), where the Fresnel integral is only half-completed and the least-squares design tapers the taps that the truncation keeps at full amplitude. The block then applies the full linear convolution

\[y[n] = \sum_{l} h[l] \, x[n - l]\]

whose output is \(N - 1\) samples longer than the input, with a group delay of \((N - 1) / 2\) samples. Units are those of the module: \(\beta_2\) in ps^2/km from comnumpy.optical.utils.compute_beta2(), converted internally to s^2/km, \(z\) in km and \(f_s\) in Hz, so that \(K\) is dimensionless.

Axes: axis -1 – the convolution runs along the last axis (scipy.signal.convolve in "full" mode); leading axes are batch, every row through the same taps.

param z:

Fiber length \(z\) in km.

type z:

float

param N:

Filter length \(N\) in taps; must be odd so that the support \(|n| \le (N-1)/2\) is symmetric.

type N:

int

param fs:

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

type fs:

float, optional, keyword-only

param w_vect:

Design band \([\Omega_1, \Omega_2]\) in rad/sample, with \(\Omega_1 < \Omega_2\). Default is [-np.pi, np.pi] (full band). A narrower band trades accuracy outside the band for accuracy inside it, which is what one wants when the signal is oversampled.

type w_vect:

List[float], optional, keyword-only

param name:

Name of the processor instance. Default is "optimal".

type name:

str, optional, keyword-only

h#

Least-squares taps \(h[n]\), solution of \(Q h = d\).

Type:

np.ndarray

K#

Dimensionless dispersion \(K = -\beta_2 z f_s^2 / 2\).

Type:

float

epsilon#

Class-level ridge term \(\epsilon\) added to the diagonal of \(Q\) before solving. Default is 1e-14.

Type:

float

lamb#

Class-level wavelength \(\lambda\) in nm (WAVELENGTH).

Type:

float

D#

Class-level dispersion coefficient \(D\) in ps/nm/km (CD_COEFFICIENT).

Type:

float

c#

Class-level speed of light \(c\) in m/s (SPEED_OF_LIGHT).

Type:

float

raises ValueError:

If the filter length N is even, or if the design band w_vect is not a strictly increasing pair.

References

  • A. Eghbali, H. Johansson, O. Gustafsson, S. J. Savory, “Optimal least-squares FIR digital filters for compensation of chromatic dispersion in digital coherent optical receivers,” Journal of Lightwave Technology, vol. 32, no. 8, pp. 1449-1456, 2014.

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

Examples

Same round trip as the truncated design, at equal filter length.

>>> from comnumpy.optical.channels import ChromaticDispersion
>>> x = np.zeros(512, dtype=complex)
>>> x[256] = 1.0
>>> y = ChromaticDispersion(1000.0, fs=20e9)(x)
>>> comp = ChromaticDispersionLSFIRCompensator(1000.0, 55, fs=20e9)
>>> delay = (comp.N - 1) // 2
>>> x_hat = comp(y)[delay:delay + 512]
>>> print(round(float(np.abs(x_hat[256])), 3))
0.977