.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_tutorials/plot_02_classic_chain.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_tutorials_plot_02_classic_chain.py: A classical single-carrier chain ================================ QPSK at several samples per symbol: root-raised-cosine pulse shaping, additive noise, the matched filter and decimation to one sample per symbol — the textbook chain, as a :class:`comnum.Chain` of native blocks. The chain runs once to look at the signals after every block (the eye diagram, the spectrum, the constellation), then as a campaign against the closed form; variants with laser phase noise and blind phase search are one line apart. .. GENERATED FROM PYTHON SOURCE LINES 12-19 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np import comnum from comnum import blocks as B .. GENERATED FROM PYTHON SOURCE LINES 20-24 **The chain.** The blocks are the classes of :mod:`comnum.blocks`, one per entry of :func:`comnum.spec_blocks`; ``help(B.PulseShape)`` lists the parameters of a block with their defaults and units. ``Awgn`` sets the SNR per symbol: its noise bandwidth is the symbol rate by default, whatever the samples per symbol. .. GENERATED FROM PYTHON SOURCE LINES 24-34 .. code-block:: Python SPS, ROLLOFF = 8, 0.35 link = comnum.Chain( tx=B.Source(constellation="QPSK", n_symbols=4096), shaping=B.PulseShape(kind="rrc", sps=SPS, rolloff=ROLLOFF, n_taps=129), channel=B.Awgn(snr_db=comnum.var("snr_db")), mf=B.MatchedFilter(kind="rrc", rolloff=ROLLOFF, n_taps=129), down=B.Decimate(), ) print(link) .. rst-class:: sphx-glr-script-out .. code-block:: none Chain( tx=Source(constellation='QPSK'), shaping=PulseShape(rolloff=0.35, n_taps=129, sps=8), channel=Awgn(snr_db=var('snr_db')), mf=MatchedFilter(rolloff=0.35, n_taps=129), down=Decimate() ) .. GENERATED FROM PYTHON SOURCE LINES 35-38 **One run, traced.** ``trace`` runs the chain once and keeps a copy of the frame after every block. A frame carries the samples, the samples per symbol and what the metrics need (the transmitted bits and symbols, the constellation). .. GENERATED FROM PYTHON SOURCE LINES 38-42 .. code-block:: Python frames = link.trace({"snr_db": 15.0}, comnum.Rng(1)) for name, f in frames.items(): print(f"{name:>8}: {f.samples.size} samples, {f.sps:g} per symbol") .. rst-class:: sphx-glr-script-out .. code-block:: none tx: 4096 samples, 1 per symbol shaping: 32768 samples, 8 per symbol channel: 32768 samples, 8 per symbol mf: 32768 samples, 8 per symbol down: 4096 samples, 1 per symbol .. GENERATED FROM PYTHON SOURCE LINES 43-46 The eye diagram after shaping is open at the sampling instants only after the matched filter (a root-raised cosine alone is not a Nyquist pulse); the spectrum has the roll-off of the pulse; the samples before and after decimation. .. GENERATED FROM PYTHON SOURCE LINES 46-57 .. code-block:: Python fig, axes = plt.subplots(2, 2, figsize=(8.5, 7)) comnum.plot.eye(frames["shaping"], ax=axes[0, 0], traces=200) axes[0, 0].set_title("eye after the transmit filter") comnum.plot.eye(frames["mf"], ax=axes[0, 1], traces=200) axes[0, 1].set_title("eye after the matched filter, 15 dB") comnum.plot.psd(frames["channel"], ax=axes[1, 0]) axes[1, 0].set(title="spectrum on the channel", xlim=(-SPS / 2, SPS / 2)) comnum.plot.constellation(frames["down"], ax=axes[1, 1]) axes[1, 1].set_title("after decimation") fig.tight_layout() .. image-sg:: /auto_tutorials/images/sphx_glr_plot_02_classic_chain_001.png :alt: eye after the transmit filter, eye after the matched filter, 15 dB, spectrum on the channel, after decimation :srcset: /auto_tutorials/images/sphx_glr_plot_02_classic_chain_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 58-61 **The campaign.** ``skip_start`` and ``skip_end`` leave the transients of the filters out of the metrics. The SNR per symbol of ``Awgn`` is :math:`E_s/N_0`, so the closed form of QPSK is that of 4-QAM at :math:`E_b/N_0 = E_s/N_0 - 3\,\mathrm{dB}`. .. GENERATED FROM PYTHON SOURCE LINES 61-80 .. code-block:: Python campaign = { "grid": {"snr_db": list(np.arange(0.0, 13.0, 1.0))}, "metrics": {"report": ["ber"], "skip_start": 64, "skip_end": 64}, "stop": {"min_errors": {"ber": 400}, "max_trials": 1000}, "seed": 1, } results = link.run(**campaign) def qpsk(snr_db): return comnum.theory_ber(4, np.asarray(snr_db) - comnum.lin2db(2), "qam") fig, ax = plt.subplots() results.plot("snr_db", "ber", ax=ax, theory=qpsk) ax.set(xlabel="SNR per symbol (dB)", ylabel="bit error rate", ylim=(1e-6, 0.5), title="QPSK, RRC pulses, matched filter; dotted: closed form") # fmt: skip fig.tight_layout() .. image-sg:: /auto_tutorials/images/sphx_glr_plot_02_classic_chain_002.png :alt: QPSK, RRC pulses, matched filter; dotted: closed form :srcset: /auto_tutorials/images/sphx_glr_plot_02_classic_chain_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 81-90 **Variants.** ``insert_after`` and ``replace`` return modified copies of the chain. A laser of linewidth :math:`\Delta\nu` adds a Wiener phase noise of variance :math:`2\pi\Delta\nu T` per symbol (:math:`T` is the symbol period; the time axis of the source is normalised, so ``linewidth_hz`` is :math:`\Delta\nu T`). Blind phase search (Pfau, Hoffmann & Noé 2009) estimates the phase from the decisions over a sliding window of :math:`2n + 1` symbols, and ``FrameSync`` resolves the four-fold ambiguity of QPSK on the known symbols. Without recovery the phase walks away within a few hundred symbols; with it the loss is a fraction of a dB. :func:`comnum.compare` runs the variants with the same grid and gathers them. .. GENERATED FROM PYTHON SOURCE LINES 90-105 .. code-block:: Python noisy = link.insert_after("channel", laser=B.PhaseNoise(linewidth_hz=1e-4)) recovered = noisy.insert_after("down", cpr=B.Bps(n=32, b=32), sync=B.FrameSync()) variants = { "ideal": link, "ΔνT = 1e-4, no recovery": noisy, "ΔνT = 1e-4, blind phase search": recovered, } res = comnum.compare(variants, **campaign) fig, ax = plt.subplots() res.plot("snr_db", "ber", by="variant", ax=ax, theory=lambda s, _: qpsk(s)) ax.set(xlabel="SNR per symbol (dB)", ylabel="bit error rate", ylim=(1e-6, 0.5), title="phase noise and carrier recovery; dotted: closed form") # fmt: skip fig.tight_layout() .. image-sg:: /auto_tutorials/images/sphx_glr_plot_02_classic_chain_003.png :alt: phase noise and carrier recovery; dotted: closed form :srcset: /auto_tutorials/images/sphx_glr_plot_02_classic_chain_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 106-110 **Probes.** ``probe`` measures metrics at a point of the chain during the whole campaign, reported as ``.``: here the power of the shaped signal and the SNR estimated from the decisions after carrier recovery, which the phase noise lowers by a fraction of a dB. .. GENERATED FROM PYTHON SOURCE LINES 110-120 .. code-block:: Python probed = recovered.probes( {"shaping": ["power_dbm"], "sync": ["snr_db"]}, skip_start=64, skip_end=64 ) res = probed.run(grid={"snr_db": [6.0, 10.0, 14.0]}, metrics=["ber"], stop={"max_trials": 16}) for r in res: m = r["metrics"] print( f"SNR {r['condition']['snr_db']:>4} dB: {m['shaping.power_dbm']['value']:.1f} dBm " f"after shaping, {m['sync.snr_db']['value']:.1f} dB estimated after carrier recovery" ) .. rst-class:: sphx-glr-script-out .. code-block:: none SNR 6.0 dB: 30.0 dBm after shaping, 6.7 dB estimated after carrier recovery SNR 10.0 dB: 30.0 dBm after shaping, 10.2 dB estimated after carrier recovery SNR 14.0 dB: 30.0 dBm after shaping, 13.8 dB estimated after carrier recovery .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 4.836 seconds) .. _sphx_glr_download_auto_tutorials_plot_02_classic_chain.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_02_classic_chain.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_02_classic_chain.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_02_classic_chain.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_