.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "examples/devices/example_004_beamsplitter.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_004_beamsplitter.py: Ideal Beam-Splitter Example =========================== This example demonstrates the use of the :class:`~symop.devices.models.beamsplitters.beamsplitter.BeamSplitter` model, we also make use of :class:`~symop.devices.models.sources.number_state_source.NumberStateSource` to generate the state. .. GENERATED FROM PYTHON SOURCE LINES 11-23 .. code-block:: Python from __future__ import annotations import numpy as np from symop.devices.models import BeamSplitter, NumberStateSource from symop.devices.models.detectors.number_detector import NumberDetector from symop.modes.labels import Path, Polarization from symop.modes.envelopes import GaussianEnvelope from symop.polynomial.state.ket import KetPolyState # Visualization Package import symop.viz as VI .. GENERATED FROM PYTHON SOURCE LINES 24-27 **Setup** Set up mode labels for sources and beamsplitter (50/50) .. GENERATED FROM PYTHON SOURCE LINES 27-42 .. code-block:: Python src_a = NumberStateSource( envelope=GaussianEnvelope(omega0=100.0, sigma=50.0, tau=0.0), polarization=Polarization.H(), n=1, ) src_b = NumberStateSource( envelope=GaussianEnvelope(omega0=100.0, sigma=50.0, tau=0.0), polarization=Polarization.H(), n=1, ) bs = BeamSplitter(theta=np.pi / 4, phi_r=0) .. GENERATED FROM PYTHON SOURCE LINES 43-46 **1) Generate the states** - Generate vacuum states for the sources to populate .. GENERATED FROM PYTHON SOURCE LINES 46-52 .. code-block:: Python vac = KetPolyState.vacuum() state_a = src_a(vac, ports={"out": Path("src_a_out")}).with_label("in_A") state_b = src_b(vac, ports={"out": Path("src_b_out")}).with_label("in_B") .. GENERATED FROM PYTHON SOURCE LINES 53-57 **2) Interfere the two states** - Join the states - Interfere them .. GENERATED FROM PYTHON SOURCE LINES 57-70 .. code-block:: Python state_joint = state_a.join(state_b).with_label("joint") state_interfered = bs( state_joint, ports={ "in0": Path("src_a_out"), "in1": Path("src_b_out"), "out0": Path("bs_out0"), "out1": Path("bs_out1"), }, ).with_label("interfered") .. GENERATED FROM PYTHON SOURCE LINES 71-72 **2a) Inspect the states** .. GENERATED FROM PYTHON SOURCE LINES 72-75 .. code-block:: Python VI.display_many(state_a, state_b, state_joint, state_interfered) .. raw:: html
2026-04-24T11:36:44.900279 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:44.928629 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:44.962949 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:45.041854 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/


.. GENERATED FROM PYTHON SOURCE LINES 76-96 *Why does the output contain several terms?* The beamsplitter acts linearly on each input creation operator. Since the joint input state contains one photon in each input path, the output is obtained by expanding the product of two linear combinations of output-mode operators. This produces amplitudes for all possible two-photon output configurations: - both photons in output path 0, - one photon in each output path, - both photons in output path 1. These terms are amplitudes, not measurement probabilities. The actual detection statistics depend on the overlaps between the output modes. When the two inputs are fully indistinguishable (same envelope and same polarization), interference can cancel coincidence contributions and enhance bunching, as in the Hong-Ou-Mandel effect. .. GENERATED FROM PYTHON SOURCE LINES 98-101 **2b) Detection statistics** Inspecting the states gives all possibilities, but due to generality the effective state is hidden in the canonicalization. To see the outcome statistics we can measure each port with number detector. Which should detect either 0 or 2 photons. .. GENERATED FROM PYTHON SOURCE LINES 101-106 .. code-block:: Python det = NumberDetector() observation_port_top = det.observe(state_interfered, ports={"in": Path("bs_out0")}) observation_port_bot = det.observe(state_interfered, ports={"in": Path("bs_out0")}) VI.plot(observation_port_top) .. image-sg:: /examples/devices/images/sphx_glr_example_004_beamsplitter_001.png :alt: $\mathrm{Observation}\left(\mathrm{outcomes}=2,\ \mathbb{E}=1.0\right)$ :srcset: /examples/devices/images/sphx_glr_example_004_beamsplitter_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none (
, ) .. GENERATED FROM PYTHON SOURCE LINES 108-112 .. code-block:: Python VI.plot(observation_port_bot) .. image-sg:: /examples/devices/images/sphx_glr_example_004_beamsplitter_002.png :alt: $\mathrm{Observation}\left(\mathrm{outcomes}=2,\ \mathbb{E}=1.0\right)$ :srcset: /examples/devices/images/sphx_glr_example_004_beamsplitter_002.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none (
, ) .. GENERATED FROM PYTHON SOURCE LINES 113-118 **3) Interfering just one pulse with vacuum** Beam splitter can also work without the counterpart provided In the example below, we interfere the photon in path ``src_a_out`` with vacuum in the other port .. GENERATED FROM PYTHON SOURCE LINES 118-133 .. code-block:: Python state_interfered_single = bs( state_a, ports={ "in0": Path("src_a_out"), "in1": Path("any"), "out0": Path("bs_out0"), "out1": Path("bs_out1"), }, ).with_label("interfered_single") VI.display_many(state_a, state_interfered_single) .. raw:: html
2026-04-24T11:36:45.240156 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:45.280872 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/


.. GENERATED FROM PYTHON SOURCE LINES 134-138 **4) Interfere the two states** - Join the states - Interfere them .. GENERATED FROM PYTHON SOURCE LINES 138-152 .. code-block:: Python state_joint_dense = state_joint.to_density() state_interfered_dense = bs( state_joint_dense, ports={ "in0": Path("src_a_out"), "in1": Path("src_b_out"), "out0": Path("bs_out0"), "out1": Path("bs_out1"), }, ).with_label("interfered") VI.display_many(state_joint_dense, state_interfered_dense) .. raw:: html
2026-04-24T11:36:45.346370 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/
2026-04-24T11:36:45.770582 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/


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