diff --git a/docs/notebooks/bose_hubbard.ipynb b/docs/notebooks/bose_hubbard.ipynb index 808ec29..1cfcde0 100644 --- a/docs/notebooks/bose_hubbard.ipynb +++ b/docs/notebooks/bose_hubbard.ipynb @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "12d61180-27de-4d3a-bad6-73a0e94c8b16", "metadata": {}, "outputs": [ @@ -925,9 +925,9 @@ } ], "source": [ - "from optyx import photonic, classical\n", + "from optyx import photonic\n", "\n", - "circuit = photonic.Create(1, 1, 1) >> photonic.ansatz(3, 4) >> photonic.Id(2) @ classical.Select(1)\n", + "circuit = photonic.Create(1, 1, 1) >> photonic.ansatz(3, 4) >> photonic.Id(2) @ photonic.Select(1)\n", "circuit.draw()" ] }, diff --git a/docs/notebooks/bosonic-vqe-2.ipynb b/docs/notebooks/bosonic-vqe-2.ipynb index dc860e5..3c9d2c8 100644 --- a/docs/notebooks/bosonic-vqe-2.ipynb +++ b/docs/notebooks/bosonic-vqe-2.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "12d61180-27de-4d3a-bad6-73a0e94c8b16", "metadata": {}, "outputs": [ @@ -893,9 +893,9 @@ } ], "source": [ - "from optyx import photonic, classical\n", + "from optyx import photonic\n", "\n", - "circuit = photonic.Create(1, 1, 1) >> photonic.ansatz(3, 4) >> photonic.Id(2) @ classical.Select(1)\n", + "circuit = photonic.Create(1, 1, 1) >> photonic.ansatz(3, 4) >> photonic.Id(2) @ photonic.Select(1)\n", "circuit.draw()" ] }, diff --git a/docs/notebooks/readme_example.ipynb b/docs/notebooks/readme_example.ipynb index 3dd8ef0..80b7c33 100644 --- a/docs/notebooks/readme_example.ipynb +++ b/docs/notebooks/readme_example.ipynb @@ -1035,7 +1035,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "f1478f68", "metadata": {}, "outputs": [ @@ -1052,7 +1052,7 @@ ], "source": [ "# an amplitude (raw result of tensor contraction)\n", - "from optyx.classical import Select\n", + "from optyx.photonic import Select\n", "(\n", " hong_ou_mandel >> Select(1, 1)\n", ").eval().tensor.array" diff --git a/optyx/__init__.py b/optyx/__init__.py index b698690..84e2db5 100644 --- a/optyx/__init__.py +++ b/optyx/__init__.py @@ -1,17 +1,24 @@ from optyx.core import zw from optyx.core import zx from optyx.core import path +from optyx.core import backends +from optyx.core import diagram +from optyx.core import control from optyx.core.channel import ( Channel, CQMap, Discard, Encode, Measure, + Diagram, + Swap, + Spider, + Id, + Scalar, mode, qmode, qubit, - bit, - Diagram + bit ) from optyx._version import ( diff --git a/optyx/classical.py b/optyx/classical.py index 8955d15..2b47b0c 100644 --- a/optyx/classical.py +++ b/optyx/classical.py @@ -22,10 +22,10 @@ :nosignatures: :toctree: - NotBit - XorBit - AndBit - OrBit + Not + Xor + And + Or CopyBit SwapBit Z @@ -40,10 +40,10 @@ :nosignatures: :toctree: - AddN - SubN - MultiplyN - DivideN + Add + Sub + Multiply + Divide Mod2 CopyN SwapN @@ -90,7 +90,7 @@ 2. Using a :code:`BinaryMatrix` to define the transformation. 3. Using primitives such as :code:`XorBit`, :code:`AddN`, etc. ->>> xor_gate = XorBit(2) +>>> xor_gate = Xor(2) >>> >>> f = ClassicalFunction(lambda b: [b[0] ^ b[1]], ... bit**2, bit) @@ -122,7 +122,7 @@ **3. Arithmetic on natural-number modes** >>> num = Digit(3, 2) ->>> add = AddN(2) # (x,y,z) => x+y+z +>>> add = Add(2) # (x,y,z) => x+y+z >>> parity = add >> Mod2() # outputs (x+y+z) mod 2 as a bit >>> post = PostselectBit(1) >>> assert np.allclose( @@ -138,8 +138,7 @@ control, zw, zx, - diagram, - path + diagram ) from optyx.core.channel import ( bit, @@ -285,7 +284,7 @@ def __init__(self, value): ) -class AddN(ClassicalBox): +class Add(ClassicalBox): """ Classical addition of n natural numbers. The domain of the map is n modes. @@ -300,7 +299,7 @@ def __init__(self, n): ) -class SubN(ClassicalBox): +class Sub(ClassicalBox): """ Classical subtraction: subtract the first number from the second. The domain of the map is 2 modes. @@ -320,7 +319,7 @@ def __init__(self): ) -class MultiplyN(ClassicalBox): +class Multiply(ClassicalBox): """ Classical multiplication of 2 natural numbers. The domain of the map is 2 modes. @@ -335,7 +334,7 @@ def __init__(self): ) -class DivideN(ClassicalBox): +class Divide(ClassicalBox): """ Classical division: divide the first number by the second. The domain of the map is 2 modes. @@ -437,7 +436,7 @@ def __init__(self, *digits): ) -class NotBit(ClassicalBox): +class Not(ClassicalBox): """ Classical NOT gate. The domain of the map is a bit. @@ -453,7 +452,7 @@ def __init__(self): ) -class XorBit(ClassicalBox): +class Xor(ClassicalBox): """ Classical XOR gate. The domain of the map is n bits. @@ -462,14 +461,14 @@ class XorBit(ClassicalBox): """ def __init__(self, n=2): super().__init__( - f"XorBit({n})", + f"Xor({n})", zx.X(n, 1) @ diagram.Scalar(np.sqrt(n)), bit**n, bit ) -class AndBit(ClassicalBox): +class And(ClassicalBox): """ Classical AND gate. The domain of the map is 2 bits. @@ -517,7 +516,7 @@ def __init__(self): ) -class OrBit(ClassicalBox): +class Or(ClassicalBox): """ Classical OR gate. The domain of the map is n bits. @@ -526,7 +525,7 @@ class OrBit(ClassicalBox): """ def __init__(self, n=2): super().__init__( - f"OrBit({n})", + f"Or({n})", zx.Or(n), bit**n, bit @@ -658,24 +657,6 @@ def __init__(self, matrix): ) -class Select(Channel): - """ - Post-select on an occupation number. - """ - def __init__(self, *photons: int): - self.photons = photons - super().__init__( - f"Select({photons})", - zw.Select(*photons) - ) - - def to_path(self, dtype=complex) -> path.Matrix: - array = np.eye(len(self.photons)) - return path.Matrix[dtype]( - array, len(self.photons), 0, selections=self.photons - ) - - class Digit(ClassicalBox): """ Create a classical state with diff --git a/optyx/core/backends.py b/optyx/core/backends.py index f3bd54d..f100922 100644 --- a/optyx/core/backends.py +++ b/optyx/core/backends.py @@ -82,7 +82,7 @@ from quimb.tensor import TensorNetwork from optyx.core.channel import Diagram, Ty, mode, bit from optyx.core.path import Matrix -from optyx.utils.utils import preprocess_quimb_tensors_safe +from optyx.utils.misc import preprocess_quimb_tensors_safe @dataclass diff --git a/optyx/core/channel.py b/optyx/core/channel.py index 0d3b2a1..20b8335 100644 --- a/optyx/core/channel.py +++ b/optyx/core/channel.py @@ -931,6 +931,15 @@ class Hypergraph(hypergraph.Hypergraph): # pragma: no cover category, functor = Category, Functor +Id = Diagram.id +Scalar = lambda s: Channel( # noqa: E731 + name=f"Scalar({s})", + kraus=diagram.Scalar(s), + dom=Ty(), + cod=Ty() +) + + Hypergraph.ty_factory = Ty Diagram.spider_factory = Spider Diagram.hypergraph_factory = Hypergraph diff --git a/optyx/core/control.py b/optyx/core/control.py index d22948d..5736bab 100644 --- a/optyx/core/control.py +++ b/optyx/core/control.py @@ -40,7 +40,7 @@ from discopy import tensor from discopy.frobenius import Dim import numpy as np -from optyx.utils.utils import BasisTransition, is_diagram_LO +from optyx.utils.misc import BasisTransition, is_diagram_LO from optyx.core import diagram, zw diff --git a/optyx/core/diagram.py b/optyx/core/diagram.py index 51f9627..ed68a33 100644 --- a/optyx/core/diagram.py +++ b/optyx/core/diagram.py @@ -121,7 +121,7 @@ Let's check the branching law from [FC23]_. >>> from optyx.core.zw import Create, W ->>> from optyx.utils.utils import compare_arrays_of_different_sizes +>>> from optyx.utils.misc import compare_arrays_of_different_sizes >>> branching_l = Create(1) >> W(2) >>> branching_r = Create(1) @ Create(0) + Create(0) @ Create(1) @@ -207,7 +207,7 @@ from discopy.frobenius import Dim from discopy.quantum.gates import format_number from enum import Enum -from optyx.utils.utils import ( +from optyx.utils.misc import ( BasisTransition, calculate_right_offset, get_max_dim_for_box @@ -303,7 +303,7 @@ def to_tensor( ) -> tensor.Diagram: """Returns a :class:`tensor.Diagram` for evaluation""" from optyx.core import zw - from optyx.utils.utils import is_identity + from optyx.utils.misc import is_identity if input_dims is None: input_dims = [2 for _ in range(len(self.dom))] diff --git a/optyx/core/path.py b/optyx/core/path.py index 4f27061..7624a27 100644 --- a/optyx/core/path.py +++ b/optyx/core/path.py @@ -135,7 +135,7 @@ from discopy.utils import unbiased import discopy.matrix as underlying from discopy.tensor import Tensor -from optyx.utils.utils import occupation_numbers, amplitudes_2_tensor +from optyx.utils.misc import occupation_numbers, amplitudes_2_tensor def npperm(matrix): diff --git a/optyx/core/zw.py b/optyx/core/zw.py index 1306fa6..5a38334 100644 --- a/optyx/core/zw.py +++ b/optyx/core/zw.py @@ -39,7 +39,7 @@ **W commutativity** ->>> from optyx.utils.utils import compare_arrays_of_different_sizes +>>> from optyx.utils.misc import compare_arrays_of_different_sizes >>> from discopy.drawing import Equation >>> bSym_l = W(2) >>> bSym_r = W(2) >> SWAP @@ -150,7 +150,7 @@ from discopy.frobenius import Dim from discopy import tensor from optyx.core import diagram -from optyx.utils.utils import ( +from optyx.utils.misc import ( occupation_numbers, multinomial, BasisTransition @@ -158,11 +158,7 @@ from optyx.core.path import Matrix -class ZWDiagram(diagram.Diagram): - pass - - -class ZWBox(diagram.Box, ZWDiagram): +class ZWBox(diagram.Box, diagram.Diagram): """Box in a :class:`Diagram`""" def __init__(self, name, dom, cod, **params): diff --git a/optyx/photonic.py b/optyx/photonic.py index 375900f..830deb8 100644 --- a/optyx/photonic.py +++ b/optyx/photonic.py @@ -127,7 +127,7 @@ Finally, let's check the effect of having both photons on two output modes using postselection. ->>> from optyx.classical import Select +>>> from optyx.photonic import Select >>> diagram_qpath = Create(1, 1) >> BS >> Select(1, 1) >>> diagram_qpath.draw(path='docs/_static/BS_hom_2.png', figsize=(3, 3)) @@ -256,11 +256,12 @@ from optyx.core import ( channel, diagram, - zw + zw, + path ) from optyx.classical import ClassicalFunction, DiscardMode -from optyx.utils.utils import matrix_to_zw +from optyx.utils.misc import matrix_to_zw from optyx.core.channel import ( bit, @@ -274,6 +275,24 @@ ) +class Select(Channel): + """ + Post-select on an occupation number. + """ + def __init__(self, *photons: int): + self.photons = photons + super().__init__( + f"Select({photons})", + zw.Select(*photons) + ) + + def to_path(self, dtype=complex) -> path.Matrix: + array = np.eye(len(self.photons)) + return path.Matrix[dtype]( + array, len(self.photons), 0, selections=self.photons + ) + + class Scalar(Channel): """ Scalar with a complex value. @@ -546,7 +565,7 @@ class BBS(AbstractGate): We can check the Hong-Ou-Mandel effect: - >>> from optyx.classical import Select + >>> from optyx.photonic import Select >>> d = Create(1, 1) >> BS >>> assert np.isclose((d >> Select(0, 2)).to_path().prob().array, ... 0.5) diff --git a/optyx/qubits.py b/optyx/qubits.py index 1a7deef..c75cdf7 100644 --- a/optyx/qubits.py +++ b/optyx/qubits.py @@ -305,7 +305,7 @@ from discopy import symmetric from sympy import lambdify # from pytket import circuit as tket_circuit -from optyx.utils.utils import explode_channel +from optyx.utils.misc import explode_channel from optyx.core import ( channel, diagram, @@ -456,7 +456,7 @@ class QubitChannel(Channel): # def decomp(self): # """Decompose into elementary gates.""" - # from optyx.utils.utils import decomp_ar + # from optyx.utils.misc import decomp_ar # from discopy import symmetric # return symmetric.Functor( # ob=lambda x: qubit**len(x), @@ -466,7 +466,7 @@ class QubitChannel(Channel): # def to_dual_rail(self): # """Convert to dual-rail encoding.""" - # from optyx.utils.utils import ar_zx2path + # from optyx.utils.misc import ar_zx2path # from optyx import qmode # from discopy import symmetric @@ -680,12 +680,11 @@ def _to_dual_rail(self): from optyx import ( photonic, - qmode, - classical + qmode ) from optyx.core import zw create = photonic.Create(1) - annil = classical.Select(1) + annil = photonic.Select(1) comonoid = Channel("Split", zw.Split(2)) monoid = Channel("Merge", zw.Merge(2)) BS = photonic.BS @@ -768,15 +767,14 @@ def _decomp(self): def _to_dual_rail(self): # pragma: no cover """Convert to dual-rail encoding.""" from optyx import ( - photonic, - classical + photonic ) root2 = photonic.Scalar(2**0.5) unit = photonic.Create(0) - counit = classical.Select(0) + counit = photonic.Select(0) create = photonic.Create(1) - annil = classical.Select(1) + annil = photonic.Select(1) BS = photonic.BS n, m = len(self.dom), len(self.cod) phase = 1 + self.phase if self.phase < 0 else self.phase diff --git a/optyx/utils/utils.py b/optyx/utils/misc.py similarity index 100% rename from optyx/utils/utils.py rename to optyx/utils/misc.py diff --git a/optyx/utils/perceval_conversion.py b/optyx/utils/perceval_conversion.py index 16cf572..e7ac7d2 100644 --- a/optyx/utils/perceval_conversion.py +++ b/optyx/utils/perceval_conversion.py @@ -1,10 +1,9 @@ -from optyx.utils.utils import matrix_to_zw, invert_perm +from optyx.utils.misc import matrix_to_zw, invert_perm from optyx import Channel, mode, qmode, photonic, bit from optyx.classical import ClassicalFunction, BitControlledGate from perceval.components.detector import DetectionType from optyx.core.channel import Spider, Diagram, Measure -from optyx.classical import Select -from optyx.photonic import Create +from optyx.photonic import Create, Select from optyx.utils.postselect_parser import compile_postselect from optyx.core.zw import Endo import numpy as np diff --git a/test/test_backends.py b/test/test_backends.py index 040abe5..eaf5526 100644 --- a/test/test_backends.py +++ b/test/test_backends.py @@ -545,7 +545,7 @@ def test_prob_effect_only_errors_no_state(circuit): @pytest.mark.parametrize("circuit", [unitary_circuit, non_unitary_circuit]) def test_prob_diagram_only_effect_errors_no_state(circuit): nmodes = len(circuit.cod) - diagram = circuit >> classical.Select(*([0]*(nmodes-1) + [2])) + diagram = circuit >> photonic.Select(*([0]*(nmodes-1) + [2])) backend = PercevalBackend() with pytest.raises(ValueError): _ = diagram.eval(backend) diff --git a/test/test_classical.py b/test/test_classical.py index c2a63b7..5399eed 100644 --- a/test/test_classical.py +++ b/test/test_classical.py @@ -1,29 +1,29 @@ from optyx.classical import * -from optyx.utils.utils import compare_arrays_of_different_sizes +from optyx.utils.misc import compare_arrays_of_different_sizes def test_addn(): - d_1 = Digit(2, 3) >> AddN(2) + d_1 = Digit(2, 3) >> Add(2) d_2 = Digit(5) assert compare_arrays_of_different_sizes(d_1.double().to_tensor().eval().array, d_2.double().to_tensor().eval().array) def test_subn(): - d_1 = Digit(2, 5) >> SubN() + d_1 = Digit(2, 5) >> Sub() d_2 = Digit(3) assert compare_arrays_of_different_sizes(d_1.double().to_tensor().eval().array, d_2.double().to_tensor().eval().array) def test_multiplyn(): - d_1 = Digit(2, 3) >> MultiplyN() + d_1 = Digit(2, 3) >> Multiply() d_2 = Digit(6) assert compare_arrays_of_different_sizes(d_1.double().to_tensor().eval().array, d_2.double().to_tensor().eval().array) def test_dividen(): - d_1 = Digit(6, 2) >> DivideN() + d_1 = Digit(6, 2) >> Divide() d_2 = Digit(3) assert compare_arrays_of_different_sizes(d_1.double().to_tensor().eval().array, @@ -65,21 +65,21 @@ def test_postselectdigit(): d_2.double().to_tensor().eval().array) def test_notbit(): - d_1 = Bit(1) >> NotBit() + d_1 = Bit(1) >> Not() d_2 = Bit(0) assert compare_arrays_of_different_sizes(d_1.double().to_tensor().eval().array, d_2.double().to_tensor().eval().array) def test_xorbit(): - d_1 = Bit(1, 0) >> XorBit() + d_1 = Bit(1, 0) >> Xor() d_2 = Bit(1) assert compare_arrays_of_different_sizes(d_1.double().to_tensor().eval().array, d_2.double().to_tensor().eval().array) def test_andbit(): - d_1 = Bit(1, 0) >> AndBit() + d_1 = Bit(1, 0) >> And() d_2 = Bit(0) assert compare_arrays_of_different_sizes(d_1.double().to_tensor().eval().array, @@ -100,7 +100,7 @@ def test_swapbit(): d_2.double().to_tensor().eval().array) def test_orbit(): - d_1 = Bit(1, 0) >> OrBit() + d_1 = Bit(1, 0) >> Or() d_2 = Bit(1) assert compare_arrays_of_different_sizes(d_1.double().to_tensor().eval().array, diff --git a/test/test_feed_forward.py b/test/test_feed_forward.py index f4b9842..db800d0 100644 --- a/test/test_feed_forward.py +++ b/test/test_feed_forward.py @@ -6,7 +6,7 @@ from optyx.core.zw import Create, W, ZBox from optyx.core.diagram import PhotonThresholdDetector, Mode, Swap, Bit, Id from optyx.photonic import Phase, BS, MZI -from optyx.utils.utils import matrix_to_zw +from optyx.utils.misc import matrix_to_zw from optyx.classical import BitControlledGate, BitControlledPhaseShift from optyx.core.channel import Channel, qmode from optyx import photonic diff --git a/test/test_zw.py b/test/test_zw.py index b416029..4d32e19 100644 --- a/test/test_zw.py +++ b/test/test_zw.py @@ -2,7 +2,7 @@ from optyx.core.zw import * from optyx.core.diagram import mode, DualRail, EmbeddingTensor, Swap, Diagram, Mode, Spider, Scalar -from optyx.utils.utils import compare_arrays_of_different_sizes, calculate_num_creations_selections +from optyx.utils.misc import compare_arrays_of_different_sizes, calculate_num_creations_selections import optyx.core.zx as zx from optyx import photonic import itertools diff --git a/test/test_zw_2_circuit.py b/test/test_zw_2_circuit.py index bde3b60..2071089 100644 --- a/test/test_zw_2_circuit.py +++ b/test/test_zw_2_circuit.py @@ -1,6 +1,6 @@ import optyx.core.zw as zw from optyx import photonic, classical -from optyx.utils.utils import tensor_2_amplitudes, calculate_num_creations_selections +from optyx.utils.misc import tensor_2_amplitudes, calculate_num_creations_selections import itertools import pytest import perceval as pcvl @@ -132,7 +132,7 @@ def test_BS_channel(photons_1, photons_2): BS = photonic.BBS(0) diagram_qpath = photonic.Create(photons_1, photons_2) >> BS - diagram_zw = diagram_qpath >> classical.Select(photons_1, photons_2) + diagram_zw = diagram_qpath >> photonic.Select(photons_1, photons_2) tensor = diagram_zw.double().to_tensor() n_photons_out = calculate_num_creations_selections(diagram_zw) @@ -153,7 +153,7 @@ def test_BBS_channel(photons_1, photons_2, bias): BS = photonic.BBS(bias) diagram_qpath = photonic.Create(photons_1, photons_2) >> BS - diagram_zw = diagram_qpath >> classical.Select(photons_1, photons_2) + diagram_zw = diagram_qpath >> photonic.Select(photons_1, photons_2) tensor = diagram_zw.double().to_tensor() n_photons_out = calculate_num_creations_selections(diagram_zw) @@ -174,7 +174,7 @@ def test_TBS_channel(photons_1, photons_2, theta): BS = photonic.TBS(theta) diagram_qpath = photonic.Create(photons_1, photons_2) >> BS - diagram_zw = diagram_qpath >> classical.Select(photons_1, photons_2) + diagram_zw = diagram_qpath >> photonic.Select(photons_1, photons_2) tensor = diagram_zw.double().to_tensor() prob_zw = tensor.eval().array @@ -192,7 +192,7 @@ def test_MZI_channel(photons_1, photons_2, theta, phi): BS = photonic.MZI(theta, phi) diagram_qpath = photonic.Create(photons_1, photons_2) >> BS - diagram_zw = diagram_qpath >> classical.Select(photons_1, photons_2) + diagram_zw = diagram_qpath >> photonic.Select(photons_1, photons_2) tensor = diagram_zw.double().to_tensor() n_photons_out = calculate_num_creations_selections(diagram_zw) diff --git a/test/test_zw_truncated_arrays.py b/test/test_zw_truncated_arrays.py index e13fd34..8563e41 100644 --- a/test/test_zw_truncated_arrays.py +++ b/test/test_zw_truncated_arrays.py @@ -3,7 +3,7 @@ import numpy as np import pytest from optyx.photonic import ansatz, MZI, TBS -from optyx.utils.utils import matrix_to_zw, filter_occupation_numbers +from optyx.utils.misc import matrix_to_zw, filter_occupation_numbers @pytest.mark.skip(reason="Helper function for testing") def kron_truncation_swap(input_dims: list[int]) -> np.ndarray[complex]: