.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "examples/devices/example_006_phase_shifter.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_examples_devices_example_006_phase_shifter.py: Phase Shifter and Mach-Zehnder Interferometer Example ===================================================== This example first demonstrates the use of the :class:`~symop.devices.models.phase_shifters.phase_shifter.PhaseShifter` model on a single path. It then shows how the same phase shifter becomes physically relevant inside a simple Mach-Zehnder interferometer (MZI) constructed from two beam splitters and one phase shifter. We also make use of :class:`~symop.devices.models.sources.number_state_source.NumberStateSource` to generate the input state. .. GENERATED FROM PYTHON SOURCE LINES 17-30 .. code-block:: Python from __future__ import annotations import numpy as np from symop.devices.models import BeamSplitter, NumberStateSource, PhaseShifter from symop.modes.envelopes import GaussianEnvelope from symop.modes.labels import Path, Polarization from symop.polynomial.state.ket import KetPolyState # Visualization Package import symop.viz as VI .. GENERATED FROM PYTHON SOURCE LINES 31-35 **Setup** Create one single-photon source, one phase shifter, and two 50/50 beam splitters for the Mach-Zehnder interferometer. .. GENERATED FROM PYTHON SOURCE LINES 35-47 .. code-block:: Python src = NumberStateSource( envelope=GaussianEnvelope(omega0=100.0, sigma=50.0, tau=0.0), polarization=Polarization.H(), n=1, ) ps = PhaseShifter(phi=np.pi / 2) bs1 = BeamSplitter(theta=np.pi / 4, phi_r=0.0) bs2 = BeamSplitter(theta=np.pi / 4, phi_r=0.0) .. GENERATED FROM PYTHON SOURCE LINES 48-51 **Generate the input state** Start from vacuum and populate one path with a single photon. .. GENERATED FROM PYTHON SOURCE LINES 51-56 .. code-block:: Python vac = KetPolyState.vacuum() state_in = src(vac, ports={"out": Path("src_out")}).with_label("input") .. GENERATED FROM PYTHON SOURCE LINES 57-64 **2) Standalone phase shifter** Apply the phase shifter directly to the occupied path. For an isolated single path, this changes only the phase of the state. By itself, this does not change number statistics, but the phase becomes important once the state interferes with another path. .. GENERATED FROM PYTHON SOURCE LINES 64-72 .. code-block:: Python state_shifted = ps( state_in, ports={"path": Path("src_out")}, ).with_label("phase_shifted") VI.display_many(state_in, state_shifted) .. raw:: html
2026-04-20T22:50:34.622769 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
2026-04-20T22:50:34.636924 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/


.. GENERATED FROM PYTHON SOURCE LINES 73-88 **Why does the standalone output look almost the same?** The phase shifter applies the unitary U(phi) = exp(i phi n) to the selected mode. At the operator level, this induces a^\dagger -> exp(i phi) a^\dagger so the excitation on that path acquires a phase factor. For a single isolated path, this does not change the direct photon-number probabilities. The effect becomes observable when the mode later interferes with another path. .. GENERATED FROM PYTHON SOURCE LINES 90-94 **3) First beam splitter: create the two MZI arms** Send the single input path through a 50/50 beam splitter. The unused second input is treated as vacuum. .. GENERATED FROM PYTHON SOURCE LINES 94-107 .. code-block:: Python state_after_bs1 = bs1( state_in, ports={ "in0": Path("src_out"), "in1": Path("unused_in"), "out0": Path("arm0"), "out1": Path("arm1"), }, ).with_label("after_bs1") VI.display_many(state_in, state_after_bs1) .. raw:: html
2026-04-20T22:50:34.652506 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
2026-04-20T22:50:34.671606 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/


.. GENERATED FROM PYTHON SOURCE LINES 108-111 **4) Apply the phase shifter to one arm** This creates a relative phase between the two interferometer arms. .. GENERATED FROM PYTHON SOURCE LINES 111-119 .. code-block:: Python state_after_ps = ps( state_after_bs1, ports={"path": Path("arm0")}, ).with_label("after_phase_shifter") VI.display_many(state_after_bs1, state_after_ps) .. raw:: html
2026-04-20T22:50:34.701333 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
2026-04-20T22:50:34.728670 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/


.. GENERATED FROM PYTHON SOURCE LINES 120-123 **5) Second beam splitter: recombine the two arms** The relative phase now affects the interference at the output. .. GENERATED FROM PYTHON SOURCE LINES 123-136 .. code-block:: Python state_out = bs2( state_after_ps, ports={ "in0": Path("arm0"), "in1": Path("arm1"), "out0": Path("out0"), "out1": Path("out1"), }, ).with_label("mzi_output") VI.display_many(state_after_ps, state_out) .. raw:: html
2026-04-20T22:50:34.758141 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
RichDisplay _repr_html_ failed.
    latex_src = |\psi_{\text{mzi_output}}\rangle=\left(\left(0.5 + 0.5\,i\\right)\,\hat{a}_{5}^\dagger + \left(-0.5 + 0.5\,i\\right)\,\hat{a}_{5}^\dagger\right)\lvert 0\rangle
    error = 
    $|\psi_{\text{mzi_output}}\rangle=((0.5 + 0.5\,i\)\,\hat{a}_{5}^\dagger + (-0.5 + 0.5\,i\)\,\hat{a}_{5}^\dagger)| 0\rangle$
    ^
    ParseException: Expected end of text, found '$'  (at char 0), (line:1, col:1)


.. GENERATED FROM PYTHON SOURCE LINES 137-147 **Why does the phase matter in the MZI?** After the first beam splitter, the photon amplitude is distributed over two paths. The phase shifter changes the relative phase of one arm. When both arms are recombined at the second beam splitter, this relative phase changes the interference pattern and therefore redistributes the output amplitudes between the two output ports. In this sense, the standalone phase shifter prepares a phase that becomes physically visible through interference. .. GENERATED FROM PYTHON SOURCE LINES 149-152 **6) Density-state version** The same sequence can also be applied to density states. .. GENERATED FROM PYTHON SOURCE LINES 152-186 .. code-block:: Python state_in_dense = state_in.to_density().with_label("input_density") state_after_bs1_dense = bs1( state_in_dense, ports={ "in0": Path("src_out"), "in1": Path("unused_in"), "out0": Path("arm0"), "out1": Path("arm1"), }, ).with_label("after_bs1_density") state_after_ps_dense = ps( state_after_bs1_dense, ports={"path": Path("arm0")}, ).with_label("after_phase_shifter_density") state_out_dense = bs2( state_after_ps_dense, ports={ "in0": Path("arm0"), "in1": Path("arm1"), "out0": Path("out0"), "out1": Path("out1"), }, ).with_label("mzi_output_density") VI.display_many( state_in_dense, state_after_bs1_dense, state_after_ps_dense, state_out_dense, ) .. raw:: html
2026-04-20T22:50:34.792241 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
2026-04-20T22:50:34.824440 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
2026-04-20T22:50:34.878278 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
2026-04-20T22:50:34.933208 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/


.. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.350 seconds) .. _sphx_glr_download_examples_devices_example_006_phase_shifter.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: example_006_phase_shifter.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: example_006_phase_shifter.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: example_006_phase_shifter.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_