diff --git a/pyproject.toml b/pyproject.toml index 37a8b516a4..28343e4dde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ optuna = "^4.2.1" tabulate = "^0.9.0" openqasm3 = { version = "^1.0.1", extras = ["parser"] } qulacs = { version = "^0.6.4", optional = true, python = "<3.13" } +perceval-quandela = ">=0.13" [tool.poetry.group.dev] optional = true diff --git a/scripts/playground.ipynb b/scripts/playground.ipynb new file mode 100644 index 0000000000..df2fdb98da --- /dev/null +++ b/scripts/playground.ipynb @@ -0,0 +1,503 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "ed697cdf", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-30T14:16:42.504441Z", + "start_time": "2025-06-30T14:16:41.539299Z" + } + }, + "source": [ + "from qibo import set_backend, get_backend\n", + "from qibo.backends import Modality\n", + "from qibo.gates import PS, BS, H\n", + "from qibo.models import Circuit\n", + "import numpy as np" + ], + "outputs": [], + "execution_count": 1 + }, + { + "cell_type": "code", + "id": "107e6780", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-30T14:16:43.042382Z", + "start_time": "2025-06-30T14:16:42.510792Z" + } + }, + "source": "set_backend(\"loqc\")", + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Qibo 0.2.19|INFO|2025-06-30 16:16:43]: Using LOQC strong simulation backend on /CPU:0\n" + ] + } + ], + "execution_count": 2 + }, + { + "cell_type": "code", + "id": "3f931e08", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-30T14:16:43.455276Z", + "start_time": "2025-06-30T14:16:43.450564Z" + } + }, + "source": [ + "backend = get_backend()\n", + "print(backend.versions, backend.modality)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'qibo': '0.2.19', 'perceval': '0.13.0'} Modality.PHOTONIC_CV\n" + ] + } + ], + "execution_count": 3 + }, + { + "cell_type": "code", + "id": "7342e8df", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-30T14:16:43.470232Z", + "start_time": "2025-06-30T14:16:43.463435Z" + } + }, + "source": [ + "# Retrieve matrix for parametrized gates\n", + "bs1 = BS(0, 1, np.pi/2)\n", + "print(\"theta=pi/2\", bs1.matrix())\n", + "print(bs1.photonic_component.compute_unitary())\n", + "\n", + "bs1.parameters = [0.4]\n", + "print(\"theta=0.4\", bs1.matrix())\n", + "print(bs1.photonic_component.compute_unitary())\n", + "\n", + "# Through the backend too\n", + "print(\"Phase shifter, phi=0.3\", backend.matrix(PS(0, 0.3)))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "theta=pi/2 [[0.70710678+0.j 0. -0.70710678j]\n", + " [0. -0.70710678j 0.70710678+0.j ]]\n", + "[[0.70710678+0.j 0. +0.70710678j]\n", + " [0. +0.70710678j 0.70710678+0.j ]]\n", + "theta=0.4 [[0.98006658+0.j 0. -0.19866933j]\n", + " [0. -0.19866933j 0.98006658+0.j ]]\n", + "[[0.98006658+0.j 0. +0.19866933j]\n", + " [0. +0.19866933j 0.98006658+0.j ]]\n", + "Phase shifter, phi=0.3 [-0.41614684+0.90929743j]\n" + ] + } + ], + "execution_count": 4 + }, + { + "cell_type": "code", + "id": "28dfa5c1", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-30T14:16:43.483826Z", + "start_time": "2025-06-30T14:16:43.479508Z" + } + }, + "source": [ + "# Create a photonic circuit\n", + "\n", + "pc = Circuit(3, modality=Modality.PHOTONIC)\n", + "pc.add(H(0))\n", + "pc.add(PS(0, 0.3, trainable=True))\n", + "pc.add(BS(0, 2))\n", + "pc.add(BS(1, 2))\n", + "pc.add(PS(2, 0.8, trainable=False))\n", + "pc.add(BS(1, 2))\n" + ], + "outputs": [], + "execution_count": 5 + }, + { + "cell_type": "code", + "id": "45900543", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-30T14:16:43.508948Z", + "start_time": "2025-06-30T14:16:43.503128Z" + } + }, + "source": [ + "print(pc.summary())\n", + "print()\n", + "print(pc.diagram())" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Circuit depth = 6\n", + "Total number of gates = 6\n", + "Number of modes = 3\n", + "Most common gates:\n", + "Beam splitter: 3\n", + "Phase shifter: 2\n", + "h: 1\n", + "\n", + "0: ─H─PS─X────────\n", + "1: ──────|─X────X─\n", + "2: ──────X─X─PS─X─\n" + ] + } + ], + "execution_count": 6 + }, + { + "cell_type": "code", + "id": "69e98485", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-30T14:16:43.611185Z", + "start_time": "2025-06-30T14:16:43.590177Z" + } + }, + "source": [ + "print(pc.to_pcvl())" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pcvl.Experiment(3)\\\n", + " .add((0, 1), BS.H())\\\n", + " .add((0,), PS(0.3))\\\n", + " .add((0, 2), BS(1.5707963267948966))\\\n", + " .add((1, 2), BS(1.5707963267948966))\\\n", + " .add((2,), PS(0.8))\\\n", + " .add((1, 2), BS(1.5707963267948966))\n" + ] + } + ], + "execution_count": 7 + }, + { + "cell_type": "code", + "id": "d9207ff4", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-30T14:16:43.637014Z", + "start_time": "2025-06-30T14:16:43.621759Z" + } + }, + "source": [ + "# circuit simulation\n", + "params = [0.]\n", + "res = pc(initial_state=(1, 0, 1), nshots=100)\n", + "print(res)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MeasurementResult(qubits=3, nshots=100)\n" + ] + } + ], + "execution_count": 8 + }, + { + "cell_type": "code", + "id": "d92b37c4", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-30T14:16:43.679822Z", + "start_time": "2025-06-30T14:16:43.669093Z" + } + }, + "source": [ + "res.samples()" + ], + "outputs": [ + { + "data": { + "text/plain": [ + "[[0, 1, 1],\n", + " [2, 0, 0],\n", + " [1, 0, 1],\n", + " [0, 0, 2],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [2, 0, 0],\n", + " [2, 0, 0],\n", + " [0, 1, 1],\n", + " [2, 0, 0],\n", + " [0, 2, 0],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [2, 0, 0],\n", + " [1, 1, 0],\n", + " [0, 2, 0],\n", + " [2, 0, 0],\n", + " [1, 1, 0],\n", + " [0, 2, 0],\n", + " [0, 0, 2],\n", + " [0, 0, 2],\n", + " [2, 0, 0],\n", + " [1, 1, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [2, 0, 0],\n", + " [1, 1, 0],\n", + " [0, 0, 2],\n", + " [2, 0, 0],\n", + " [1, 1, 0],\n", + " [2, 0, 0],\n", + " [2, 0, 0],\n", + " [2, 0, 0],\n", + " [1, 0, 1],\n", + " [1, 1, 0],\n", + " [0, 1, 1],\n", + " [1, 1, 0],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [1, 0, 1],\n", + " [0, 0, 2],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [1, 1, 0],\n", + " [0, 0, 2],\n", + " [0, 2, 0],\n", + " [2, 0, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 0, 2],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [0, 0, 2],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [0, 0, 2],\n", + " [2, 0, 0],\n", + " [2, 0, 0],\n", + " [1, 1, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [2, 0, 0],\n", + " [1, 1, 0],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 0, 2],\n", + " [0, 0, 2],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [2, 0, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [2, 0, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [2, 0, 0],\n", + " [2, 0, 0],\n", + " [1, 0, 1],\n", + " [0, 2, 0],\n", + " [2, 0, 0],\n", + " [0, 1, 1],\n", + " [1, 1, 0]]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 9 + }, + { + "cell_type": "code", + "id": "74846079", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-30T14:16:53.725634Z", + "start_time": "2025-06-30T14:16:43.700522Z" + } + }, + "source": [ + "# Now with a Cloud back-end\n", + "set_backend(\"quandela\", platform=\"qpu:ascella\", token=\"YOUR TOKEN\") # Requires a valid Quandela Cloud token\n", + "res = pc(initial_state=(1, 0, 1), nshots=100)\n", + "res.samples() # qpu:ascella is a noisy platform (it's an actual QPU), hence the photon loss observed in the results (nearly all output states have only 1 detection)" + ], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Qibo 0.2.19|INFO|2025-06-30 16:16:43]: Using Quandela Cloud backend on /CPU:0\n" + ] + }, + { + "data": { + "text/plain": [ + "[[1, 0, 0],\n", + " [1, 0, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [1, 0, 0],\n", + " [0, 0, 1],\n", + " [0, 0, 1],\n", + " [1, 0, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [1, 1, 0],\n", + " [0, 0, 1],\n", + " [1, 0, 0],\n", + " [0, 0, 1],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [1, 0, 0],\n", + " [0, 0, 1],\n", + " [0, 1, 0],\n", + " [1, 0, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [1, 1, 0],\n", + " [1, 0, 0],\n", + " [0, 1, 0],\n", + " [0, 0, 1],\n", + " [0, 1, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [0, 1, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [0, 0, 1],\n", + " [0, 0, 1],\n", + " [0, 0, 1],\n", + " [0, 1, 0],\n", + " [0, 0, 1],\n", + " [0, 0, 1],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [0, 1, 0],\n", + " [1, 0, 0],\n", + " [0, 0, 1],\n", + " [1, 0, 0],\n", + " [0, 0, 1],\n", + " [0, 0, 1],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [0, 0, 1],\n", + " [0, 1, 0],\n", + " [0, 0, 1],\n", + " [1, 0, 0],\n", + " [0, 0, 1],\n", + " [0, 1, 0],\n", + " [1, 0, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [0, 0, 1],\n", + " [0, 1, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [0, 1, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [0, 0, 1],\n", + " [0, 0, 1],\n", + " [0, 0, 1],\n", + " [0, 0, 1],\n", + " [1, 0, 0],\n", + " [1, 1, 0],\n", + " [0, 0, 1],\n", + " [1, 0, 0],\n", + " [0, 0, 1],\n", + " [1, 0, 0],\n", + " [0, 1, 0],\n", + " [0, 0, 1],\n", + " [0, 0, 1],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [0, 1, 1],\n", + " [0, 0, 1],\n", + " [0, 0, 1],\n", + " [0, 1, 0],\n", + " [0, 0, 1],\n", + " [0, 1, 0]]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 10 + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index c8ed9a2e5b..30f960d155 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -7,11 +7,12 @@ from qibo.backends.abstract import Backend from qibo.backends.clifford import CliffordBackend from qibo.backends.hamming_weight import HammingWeightBackend +from qibo.backends.modality import Modality from qibo.backends.npmatrices import NumpyMatrices from qibo.backends.numpy import NumpyBackend from qibo.config import log, raise_error -QIBO_NATIVE_BACKENDS = ("numpy", "qulacs") +QIBO_NATIVE_BACKENDS = ("numpy", "qulacs", "loqc", "quandela") class MissingBackend(ValueError): @@ -58,7 +59,15 @@ def load(backend: str, **kwargs) -> Backend: backend_obj = QulacsBackend() backend_obj.set_dtype(dtype=dtype) - return backend_obj + if backend == "loqc": + from qibo.backends.photonic_strong_simulation import LoqcStrongBackend + + return LoqcStrongBackend() + + if backend == "quandela": + from qibo.backends.photonic_strong_simulation import QuandelaCloudBackend + + return QuandelaCloudBackend(**kwargs) backend_obj = NumpyBackend() backend_obj.set_dtype(dtype=dtype) diff --git a/src/qibo/backends/abstract.py b/src/qibo/backends/abstract.py index adf22bac52..23963709ca 100644 --- a/src/qibo/backends/abstract.py +++ b/src/qibo/backends/abstract.py @@ -3,6 +3,8 @@ from qibo.config import raise_error +from .modality import Modality + class Backend(abc.ABC): def __init__(self): @@ -17,6 +19,7 @@ def __init__(self): self.nthreads = 1 self.supports_multigpu = False self.oom_error = MemoryError + self.modality = Modality.GATE def __reduce__(self): """Allow pickling backend objects that have references to modules.""" diff --git a/src/qibo/backends/modality.py b/src/qibo/backends/modality.py new file mode 100644 index 0000000000..6e32e36772 --- /dev/null +++ b/src/qibo/backends/modality.py @@ -0,0 +1,24 @@ +from enum import Enum + + +class Modality(Enum): + GATE = "gate" + PHOTONIC = "photonic" + PHOTONIC_CV = "photonic-cv" + PHOTONIC_DV = "photonic-dv" + PULSE = "pulse" + + @property + def is_gate(self) -> bool: + """Check if the modality is gate-based.""" + return self == Modality.GATE + + @property + def is_photonic(self) -> bool: + """Check if the modality is photonic.""" + return self in (Modality.PHOTONIC, Modality.PHOTONIC_DV, Modality.PHOTONIC_CV) + + @property + def is_pulse(self) -> bool: + """Check if the modality is pulse.""" + return self == Modality.PULSE diff --git a/src/qibo/backends/photonic_strong_simulation.py b/src/qibo/backends/photonic_strong_simulation.py new file mode 100644 index 0000000000..cf63651beb --- /dev/null +++ b/src/qibo/backends/photonic_strong_simulation.py @@ -0,0 +1,105 @@ +import numpy as np +from perceval import ( + BS, + BasicState, + Experiment, + Processor, + RemoteProcessor, +) +from perceval import __version__ as pcvl_version +from perceval import ( + probs_to_samples, +) +from perceval.algorithm import Sampler + +from qibo import Circuit, __version__ +from qibo.backends.numpy import NumpyBackend, NumpyMatrices +from qibo.gates import H, PhotonicGate +from qibo.measurements import MeasurementResult + +from .modality import Modality + + +class LOQCMatrices: + def __init__(self, dtype): + self.dtype = dtype + + def _cast(self, x, dtype): + if isinstance(x, list): + return np.array(x, dtype=dtype) + return x.astype(dtype) + + def BS(self, theta): + cos = np.cos(theta / 2.0) + 0j + isin = -1j * np.sin(theta / 2.0) + return self._cast([[cos, isin], [isin, cos]], dtype=self.dtype) + + def PS(self, phi): + return self._cast([np.exp(1j * phi)], dtype=self.dtype) + + def H(self): + npmat = NumpyMatrices(self.dtype) + return npmat.H + + +class LoqcStrongBackend(NumpyBackend): + + def __init__(self): + super().__init__() + self.name = "LOQC strong simulation" + self.matrices = LOQCMatrices(self.dtype) + self.tensor_types = np.ndarray + self.versions = {"qibo": __version__, "perceval": pcvl_version} + self.modality = Modality.PHOTONIC_CV + + @staticmethod + def _convert_gate(gate): + if isinstance(gate, PhotonicGate): + return gate.photonic_component + if isinstance(gate, H): + return BS.H() + + def _convert_circuit(self, circuit: Circuit): + exp = Experiment(circuit.nqubits) + for gate in circuit.queue: + component = self._convert_gate(gate) + exp.add(gate.wires, component) + return exp + + def execute_circuit(self, circuit: Circuit, initial_state=None, nshots=None): + experiment = self._convert_circuit(circuit) + if initial_state is not None: + experiment.with_input(BasicState(initial_state)) + p = Processor("SLOS", experiment) + samples = probs_to_samples(p.probs()["results"], count=nshots) + + m = MeasurementResult(experiment.m) + m.register_samples([list(k) for k in samples]) + return m + + +class QuandelaCloudBackend(LoqcStrongBackend): + # Should probably be moved to qibo-cloud-backends + # But for this proof of concept, I've written the class here + + def __init__(self, platform, token): + super().__init__() + self._platform = platform + self._token = token + self.name = "Quandela Cloud" + + def execute_circuit(self, circuit: Circuit, initial_state=None, nshots=None): + assert initial_state is not None + assert nshots is not None + + experiment = self._convert_circuit(circuit) + rp = RemoteProcessor(self._platform, self._token) + rp.add(0, experiment) + rp.with_input(BasicState(initial_state)) + rp.min_detected_photons_filter(1) + sampler = Sampler(rp, max_shots_per_call=nshots) + response = sampler.samples(nshots) + + m = MeasurementResult(experiment.m) + m.register_samples([list(k) for k in response["results"]]) + return m diff --git a/src/qibo/config.py b/src/qibo/config.py index 015c2a7a50..ebd077f0c1 100644 --- a/src/qibo/config.py +++ b/src/qibo/config.py @@ -35,7 +35,7 @@ MAX_ITERATIONS = 50 -def raise_error(exception, message=None): +def raise_error(exception: type[Exception], message=None): """Raise exception with logging error. Args: diff --git a/src/qibo/gates/__init__.py b/src/qibo/gates/__init__.py index 9dd06e6c98..111fa108a4 100644 --- a/src/qibo/gates/__init__.py +++ b/src/qibo/gates/__init__.py @@ -2,3 +2,5 @@ from qibo.gates.gates import * from qibo.gates.measurements import * from qibo.gates.special import * + +from .photonic_gates import * diff --git a/src/qibo/gates/gates.py b/src/qibo/gates/gates.py index 00972be099..cabed4b19a 100644 --- a/src/qibo/gates/gates.py +++ b/src/qibo/gates/gates.py @@ -39,6 +39,11 @@ def clifford(self): def qasm_label(self): return "h" + @property + def wires(self): # wires is used for the photonic computation + q = self.target_qubits[0] + return (q, q + 1) + class X(Gate): """The Pauli-:math:`X` gate. diff --git a/src/qibo/gates/photonic_gates.py b/src/qibo/gates/photonic_gates.py new file mode 100644 index 0000000000..eef4ed6c8f --- /dev/null +++ b/src/qibo/gates/photonic_gates.py @@ -0,0 +1,74 @@ +from abc import abstractmethod + +import numpy as np + +from .abstract import ParametrizedGate +from .gates import H + + +class PhotonicGate: + def __init__(self, wires: tuple[int, ...]): + import perceval as pcvl + + self._wires = wires + self._pcvl = pcvl + + @property + def wires(self): + return self._wires + + @property + @abstractmethod + def photonic_component(self): + """return a Perceval photonic component from the gate internal data""" + + +class PS(ParametrizedGate, PhotonicGate): + def __init__(self, wire: int, phi, trainable: bool = True): + ParametrizedGate.__init__(self, trainable=trainable) + self.nparams = 1 + self.parameter_names = [phi] + self.parameters = (phi,) + + PhotonicGate.__init__(self, (wire,)) + self.target_qubits = (wire,) + self.init_args = [wire, phi] + self.name = "Phase shifter" + self.draw_label = "PS" + self.init_kwargs = {"phi": phi, "trainable": trainable} + + @property + def photonic_component(self): + phi = self.parameters[0] + if isinstance(phi, str): + return self._pcvl.PS(self._pcvl.Parameter(phi)) + return self._pcvl.PS(phi) + + +class BS(ParametrizedGate, PhotonicGate): + def __init__(self, q0, q1, theta=np.pi / 2, trainable: bool = True): + ParametrizedGate.__init__(self, trainable=True) + self.nparams = 1 + self.parameter_names = [theta] + self.parameters = (theta,) + + PhotonicGate.__init__(self, (q0, q1)) + self.name = "Beam splitter" + self.draw_label = "X" + self.target_qubits = (q0, q1) + self.init_args = [q0, q1, theta] + self.init_kwargs = {"theta": theta, "trainable": trainable} + + @property + def wires(self): + return self.target_qubits + + @property + def photonic_component(self): + theta = self.parameters[0] + if isinstance(theta, str): + return self._pcvl.BS(self._pcvl.Parameter(theta)) + return self._pcvl.BS(theta) + + +PHOTONIC_GATE_TYPES = (PhotonicGate, H) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 7c5d1ea591..c9dca8f843 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -10,9 +10,9 @@ from tabulate import tabulate from qibo import __version__, gates -from qibo.backends import _check_backend, _Global +from qibo.backends import Modality, _check_backend, _Global from qibo.config import raise_error -from qibo.gates import ParametrizedGate +from qibo.gates import PHOTONIC_GATE_TYPES, ParametrizedGate, PhotonicGate from qibo.gates.abstract import Gate from qibo.models._openqasm import QASMParser from qibo.result import CircuitResult, QuantumState @@ -178,6 +178,7 @@ def __init__( accelerators=None, density_matrix: bool = False, wire_names: Optional[list] = None, + modality: Optional[Modality] = None, ): nqubits, wire_names = _resolve_qubits(nqubits, wire_names) self.nqubits = nqubits @@ -186,6 +187,7 @@ def __init__( "accelerators": accelerators, "density_matrix": density_matrix, "wire_names": wire_names, + "modality": modality, } self.wire_names = wire_names self.queue = _Queue(nqubits) @@ -214,6 +216,13 @@ def __init__( "Distributed circuit is not implemented for density matrices.", ) self._distributed_init(nqubits, accelerators) + self._modality: Modality = ( + modality or Modality.GATE + ) # For compatibility, the default is gate based + if Modality.is_photonic: + self._wire_type = "mode" + else: + self._wire_type = "qubit" def _distributed_init(self, nqubits, accelerators): # pragma: no cover """Distributed implementation of :class:`qibo.models.circuit.Circuit`. @@ -644,6 +653,13 @@ def add(self, gate): + f"on a circuit of {self.nqubits} qubits.", ) + if self._modality.is_photonic: + if not isinstance(gate, PHOTONIC_GATE_TYPES): + raise_error( + TypeError, + f"Gate {gate.name} cannot be used with photonic modality.", + ) + if isinstance(gate, gates.M): # The following loop is useful when two circuits are added together: # all the gates in the basis of the measure gates should not @@ -935,7 +951,7 @@ def summary(self) -> str: logs = [ f"Circuit depth = {self.depth}", f"Total number of gates = {self.ngates}", - f"Number of qubits = {self.nqubits}", + f"Number of {self._wire_type}s = {self.nqubits}", "Most common gates:", ] common_gates = self.gate_names.most_common() @@ -1080,19 +1096,30 @@ def execute(self, initial_state=None, nshots: int = 1000, **kwargs): return self._final_state backend = _Global.backend() - transpiler = _Global.transpiler() - transpiled_circuit, _ = transpiler(self) # pylint: disable=E1102 - if self.accelerators: # pragma: no cover - return backend.execute_distributed_circuit( - transpiled_circuit, initial_state, nshots - ) + if self._modality.is_photonic: + assert ( + backend.modality.is_photonic + ), "Back-end has to be photonic compatible for a photonic circuit." + assert ( + initial_state is not None + ), "Photonic modality requires an initial state." + args = [self, initial_state, nshots] - args = [transpiled_circuit, initial_state, nshots] + else: + transpiler = _Global.transpiler() + transpiled_circuit, _ = transpiler(self) # pylint: disable=E1102 - if backend.name == "hamming_weight": - weight = kwargs["weight"] - args = args[:1] + [weight] + args[1:] + if self.accelerators: # pragma: no cover + return backend.execute_distributed_circuit( + transpiled_circuit, initial_state, nshots + ) + + args = [transpiled_circuit, initial_state, nshots] + + if backend.name == "hamming_weight": + weight = kwargs["weight"] + args = args[:1] + [weight] + args[1:] return backend.execute_circuit(*args) @@ -1138,6 +1165,8 @@ def to_qasm(self): This method does not support multi-controlled gates and gates with ``torch.Tensor`` as parameters. """ + assert not self._modality.is_photonic + code = [f"// Generated by QIBO {__version__}"] code += ["OPENQASM 2.0;"] code += ['include "qelib1.inc";'] @@ -1210,6 +1239,21 @@ def from_qasm(cls, qasm_code, accelerators=None, density_matrix=False): parser = QASMParser() return parser.to_circuit(qasm_code, accelerators, density_matrix) + def to_pcvl(self): + """Generates the code to build the Perceval experiment.""" + assert self._modality.is_photonic + + code = f"pcvl.Experiment({self.nqubits})" + + for gate in self.queue: + wires = gate.wires + pcvl_class = gate.__class__.__name__ + if pcvl_class == "H": + pcvl_class = "BS.H" + code += f"\\\n .add({tuple(wires)}, {pcvl_class}({', '.join(map(str, gate.init_args[len(wires):]))}))" + + return code + def _update_draw_matrix(self, matrix, idx, gate, gate_symbol=None): """Helper method for :meth:`qibo.models.circuit.Circuit.draw`.""" if gate_symbol is None: