.. 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-24T11:36:46.372110 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:46.400693 image/svg+xml Matplotlib v3.10.9, 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-24T11:36:46.431230 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:46.471545 image/svg+xml Matplotlib v3.10.9, 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-24T11:36:46.523536 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:46.577474 image/svg+xml Matplotlib v3.10.9, 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-24T11:36:46.631790 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:46.687335 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/


.. 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-24T11:36:46.738727 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:46.814911 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:46.933109 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:47.055706 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/


.. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.764 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 `_