.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/optical/plot_01_chromatic_dispersion.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_optical_plot_01_chromatic_dispersion.py: Chromatic dispersion and its compensation ========================================= Standard single-mode fibre spreads a 32 GBd signal over many symbols: :math:`D = 17` ps/(nm·km) accumulate 1360 ps/nm over 80 km, and the received 16-QAM constellation collapses into a cloud. Dispersion is an all-pass filter, :math:`e^{j \beta_2 \omega^2 L / 2}`, so the receiver undoes it exactly in the frequency domain (:func:`comnum.cdc`), or with an FIR whose length grows with the accumulated dispersion (:func:`comnum.cd_fir_taps`). .. GENERATED FROM PYTHON SOURCE LINES 12-59 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np import comnum from comnum.plot import BLUE, CRIMSON, GREY, LABEL, TEAL RS, SPS = 32e9, 2 FS = RS * SPS fiber = comnum.FiberParams(n_spans=1, span_km=80.0) c = comnum.Constellation("qam", 16).normalized() rng = comnum.Rng(4) _, x = comnum.symbols(c, 8192, rng) h = comnum.pulse_shape("rrc", SPS, n_taps=2 * 128 + 1, rolloff=0.1) tx = comnum.fir_filter(h, comnum.upsample(x, SPS)) # the span loss is compensated by a noisy amplifier: an OSNR-limited link at 20 dB SNR rx = comnum.awgn( comnum.linear_fiber_channel(tx, fiber, 80.0, FS), 20.0, rng, fs=FS, bandwidth_hz=RS ) def symbols(r): return comnum.pnorm(comnum.downsample(comnum.fir_filter(h, r), SPS))[200:-200] cases = { "back to back": comnum.awgn(tx, 20.0, rng, fs=FS, bandwidth_hz=RS), "80 km, no compensation": rx, "80 km, compensated": comnum.cdc(rx, fiber, 80.0, FS), } fig, axes = plt.subplots(1, 3, figsize=(9.5, 3.6), sharex=True, sharey=True) for ax, (name, r) in zip(axes, cases.items(), strict=True): z = symbols(r) ber, _, snr = comnum.fast_ber_calc(z, x[200:-200], c) ax.scatter(z.real, z.imag, s=2, color=BLUE, alpha=0.35, linewidth=0) ax.set(title=name, aspect="equal", xlim=(-1.8, 1.8), ylim=(-1.8, 1.8)) ax.text( 0.03, 0.03, f"SNR {snr:.1f} dB\nBER {ber:.1e}", transform=ax.transAxes, fontsize=8, color=LABEL, va="bottom", ) fig.tight_layout() .. image-sg:: /auto_examples/optical/images/sphx_glr_plot_01_chromatic_dispersion_001.png :alt: back to back, 80 km, no compensation, 80 km, compensated :srcset: /auto_examples/optical/images/sphx_glr_plot_01_chromatic_dispersion_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 60-62 The FIR alternative: OptiCommPy's length rule, and its accuracy against the exact compensation. .. GENERATED FROM PYTHON SOURCE LINES 62-67 .. code-block:: Python n = comnum.cd_taps_needed(fiber, 80.0, RS, FS) taps = comnum.cd_fir_taps(fiber, 80.0, FS, max(n, 1) | 1) z = symbols(comnum.convolve(taps, rx)[len(taps) // 2 : len(taps) // 2 + rx.size]) print(f"{n} taps for 80 km: SNR {comnum.fast_ber_calc(z, x[200:-200], c)[2]:.1f} dB") .. rst-class:: sphx-glr-script-out .. code-block:: none 48 taps for 80 km: SNR 19.9 dB .. GENERATED FROM PYTHON SOURCE LINES 68-90 Optimal FIR filters in the least-squares sense ---------------------------------------------- Savory's filter (:func:`comnum.cd_savory_taps`; S. J. Savory, *Opt. Express* 16(2), 2008) truncates the impulse response of the analog compensation to :math:`N = 2\lfloor 2\pi K \rfloor + 1` taps, with :math:`K = -\beta_2 L f_s^2 / 2` (:func:`comnum.cd_dispersion_factor`). Eghbali, Johansson, Gustafsson and Savory (*J. Lightw. Technol.* 32(8), 2014) design the taps that minimise the energy of the error :math:`E = \frac{1}{2\pi}\int_{-\Omega}^{\Omega} |H(e^{j\omega}) - e^{jK\omega^2}|^2 d\omega` (:func:`comnum.cd_ls_taps`, :func:`comnum.cd_ls_error`): over the whole band (:math:`\Omega = \pi`) the taps are closed-form, and over the band of the signal (:math:`\Omega = \pi(1 + \rho)/L` for a roll-off :math:`\rho` at :math:`L` samples per symbol) far fewer taps reach the same error. The example below is example 1 of the paper: 4000 km of fibre with :math:`D = 17` ps/(nm·km), 10.7 GBd at two samples per symbol, roll-off 0.25, so :math:`K \approx 19.9` and :math:`N = 251`. The error of the least-squares filter falls with its length; Savory's error is smallest at its own length and grows beyond (Sec. III-A2 of the paper); frequency sampling (:func:`comnum.cd_fir_taps`) fits the response only at :math:`N` frequencies. The closed form of :math:`E` loses its precision near :math:`10^{-16}`: the curves are clipped there. .. GENERATED FROM PYTHON SOURCE LINES 90-129 .. code-block:: Python FS1 = 21.4e9 SPAN = 4000.0 fib = comnum.FiberParams(d_ps_nm_km=17.0, fc_hz=3e8 / 1553e-9) band = FS1 * 1.25 / 4 print( f"K = {comnum.cd_dispersion_factor(fib, SPAN, FS1):.2f}, N = {len(comnum.cd_savory_taps(fib, SPAN, FS1))}" ) def err_db(taps, b=FS1 / 2): return comnum.lin2db(max(comnum.cd_ls_error(taps, fib, SPAN, FS1, band_hz=b), 1e-16)) lengths = np.arange(101, 352, 10) curves = { "Savory, whole band": [ err_db(comnum.cd_savory_taps(fib, SPAN, FS1, n_taps=n)) for n in lengths ], "frequency sampling, whole band": [ err_db(comnum.cd_fir_taps(fib, SPAN, FS1, n)) for n in lengths ], "least squares, whole band": [err_db(comnum.cd_ls_taps(fib, SPAN, FS1, n)) for n in lengths], "least squares, band of the signal": [ err_db(comnum.cd_ls_taps(fib, SPAN, FS1, n, band_hz=band, epsilon=1e-14), band) for n in lengths ], } fig, ax = plt.subplots(figsize=(6.4, 3.8)) for (name, e), color, marker in zip( curves.items(), [CRIMSON, GREY, BLUE, TEAL], "sdo^", strict=True ): ax.plot(lengths, e, marker=marker, color=color, markersize=4, label=name) ax.axvline(251, color=GREY, linewidth=0.8, linestyle=":") ax.set(xlabel="number of taps", ylabel="error energy E (dB)", ylim=(-165, 5)) ax.grid(alpha=0.3) ax.legend(fontsize=8) fig.tight_layout() .. image-sg:: /auto_examples/optical/images/sphx_glr_plot_01_chromatic_dispersion_002.png :alt: plot 01 chromatic dispersion :srcset: /auto_examples/optical/images/sphx_glr_plot_01_chromatic_dispersion_002.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none K = 19.91, N = 251 .. GENERATED FROM PYTHON SOURCE LINES 130-136 The BER of 16-QAM through the chain of Fig. 2 of the paper: ideal root-raised-cosine filters at both ends, the dispersion, white noise, the FIR, at :math:`E_b/N_0 = 10` to 15 dB. The least-squares filter of 251 taps nearly reaches the back-to-back curve, Savory's filter of the same length loses about 0.9 dB at :math:`10^{-5}`, and the band-limited filter matches the 251 taps of Savory with 119 (Figs. 5 and 9 of the paper; ``validation/tests/fiber/eghbali2014.rs`` checks these points). .. GENERATED FROM PYTHON SOURCE LINES 136-180 .. code-block:: Python NSYM = 1 << 19 g = comnum.rrc_spectrum(NSYM, 2, 0.25) * np.sqrt(2) w = 2 * np.pi * np.fft.fftfreq(2 * NSYM) k1 = comnum.cd_dispersion_factor(fib, SPAN, FS1) c16 = comnum.Constellation("qam", 16).normalized() def ber_16qam(taps, ebn0_db, rng): _, a = comnum.symbols(c16, NSYM, rng) u = np.zeros(2 * NSYM, complex) u[::2] = a k = 0.0 if taps is None else k1 y = np.fft.ifft(np.fft.fft(u) * g * np.exp(-1j * k * w**2)) y = y + rng.complex_noise(y.size, 1 / (4 * comnum.db2lin(ebn0_db))) yf = np.fft.fft(y) * g if taps is not None: h = np.zeros(y.size, complex) h[(np.arange(taps.size) - taps.size // 2) % y.size] = taps yf = yf * np.fft.fft(h) return comnum.fast_ber_calc(np.fft.ifft(yf)[::2], a, c16)[0] ebn0 = np.arange(10.0, 15.5, 1.0) filters = { "back to back": None, "Savory, 251 taps": comnum.cd_savory_taps(fib, SPAN, FS1), "least squares, 251 taps": comnum.cd_ls_taps(fib, SPAN, FS1, 251), "least squares, band of the signal, 119 taps": comnum.cd_ls_taps( fib, SPAN, FS1, 119, band_hz=band, epsilon=1e-14 ), } rng = comnum.Rng(14) fig, ax = plt.subplots(figsize=(6.4, 3.8)) for (name, taps), color, marker in zip( filters.items(), [LABEL, CRIMSON, BLUE, TEAL], "xsod", strict=True ): b = [ber_16qam(taps, e, rng) for e in ebn0] ax.semilogy(ebn0, np.maximum(b, 1e-7), marker=marker, color=color, markersize=4, label=name) ax.set(xlabel="$E_b/N_0$ (dB)", ylabel="BER", ylim=(1e-6, 1e-1)) ax.grid(alpha=0.3, which="both") ax.legend(fontsize=8) fig.tight_layout() .. image-sg:: /auto_examples/optical/images/sphx_glr_plot_01_chromatic_dispersion_003.png :alt: plot 01 chromatic dispersion :srcset: /auto_examples/optical/images/sphx_glr_plot_01_chromatic_dispersion_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 181-187 The price of the band-limited design: the response outside the band of the signal is not constrained and exceeds :math:`10^{5}` (100 dB), so the receive filter must reject that band; the paper assumes ideal pulse-shaping filters. With a truncated root-raised-cosine filter of finite stopband, the amplified out-of-band noise ruins the link; a larger penalty :math:`\epsilon` lowers this gain at the cost of a larger error in the band. .. GENERATED FROM PYTHON SOURCE LINES 187-199 .. code-block:: Python f = np.linspace(-0.5, 0.5, 2001) fig, ax = plt.subplots(figsize=(6.4, 3.4)) for (name, taps), color in zip(list(filters.items())[1:], [CRIMSON, BLUE, TEAL], strict=True): resp = np.exp(-2j * np.pi * np.outer(f, np.arange(taps.size) - taps.size // 2)) @ taps ax.plot(f, 20 * np.log10(np.abs(resp)), color=color, label=name) for s in (-1, 1): ax.axvline(s * band / FS1, color=GREY, linewidth=0.8, linestyle=":") ax.set(xlabel="frequency / $f_s$", ylabel="$|H|$ (dB)") ax.grid(alpha=0.3) ax.legend(fontsize=8) fig.tight_layout() .. image-sg:: /auto_examples/optical/images/sphx_glr_plot_01_chromatic_dispersion_004.png :alt: plot 01 chromatic dispersion :srcset: /auto_examples/optical/images/sphx_glr_plot_01_chromatic_dispersion_004.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 3.876 seconds) .. _sphx_glr_download_auto_examples_optical_plot_01_chromatic_dispersion.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_01_chromatic_dispersion.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_01_chromatic_dispersion.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_01_chromatic_dispersion.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_