.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/optical/plot_06_mach_zehnder.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_06_mach_zehnder.py: Mach–Zehnder and IQ modulators ============================== A Mach–Zehnder modulator (MZM) splits the optical field into two arms, shifts their phases with the driving voltage and recombines them. Driven in push-pull, the two arms move in opposite directions and the output field is real (no chirp): .. math:: E_{out} = E_{in} \left[c_I \cos\theta + j\, c_Q \sin\theta\right], \qquad \theta = \frac{\pi (u + V_b)}{2 V_\pi}, with :math:`V_\pi` the half-wave voltage, :math:`V_b` the bias and :math:`c_I = \sqrt{r/(r+1)}`, :math:`c_Q = 1/\sqrt{r+1}` the arm coefficients of an extinction ratio :math:`r` (:math:`c_I = 1, c_Q = 0` for an ideal modulator). Two bias points matter: - the **quadrature point** :math:`V_b = -V_\pi/2`, where the *power* responds linearly to small drives (intensity modulation); - the **null** :math:`V_b = -V_\pi`, where the *field* is :math:`\sin(\pi u / 2V_\pi)`, odd in the drive: the bias of coherent modulation (BPSK, the two arms of an IQ modulator). `comnum.mzm`, `comnum.iqm` and `comnum.phase_modulator` follow the device models of OptiCommPy (``tests/oracle/test_oracle.py`` compares them on the same inputs); ``tests/test_performance.py`` checks every closed form of this page. .. GENERATED FROM PYTHON SOURCE LINES 29-46 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np import comnum from comnum.plot import BACKGROUND, GREY, MUTED VPI = 2.0 def bessel_j(k, x): """J_k(x) = (1/π) ∫_0^π cos(kτ - x sin τ) dτ (the trapezoidal rule is exact to rounding for this periodic integrand).""" tau = np.linspace(0.0, np.pi, 4001) return np.trapezoid(np.cos(k * tau - x * np.sin(tau)), tau) / np.pi .. GENERATED FROM PYTHON SOURCE LINES 47-53 Transfer functions and bias points ---------------------------------- The power transmission :math:`\cos^2\theta` and, biased at the null, the field :math:`\sin(\pi u/2V_\pi)` against the drive. A finite extinction ratio keeps a floor of power :math:`1/(r+1)` at the null. .. GENERATED FROM PYTHON SOURCE LINES 53-70 .. code-block:: Python u = np.linspace(-2 * VPI, 2 * VPI, 801) fig, ax = plt.subplots() for er_db, style in [(np.inf, "-"), (20.0, "--")]: p = np.abs(comnum.mzm(np.ones(u.size), u, vpi=VPI, vb=0.0, er_db=er_db)) ** 2 label = "ideal" if np.isinf(er_db) else f"ER = {er_db:.0f} dB" ax.plot(u / VPI, p, style, label=f"power transmission, {label}") field = comnum.mzm(np.ones(u.size), u, vpi=VPI, vb=-VPI, er_db=np.inf).real ax.plot(u / VPI, field, label="field, biased at the null") for x, name in [(-0.5, "quadrature"), (-1.0, "null")]: ax.axvline(x, color=GREY, linestyle=":", linewidth=1.0) ax.annotate(name, (x, 1.03), ha="center", fontsize=8, color=MUTED) ax.set(xlabel=r"$(u + V_b)/V_\pi$ (power curves: $V_b = 0$)", ylabel="transmission", ylim=(-1.75, 1.15), title="Push-pull MZM") # fmt: skip ax.legend(fontsize=8, loc="lower center", ncols=2) fig.tight_layout() .. image-sg:: /auto_examples/optical/images/sphx_glr_plot_06_mach_zehnder_001.png :alt: Push-pull MZM :srcset: /auto_examples/optical/images/sphx_glr_plot_06_mach_zehnder_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 71-76 Extinction ratio ---------------- Sweeping the bias, the ratio of the largest to the smallest output power is the extinction ratio :math:`r` of the arm coefficients. .. GENERATED FROM PYTHON SOURCE LINES 76-98 .. code-block:: Python biases = np.linspace(-2 * VPI, 0.0, 2001) fig, ax = plt.subplots() print("configured ER (dB) measured ER (dB)") for er_db in (15.0, 25.0, 35.0): p = np.array( [ abs(comnum.mzm(np.ones(1), np.zeros(1), vpi=VPI, vb=b, er_db=er_db)[0]) ** 2 for b in biases ] ) ax.plot(biases / VPI, comnum.lin2db(p), label=f"ER = {er_db:.0f} dB") print(f"{er_db:20.1f} {comnum.lin2db(p.max() / p.min()):16.6f}") ax.set( xlabel=r"bias $V_b/V_\pi$", ylabel="output power (dB)", ylim=(-40, 2), title="Power against the bias", ) ax.legend() fig.tight_layout() .. image-sg:: /auto_examples/optical/images/sphx_glr_plot_06_mach_zehnder_002.png :alt: Power against the bias :srcset: /auto_examples/optical/images/sphx_glr_plot_06_mach_zehnder_002.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none configured ER (dB) measured ER (dB) 15.0 15.000000 25.0 25.000000 35.0 35.000000 .. GENERATED FROM PYTHON SOURCE LINES 99-108 Intensity modulation: harmonics of a sine drive ----------------------------------------------- Biased at the quadrature point and driven by :math:`V_m \sin x`, the power is :math:`\frac{1}{2}(1 + \sin(m \sin x))` with the modulation depth :math:`m = \pi V_m/V_\pi`. The Jacobi–Anger expansion :math:`\sin(m \sin x) = 2 \sum_{k\ \mathrm{odd}} J_k(m) \sin kx` (Abramowitz & Stegun, Sec. 9.1) gives the amplitude :math:`J_k(m)` of every odd harmonic and none of the even ones: the third harmonic grows as :math:`m^3`, the distortion that limits analogue links. .. GENERATED FROM PYTHON SOURCE LINES 108-133 .. code-block:: Python n, cycles = 4096, 8 x = 2 * np.pi * cycles * np.arange(n) / n depths = np.linspace(0.05, 3.0, 60) fig, ax = plt.subplots() for k, marker in zip([1, 3, 5], ["o", "s", "D"], strict=True): (line,) = ax.semilogy(depths, [abs(bessel_j(k, m)) for m in depths], label=f"$|J_{k}(m)|$") measured = [] for m in depths[::6]: p = ( np.abs( comnum.mzm( np.ones(n), m * VPI / np.pi * np.sin(x), vpi=VPI, vb=-VPI / 2, er_db=np.inf ) ) ** 2 ) measured.append(abs(np.fft.rfft(p)[k * cycles]) * 2 / n) ax.semilogy(depths[::6], measured, marker, color=line.get_color(), markeredgecolor=BACKGROUND, label=f"harmonic {k}, comnum.mzm") # fmt: skip ax.set(xlabel=r"modulation depth $m = \pi V_m / V_\pi$", ylabel="harmonic amplitude", ylim=(1e-6, 1), title="MZM at quadrature: odd harmonics of a sine drive") # fmt: skip ax.legend(ncols=2, fontsize=8) fig.tight_layout() .. image-sg:: /auto_examples/optical/images/sphx_glr_plot_06_mach_zehnder_003.png :alt: MZM at quadrature: odd harmonics of a sine drive :srcset: /auto_examples/optical/images/sphx_glr_plot_06_mach_zehnder_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 134-143 IQ modulator: compression of 16-QAM and arcsine predistortion ------------------------------------------------------------- Two MZMs biased at the null, on the two halves of the field, recombined with a 90° phase: :math:`E_{out} = \frac{E_{in}}{\sqrt{2}} [\sin(\pi u_I/2V_\pi) + j \sin(\pi u_Q/2V_\pi)]`. A linear drive of peak swing :math:`V_{pk}` compresses the outer levels of 16-QAM; a larger swing gives more power (less modulation loss) but more distortion. Driving with :math:`u = \frac{2V_\pi}{\pi} \arcsin(s)` makes the field proportional to the target :math:`s` exactly. .. GENERATED FROM PYTHON SOURCE LINES 143-165 .. code-block:: Python levels = np.array([-3.0, -1.0, 1.0, 3.0]) / 3 ui, uq = np.meshgrid(levels, levels) s = (ui + 1j * uq).ravel() ideal = dict(vpi=VPI, vb_i=-VPI, vb_q=-VPI, vphi=VPI / 2, er_i_db=np.inf, er_q_db=np.inf) fig, axes = plt.subplots(1, 3, figsize=(9.0, 3.4), sharex=True, sharey=True) for ax, swing in zip(axes, [0.3, 0.6, 0.95], strict=True): lin = comnum.iqm(np.ones(s.size), swing * VPI * s, **ideal) target = np.sin(np.pi * swing / 2) * s # the same peak, without compression pre = comnum.iqm( np.ones(s.size), 2 * VPI / np.pi * (np.arcsin(target.real) + 1j * np.arcsin(target.imag)), **ideal, ) ax.plot(pre.real, pre.imag, "o", markerfacecolor="none", markersize=8, label="arcsine drive") ax.plot(lin.real, lin.imag, ".", markersize=8, label="linear drive") ax.set(title=f"$V_{{pk}} = {swing:g}\\, V_\\pi$", aspect="equal") axes[0].legend(fontsize=8, loc="lower left") fig.suptitle("16-QAM out of an IQ modulator", fontsize=11) fig.tight_layout() .. image-sg:: /auto_examples/optical/images/sphx_glr_plot_06_mach_zehnder_004.png :alt: 16-QAM out of an IQ modulator, $V_{pk} = 0.3\, V_\pi$, $V_{pk} = 0.6\, V_\pi$, $V_{pk} = 0.95\, V_\pi$ :srcset: /auto_examples/optical/images/sphx_glr_plot_06_mach_zehnder_004.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 166-169 The trade-off, against the closed forms: the mean output power over the 16 points (in dB of the input) and the error of the linear drive with respect to an uncompressed constellation of the same mean power. .. GENERATED FROM PYTHON SOURCE LINES 169-198 .. code-block:: Python swings = np.linspace(0.05, 1.0, 40) loss_theory, evm_theory, loss_sim, evm_sim = [], [], [], [] for swing in swings: a = np.sin(np.pi * swing * levels / 2) loss_theory.append(comnum.lin2db(np.mean(a**2))) # (1/2) * 2 E[sin²] per point scaled = levels * np.sqrt(np.mean(a**2) / np.mean(levels**2)) evm_theory.append(100 * np.sqrt(np.mean((a - scaled) ** 2) / np.mean(a**2))) for swing in swings[::4]: e = comnum.iqm(np.ones(s.size), swing * VPI * s, **ideal) loss_sim.append(comnum.lin2db(np.mean(np.abs(e) ** 2))) ref = s * np.sqrt(np.mean(np.abs(e) ** 2) / np.mean(np.abs(s) ** 2)) evm_sim.append(100 * np.sqrt(np.mean(np.abs(e - ref) ** 2) / np.mean(np.abs(e) ** 2))) fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9.0, 3.6)) ax1.plot(swings, loss_theory, label="closed form") ax1.plot(swings[::4], loss_sim, "o", markeredgecolor=BACKGROUND, label="comnum.iqm") ax1.set( xlabel=r"peak swing $V_{pk}/V_\pi$", ylabel="mean output power (dB)", title="Modulation loss" ) ax1.legend() ax2.plot(swings, evm_theory, label="closed form") ax2.plot(swings[::4], evm_sim, "o", markeredgecolor=BACKGROUND, label="comnum.iqm") ax2.set( xlabel=r"peak swing $V_{pk}/V_\pi$", ylabel="EVM of the compression (%)", title="Linear drive" ) ax2.legend() fig.tight_layout() .. image-sg:: /auto_examples/optical/images/sphx_glr_plot_06_mach_zehnder_005.png :alt: Modulation loss, Linear drive :srcset: /auto_examples/optical/images/sphx_glr_plot_06_mach_zehnder_005.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 199-208 Limits of the model ------------------- The modulators are memoryless and chirp-free: the electrode bandwidth (a low-pass filter on the drive), the chirp of a single-drive MZM and the nonlinearity of the driver are not modelled. The first is a filter in a chain (``comnum.blocks``); the chirp of an unbalanced drive is the subject of Koyama & Iga, "Frequency chirping in external modulators" (*J. Lightw. Technol.*, 1988), the reference a chirped model would be checked against. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.491 seconds) .. _sphx_glr_download_auto_examples_optical_plot_06_mach_zehnder.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_06_mach_zehnder.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_06_mach_zehnder.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_06_mach_zehnder.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_