From 4d5aea1db7dcf9e94613eb38f278143dcd9b8fa0 Mon Sep 17 00:00:00 2001 From: Jean Senellart Date: Fri, 27 Jun 2025 10:35:05 +0200 Subject: [PATCH 1/7] second implementation based on Eric's: - introduce modality - inherit backend from numpy backend - support matrices operation - returns result as measurement --- scripts/playground.ipynb | 331 ++++++++++++++++++ src/qibo/backends/__init__.py | 8 +- src/qibo/backends/abstract.py | 3 +- src/qibo/backends/modality.py | 24 ++ .../backends/photonic_strong_simulation.py | 78 +++++ src/qibo/gates/__init__.py | 1 + src/qibo/gates/photonic_gates.py | 54 +++ src/qibo/models/__init__.py | 1 + src/qibo/models/circuit.py | 3 +- src/qibo/models/photonic_circuit.py | 43 +++ 10 files changed, 543 insertions(+), 3 deletions(-) create mode 100644 scripts/playground.ipynb create mode 100644 src/qibo/backends/modality.py create mode 100644 src/qibo/backends/photonic_strong_simulation.py create mode 100644 src/qibo/gates/photonic_gates.py create mode 100644 src/qibo/models/photonic_circuit.py diff --git a/scripts/playground.ipynb b/scripts/playground.ipynb new file mode 100644 index 0000000000..87f7a9f058 --- /dev/null +++ b/scripts/playground.ipynb @@ -0,0 +1,331 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "ed697cdf", + "metadata": {}, + "outputs": [], + "source": [ + "from qibo import set_backend, get_backend\n", + "from qibo.gates import PS, BS\n", + "from qibo.models import PhotonicCircuit\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "107e6780", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Qibo 0.2.19|INFO|2025-06-04 00:13:28]: Using slos backend on /CPU:0\n" + ] + } + ], + "source": [ + "set_backend(\"slos\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "3f931e08", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'qibo': '0.2.19', 'perceval': '0.13.1'} Modality.PHOTONIC_CV\n" + ] + } + ], + "source": [ + "backend = get_backend()\n", + "print(backend.versions, backend.modality)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7342e8df", + "metadata": {}, + "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", + "theta=0.4 [[0.98006658+0.j 0. -0.19866933j]\n", + " [0. -0.19866933j 0.98006658+0.j ]]\n" + ] + } + ], + "source": [ + "# Parametrized gates\n", + "bs1 = BS(0, 1, np.pi/2)\n", + "print(\"theta=pi/2\", bs1.matrix())\n", + "\n", + "bs1.parameters = [0.4]\n", + "print(\"theta=0.4\", bs1.matrix())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28dfa5c1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0: ─X─PS─X────────\n", + "1: ─X────X─X────X─\n", + "2: ────────X─PS─X─\n" + ] + } + ], + "source": [ + "# Create a photonic circuit\n", + "\n", + "pc = PhotonicCircuit(3)\n", + "pc.add(BS(0, 1))\n", + "pc.add(PS(0, 0.3, trainable=True))\n", + "pc.add(BS(0, 1))\n", + "pc.add(BS(1, 2))\n", + "pc.add(PS(2, 0.8, trainable=False))\n", + "pc.add(BS(1, 2))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "45900543", + "metadata": {}, + "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: 4\n", + "Phase shifter: 2\n", + "\n", + "0: ─X─PS─X────────\n", + "1: ─X────X─X────X─\n", + "2: ────────X─PS─X─\n" + ] + } + ], + "source": [ + "print(pc.summary())\n", + "print()\n", + "print(pc.diagram())" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "69e98485", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Circuit(3)\n", + " .add(0, BS(1.5707963267948966))\n", + " .add(0, PS(0.3))\n", + " .add(0, BS(1.5707963267948966))\n", + " .add(1, BS(1.5707963267948966))\n", + " .add(2, PS(0.8))\n", + " .add(1, BS(1.5707963267948966))\n" + ] + } + ], + "source": [ + "print(pc.to_pcvl())" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "d9207ff4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MeasurementResult(qubits=3, nshots=100)\n" + ] + } + ], + "source": [ + "# circuit simulation\n", + "params = [0.]\n", + "res = pc(initial_state=(1, 0, 1), nshots=100)\n", + "print(res)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d92b37c4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[0, 2, 0],\n", + " [0, 0, 2],\n", + " [0, 2, 0],\n", + " [0, 2, 0],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 2, 0],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [1, 1, 0],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\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", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 2, 0],\n", + " [0, 0, 2],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 0, 2],\n", + " [0, 2, 0],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 2, 0],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 0, 2],\n", + " [0, 2, 0],\n", + " [0, 2, 0],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", + " [0, 2, 0],\n", + " [0, 2, 0],\n", + " [0, 0, 2],\n", + " [0, 2, 0]]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "res.samples()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74846079", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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 bcd1de1a4b..4e296e500c 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -9,8 +9,9 @@ from qibo.backends.npmatrices import NumpyMatrices from qibo.backends.numpy import NumpyBackend from qibo.config import log, raise_error +from qibo.backends.modality import Modality -QIBO_NATIVE_BACKENDS = ("numpy", "qulacs") +QIBO_NATIVE_BACKENDS = ("numpy", "qulacs", "slos") class MissingBackend(ValueError): @@ -50,6 +51,11 @@ def load(backend: str, **kwargs) -> Backend: return QulacsBackend() + if backend == "slos": + from qibo.backends.photonic_strong_simulation import SlosBackend + + return SlosBackend() + return NumpyBackend() def list_available(self) -> dict: diff --git a/src/qibo/backends/abstract.py b/src/qibo/backends/abstract.py index 3f79ac9f49..34d60609ea 100644 --- a/src/qibo/backends/abstract.py +++ b/src/qibo/backends/abstract.py @@ -2,7 +2,7 @@ from typing import Optional, Union from qibo.config import raise_error - +from .modality import Modality class Backend(abc.ABC): def __init__(self): @@ -18,6 +18,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..b26031b56e --- /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..462575bbbd --- /dev/null +++ b/src/qibo/backends/photonic_strong_simulation.py @@ -0,0 +1,78 @@ +from .abstract import Backend +from qibo.backends.numpy import NumpyBackend +from perceval import Experiment, Processor, BasicState, probs_to_samples +from perceval import __version__ as pcvl_version +import numpy as np +from qibo import __version__ +from qibo.measurements import MeasurementResult +from .modality import Modality + + +class LOQCMatrices: + def __init__(self, dtype): + import numpy as np + + self.dtype = dtype + self.np = np + + def _cast(self, x, dtype): + if isinstance(x, list): + return self.np.array(x, dtype=dtype) + return x.astype(dtype) + + def BS(self, theta): + cos = self.np.cos(theta / 2.0) + 0j + isin = -1j * self.np.sin(theta / 2.0) + return self._cast([[cos, isin], [isin, cos]], dtype=self.dtype) + + def PS(self, phi): + return self._cast([self.np.exp(1j * phi)], dtype=self.dtype) + + +class SlosBackend(NumpyBackend): + + def __init__(self): + super().__init__() + self.name = "slos" + self.matrices = LOQCMatrices(self.dtype) + self.tensor_types = np.ndarray + self.versions = {"qibo": __version__, "perceval": pcvl_version} + self.modality = Modality.PHOTONIC_CV + + def cast(self, x, dtype=None, copy=False): + if dtype is None: + dtype = self.dtype + if isinstance(x, self.tensor_types): + return x.astype(dtype, copy=copy) + elif self.is_sparse(x): + return x.astype(dtype, copy=copy) + return np.asarray(x, dtype=dtype, copy=copy if copy else None) + + def execute_circuit(self, circuit: Experiment, initial_state=None, nshots=None): + experiment = 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 + + def matrix(self, gate): + """Convert a gate to its matrix representation in the computational basis.""" + name = gate.__class__.__name__ + _matrix = getattr(self.matrices, name) + if callable(_matrix): + _matrix = _matrix(len(gate.target_qubits)) + return self.cast(_matrix, dtype=_matrix.dtype) + + def matrix_parametrized(self, gate): + """Convert a parametrized gate to its matrix representation in the computational basis.""" + name = gate.__class__.__name__ + _matrix = getattr(self.matrices, name) + _matrix = _matrix(*gate.parameters) + return self.cast(_matrix, dtype=_matrix.dtype) + + diff --git a/src/qibo/gates/__init__.py b/src/qibo/gates/__init__.py index 9dd06e6c98..fc50514e65 100644 --- a/src/qibo/gates/__init__.py +++ b/src/qibo/gates/__init__.py @@ -2,3 +2,4 @@ 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/photonic_gates.py b/src/qibo/gates/photonic_gates.py new file mode 100644 index 0000000000..6c1bd91f35 --- /dev/null +++ b/src/qibo/gates/photonic_gates.py @@ -0,0 +1,54 @@ +from .abstract import Gate, ParametrizedGate +import perceval as pcvl +import numpy as np + + +class PhotonicGate: + def __init__(self, wires: tuple[int, ...]): + self._pcvl: pcvl.AComponent + self._wires = wires + + @property + def wires(self): + return self._wires + + @property + def photonic_component(self): + return self._pcvl + + +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, + if isinstance(phi, str): + self._pcvl = pcvl.PS(pcvl.Parameter(phi)) + else: + self._pcvl = pcvl.PS(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} + + +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, + if isinstance(theta, str): + self._pcvl = pcvl.BS(pcvl.Parameter(theta)) + else: + self._pcvl = pcvl.BS(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] + diff --git a/src/qibo/models/__init__.py b/src/qibo/models/__init__.py index ef1ef78b80..8513373193 100644 --- a/src/qibo/models/__init__.py +++ b/src/qibo/models/__init__.py @@ -16,3 +16,4 @@ from qibo.models.grover import Grover from qibo.models.qft import QFT from qibo.models.variational import AAVQE, FALQON, QAOA, VQE +from .photonic_circuit import PhotonicCircuit diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 73f0e8a518..04afee3b16 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -214,6 +214,7 @@ def __init__( "Distributed circuit is not implemented for density matrices.", ) self._distributed_init(nqubits, accelerators) + self._wire_type = "qubit" def _distributed_init(self, nqubits, accelerators): # pragma: no cover """Distributed implementation of :class:`qibo.models.circuit.Circuit`. @@ -935,7 +936,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() diff --git a/src/qibo/models/photonic_circuit.py b/src/qibo/models/photonic_circuit.py new file mode 100644 index 0000000000..1f164e9cf5 --- /dev/null +++ b/src/qibo/models/photonic_circuit.py @@ -0,0 +1,43 @@ +from .circuit import Circuit +import perceval as pcvl +from qibo.gates.photonic_gates import PhotonicGate +from qibo.backends import _Global + + +class PhotonicCircuit(Circuit): + + def __init__(self, nmodes): + super().__init__(nmodes) + self._exp = pcvl.Experiment(nmodes) + self._wire_type = "mode" + + def add(self, gate): + if not isinstance(gate, PhotonicGate): + raise TypeError("only photonic gates are supported in a photonic circuit") + + super().add(gate) + self._exp.add(gate.wires, gate.photonic_component) + + def set_input_state(self, input_state: tuple[int, ...]): + if len(input_state) != self.nqubits: + raise ValueError(f"input state must have length {self.nqubits}") + for v in input_state: + if v < 0: + raise ValueError("input state must have positive value") + + self._exp.with_input(pcvl.BasicState(input_state)) + + def __call__(self, initial_state=None, nshots=1000): + self.set_input_state(initial_state) + backend = _Global.backend() + return backend.execute_circuit(self._exp, initial_state, nshots) + + def to_pcvl(self): + """Convert the circuit to a perceval experiment.""" + code = f"Circuit({self.nqubits})" + + for gate in self.queue: + wires = gate.wires + code += f"\n .add({wires[0]}, {gate.__class__.__name__}({', '.join(map(str, gate.init_args[len(wires):]))}))" + + return code \ No newline at end of file From d757476581b597620055c0f8fafaee06fbb9b696 Mon Sep 17 00:00:00 2001 From: Eric Bertasi Date: Fri, 27 Jun 2025 15:28:57 +0200 Subject: [PATCH 2/7] Remove PhotonicCircuit, Circuit now works for gate based and photonics --- .../backends/photonic_strong_simulation.py | 70 ++++++++----------- src/qibo/config.py | 2 +- src/qibo/gates/gates.py | 5 ++ src/qibo/gates/photonic_gates.py | 11 ++- src/qibo/models/__init__.py | 1 - src/qibo/models/circuit.py | 62 ++++++++++++---- src/qibo/models/photonic_circuit.py | 43 ------------ 7 files changed, 94 insertions(+), 100 deletions(-) delete mode 100644 src/qibo/models/photonic_circuit.py diff --git a/src/qibo/backends/photonic_strong_simulation.py b/src/qibo/backends/photonic_strong_simulation.py index 462575bbbd..0dd6b3a6cc 100644 --- a/src/qibo/backends/photonic_strong_simulation.py +++ b/src/qibo/backends/photonic_strong_simulation.py @@ -1,33 +1,35 @@ -from .abstract import Backend -from qibo.backends.numpy import NumpyBackend -from perceval import Experiment, Processor, BasicState, probs_to_samples +from qibo.backends.numpy import NumpyBackend, NumpyMatrices +from perceval import Experiment, Processor, BasicState, probs_to_samples, BS from perceval import __version__ as pcvl_version -import numpy as np -from qibo import __version__ +from qibo import __version__, Circuit from qibo.measurements import MeasurementResult from .modality import Modality +import numpy as np + +from qibo.gates import H, PhotonicGate class LOQCMatrices: def __init__(self, dtype): - import numpy as np - self.dtype = dtype - self.np = np def _cast(self, x, dtype): if isinstance(x, list): - return self.np.array(x, dtype=dtype) + return np.array(x, dtype=dtype) return x.astype(dtype) def BS(self, theta): - cos = self.np.cos(theta / 2.0) + 0j - isin = -1j * self.np.sin(theta / 2.0) + 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([self.np.exp(1j * phi)], dtype=self.dtype) + def H(self): + npmat = NumpyMatrices(self.dtype) + return npmat.H + class SlosBackend(NumpyBackend): @@ -39,40 +41,28 @@ def __init__(self): self.versions = {"qibo": __version__, "perceval": pcvl_version} self.modality = Modality.PHOTONIC_CV - def cast(self, x, dtype=None, copy=False): - if dtype is None: - dtype = self.dtype - if isinstance(x, self.tensor_types): - return x.astype(dtype, copy=copy) - elif self.is_sparse(x): - return x.astype(dtype, copy=copy) - return np.asarray(x, dtype=dtype, copy=copy if copy else None) + def _convert_gate(self, gate): + if isinstance(gate, PhotonicGate): + return gate._pcvl + if isinstance(gate, H): + return BS.H() - def execute_circuit(self, circuit: Experiment, initial_state=None, nshots=None): - experiment = circuit + 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 - - def matrix(self, gate): - """Convert a gate to its matrix representation in the computational basis.""" - name = gate.__class__.__name__ - _matrix = getattr(self.matrices, name) - if callable(_matrix): - _matrix = _matrix(len(gate.target_qubits)) - return self.cast(_matrix, dtype=_matrix.dtype) - - def matrix_parametrized(self, gate): - """Convert a parametrized gate to its matrix representation in the computational basis.""" - name = gate.__class__.__name__ - _matrix = getattr(self.matrices, name) - _matrix = _matrix(*gate.parameters) - return self.cast(_matrix, dtype=_matrix.dtype) + m = MeasurementResult(experiment.m) + m.register_samples([list(k) for k in samples]) + 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/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 index 6c1bd91f35..db64c562f0 100644 --- a/src/qibo/gates/photonic_gates.py +++ b/src/qibo/gates/photonic_gates.py @@ -1,4 +1,5 @@ -from .abstract import Gate, ParametrizedGate +from .abstract import ParametrizedGate +from .gates import H import perceval as pcvl import numpy as np @@ -37,7 +38,7 @@ def __init__(self, wire: int, phi, trainable: bool = True): class BS(ParametrizedGate, PhotonicGate): - def __init__(self, q0, q1, theta=np.pi / 2,trainable: bool = True): + def __init__(self, q0, q1, theta=np.pi / 2, trainable: bool = True): ParametrizedGate.__init__(self, trainable=True) self.nparams = 1 self.parameter_names = [theta] @@ -52,3 +53,9 @@ def __init__(self, q0, q1, theta=np.pi / 2,trainable: bool = True): self.target_qubits = (q0, q1) self.init_args = [q0, q1, theta] + @property + def wires(self): + return self.target_qubits + + +PHOTONIC_GATE_TYPES = (PhotonicGate, H) diff --git a/src/qibo/models/__init__.py b/src/qibo/models/__init__.py index d038b5ef43..ca2d1c5e98 100644 --- a/src/qibo/models/__init__.py +++ b/src/qibo/models/__init__.py @@ -18,4 +18,3 @@ from qibo.models.grover import Grover from qibo.models.qft import QFT from qibo.models.variational import AAVQE, FALQON, QAOA, VQE -from .photonic_circuit import PhotonicCircuit diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 1a8ddd31d3..287e537ce0 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 _check_backend, _Global, Modality from qibo.config import raise_error -from qibo.gates import ParametrizedGate +from qibo.gates import ParametrizedGate, PhotonicGate, PHOTONIC_GATE_TYPES 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,7 +216,11 @@ def __init__( "Distributed circuit is not implemented for density matrices.", ) self._distributed_init(nqubits, accelerators) - self._wire_type = "qubit" + 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`. @@ -645,6 +651,12 @@ 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 @@ -1081,19 +1093,26 @@ 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) @@ -1139,6 +1158,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";'] @@ -1211,6 +1232,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({wires[0]}, {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: diff --git a/src/qibo/models/photonic_circuit.py b/src/qibo/models/photonic_circuit.py deleted file mode 100644 index 1f164e9cf5..0000000000 --- a/src/qibo/models/photonic_circuit.py +++ /dev/null @@ -1,43 +0,0 @@ -from .circuit import Circuit -import perceval as pcvl -from qibo.gates.photonic_gates import PhotonicGate -from qibo.backends import _Global - - -class PhotonicCircuit(Circuit): - - def __init__(self, nmodes): - super().__init__(nmodes) - self._exp = pcvl.Experiment(nmodes) - self._wire_type = "mode" - - def add(self, gate): - if not isinstance(gate, PhotonicGate): - raise TypeError("only photonic gates are supported in a photonic circuit") - - super().add(gate) - self._exp.add(gate.wires, gate.photonic_component) - - def set_input_state(self, input_state: tuple[int, ...]): - if len(input_state) != self.nqubits: - raise ValueError(f"input state must have length {self.nqubits}") - for v in input_state: - if v < 0: - raise ValueError("input state must have positive value") - - self._exp.with_input(pcvl.BasicState(input_state)) - - def __call__(self, initial_state=None, nshots=1000): - self.set_input_state(initial_state) - backend = _Global.backend() - return backend.execute_circuit(self._exp, initial_state, nshots) - - def to_pcvl(self): - """Convert the circuit to a perceval experiment.""" - code = f"Circuit({self.nqubits})" - - for gate in self.queue: - wires = gate.wires - code += f"\n .add({wires[0]}, {gate.__class__.__name__}({', '.join(map(str, gate.init_args[len(wires):]))}))" - - return code \ No newline at end of file From 6bf3a8a96af65a87ccc278f5b1949ae6e1e2dbbb Mon Sep 17 00:00:00 2001 From: Eric Bertasi Date: Fri, 27 Jun 2025 15:43:59 +0200 Subject: [PATCH 3/7] Rename "slos" backend to "loqc" + clean-up --- scripts/playground.ipynb | 326 ++++++++++-------- src/qibo/backends/__init__.py | 8 +- .../backends/photonic_strong_simulation.py | 13 +- 3 files changed, 195 insertions(+), 152 deletions(-) diff --git a/scripts/playground.ipynb b/scripts/playground.ipynb index 87f7a9f058..2d00259c67 100644 --- a/scripts/playground.ipynb +++ b/scripts/playground.ipynb @@ -2,59 +2,88 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, "id": "ed697cdf", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-27T13:43:21.569153Z", + "start_time": "2025-06-27T13:43:20.520720Z" + } + }, "source": [ "from qibo import set_backend, get_backend\n", - "from qibo.gates import PS, BS\n", - "from qibo.models import PhotonicCircuit\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", - "execution_count": 2, "id": "107e6780", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-27T13:43:21.580381Z", + "start_time": "2025-06-27T13:43:21.569153Z" + } + }, + "source": "set_backend(\"loqc\")", "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "[Qibo 0.2.19|INFO|2025-06-04 00:13:28]: Using slos backend on /CPU:0\n" + "[Qibo 0.2.19|INFO|2025-06-27 15:43:21]: Using LOQC strong simulation backend on /CPU:0\n" ] } ], - "source": [ - "set_backend(\"slos\")" - ] + "execution_count": 2 }, { "cell_type": "code", - "execution_count": 15, "id": "3f931e08", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-27T13:43:21.708282Z", + "start_time": "2025-06-27T13:43:21.703625Z" + } + }, + "source": [ + "backend = get_backend()\n", + "print(backend.versions, backend.modality)" + ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{'qibo': '0.2.19', 'perceval': '0.13.1'} Modality.PHOTONIC_CV\n" + "{'qibo': '0.2.19', 'perceval': '0.13.0'} Modality.PHOTONIC_CV\n" ] } ], - "source": [ - "backend = get_backend()\n", - "print(backend.versions, backend.modality)" - ] + "execution_count": 3 }, { "cell_type": "code", - "execution_count": null, "id": "7342e8df", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-27T13:43:21.719398Z", + "start_time": "2025-06-27T13:43:21.714315Z" + } + }, + "source": [ + "# Retrieve matrix for parametrized gates\n", + "bs1 = BS(0, 1, np.pi/2)\n", + "print(\"theta=pi/2\", bs1.matrix())\n", + "\n", + "bs1.parameters = [0.4]\n", + "print(\"theta=0.4\", bs1.matrix())\n", + "\n", + "# Through the backend too\n", + "print(\"Phase shifter, phi=0.3\", backend.matrix(PS(0, 0.3)))" + ], "outputs": [ { "name": "stdout", @@ -63,52 +92,50 @@ "theta=pi/2 [[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. -0.19866933j 0.98006658+0.j ]]\n", + "Phase shifter, phi=0.3 [-0.41614684+0.90929743j]\n" ] } ], - "source": [ - "# Parametrized gates\n", - "bs1 = BS(0, 1, np.pi/2)\n", - "print(\"theta=pi/2\", bs1.matrix())\n", - "\n", - "bs1.parameters = [0.4]\n", - "print(\"theta=0.4\", bs1.matrix())" - ] + "execution_count": 4 }, { "cell_type": "code", - "execution_count": null, "id": "28dfa5c1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0: ─X─PS─X────────\n", - "1: ─X────X─X────X─\n", - "2: ────────X─PS─X─\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-27T13:43:21.731734Z", + "start_time": "2025-06-27T13:43:21.724439Z" } - ], + }, "source": [ "# Create a photonic circuit\n", "\n", - "pc = PhotonicCircuit(3)\n", - "pc.add(BS(0, 1))\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, 1))\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", - "execution_count": 17, "id": "45900543", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-27T13:43:21.763446Z", + "start_time": "2025-06-27T13:43:21.758464Z" + } + }, + "source": [ + "print(pc.summary())\n", + "print()\n", + "print(pc.diagram())" + ], "outputs": [ { "name": "stdout", @@ -118,33 +145,37 @@ "Total number of gates = 6\n", "Number of modes = 3\n", "Most common gates:\n", - "Beam splitter: 4\n", + "Beam splitter: 3\n", "Phase shifter: 2\n", + "h: 1\n", "\n", - "0: ─X─PS─X────────\n", - "1: ─X────X─X────X─\n", + "0: ─H─PS─X────────\n", + "1: ──────X─X────X─\n", "2: ────────X─PS─X─\n" ] } ], - "source": [ - "print(pc.summary())\n", - "print()\n", - "print(pc.diagram())" - ] + "execution_count": 6 }, { "cell_type": "code", - "execution_count": 7, "id": "69e98485", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-27T13:43:21.778355Z", + "start_time": "2025-06-27T13:43:21.768824Z" + } + }, + "source": [ + "print(pc.to_pcvl())" + ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Circuit(3)\n", - " .add(0, BS(1.5707963267948966))\n", + "pcvl.Experiment(3)\n", + " .add(0, BS.H())\n", " .add(0, PS(0.3))\n", " .add(0, BS(1.5707963267948966))\n", " .add(1, BS(1.5707963267948966))\n", @@ -153,15 +184,23 @@ ] } ], - "source": [ - "print(pc.to_pcvl())" - ] + "execution_count": 7 }, { "cell_type": "code", - "execution_count": 18, "id": "d9207ff4", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-27T13:43:21.796712Z", + "start_time": "2025-06-27T13:43:21.784029Z" + } + }, + "source": [ + "# circuit simulation\n", + "params = [0.]\n", + "res = pc(initial_state=(1, 0, 1), nshots=100)\n", + "print(res)" + ], "outputs": [ { "name": "stdout", @@ -171,140 +210,145 @@ ] } ], - "source": [ - "# circuit simulation\n", - "params = [0.]\n", - "res = pc(initial_state=(1, 0, 1), nshots=100)\n", - "print(res)" - ] + "execution_count": 8 }, { "cell_type": "code", - "execution_count": 19, "id": "d92b37c4", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-27T13:43:21.812991Z", + "start_time": "2025-06-27T13:43:21.805645Z" + } + }, + "source": [ + "res.samples()" + ], "outputs": [ { "data": { "text/plain": [ - "[[0, 2, 0],\n", - " [0, 0, 2],\n", - " [0, 2, 0],\n", - " [0, 2, 0],\n", - " [0, 2, 0],\n", - " [0, 1, 1],\n", - " [0, 0, 2],\n", + "[[0, 1, 1],\n", + " [1, 1, 0],\n", " [0, 2, 0],\n", + " [1, 1, 0],\n", + " [1, 0, 1],\n", " [0, 0, 2],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [0, 1, 1],\n", - " [0, 1, 1],\n", - " [0, 0, 2],\n", - " [0, 1, 1],\n", - " [0, 1, 1],\n", - " [0, 1, 1],\n", - " [0, 0, 2],\n", - " [0, 1, 1],\n", + " [1, 0, 1],\n", + " [1, 0, 1],\n", " [0, 2, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [0, 1, 1],\n", + " [1, 0, 1],\n", " [0, 0, 2],\n", - " [0, 1, 1],\n", - " [0, 0, 2],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [0, 1, 1],\n", " [0, 2, 0],\n", " [0, 1, 1],\n", - " [0, 0, 2],\n", " [0, 2, 0],\n", + " [1, 1, 0],\n", " [0, 1, 1],\n", " [0, 0, 2],\n", - " [0, 1, 1],\n", + " [1, 1, 0],\n", " [0, 0, 2],\n", - " [0, 1, 1],\n", - " [0, 2, 0],\n", - " [0, 1, 1],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 0, 1],\n", " [0, 1, 1],\n", " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [0, 1, 1],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [0, 0, 2],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [0, 2, 0],\n", - " [0, 1, 1],\n", - " [0, 1, 1],\n", - " [0, 1, 1],\n", - " [0, 1, 1],\n", - " [0, 0, 2],\n", - " [0, 0, 2],\n", - " [0, 1, 1],\n", + " [1, 0, 1],\n", " [0, 1, 1],\n", " [0, 0, 2],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 0, 1],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [0, 1, 1],\n", - " [0, 1, 1],\n", - " [0, 1, 1],\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", - " [0, 1, 1],\n", - " [0, 1, 1],\n", - " [0, 0, 2],\n", - " [0, 1, 1],\n", - " [0, 1, 1],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [0, 2, 0],\n", - " [0, 1, 1],\n", " [0, 2, 0],\n", " [0, 1, 1],\n", " [0, 2, 0],\n", + " [1, 1, 0],\n", + " [1, 0, 1],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [0, 1, 1],\n", - " [0, 0, 2],\n", - " [0, 1, 1],\n", - " [0, 0, 2],\n", - " [0, 1, 1],\n", - " [0, 2, 0],\n", - " [0, 2, 0],\n", - " [0, 0, 2],\n", " [0, 1, 1],\n", + " [1, 1, 0],\n", " [0, 2, 0],\n", - " [0, 0, 2],\n", - " [0, 2, 0],\n", - " [0, 2, 0],\n", + " [1, 1, 0],\n", + " [1, 0, 1],\n", " [0, 1, 1],\n", - " [0, 2, 0],\n", - " [0, 2, 0],\n", - " [0, 2, 0],\n", " [0, 1, 1],\n", - " [0, 0, 2],\n", - " [0, 2, 0],\n", - " [0, 2, 0],\n", - " [0, 2, 0],\n", " [0, 1, 1],\n", " [0, 1, 1],\n", - " [0, 2, 0],\n", + " [1, 1, 0],\n", " [0, 1, 1],\n", + " [1, 1, 0],\n", " [0, 1, 1],\n", + " [1, 1, 0],\n", " [0, 1, 1],\n", " [0, 1, 1],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [0, 2, 0],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [0, 2, 0],\n", - " [0, 0, 2],\n", - " [0, 2, 0]]" + " [1, 1, 0]]" ] }, - "execution_count": 19, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], - "source": [ - "res.samples()" - ] + "execution_count": 9 }, { "cell_type": "code", - "execution_count": null, "id": "74846079", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-27T13:43:21.822317Z", + "start_time": "2025-06-27T13:43:21.820089Z" + } + }, + "source": [], "outputs": [], - "source": [] + "execution_count": null } ], "metadata": { diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index 570f232033..ce49b4445a 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -12,7 +12,7 @@ from qibo.config import log, raise_error from qibo.backends.modality import Modality -QIBO_NATIVE_BACKENDS = ("numpy", "qulacs", "slos") +QIBO_NATIVE_BACKENDS = ("numpy", "qulacs", "loqc") class MissingBackend(ValueError): @@ -59,10 +59,10 @@ def load(backend: str, **kwargs) -> Backend: backend_obj = QulacsBackend() backend_obj.set_dtype(dtype=dtype) - if backend == "slos": - from qibo.backends.photonic_strong_simulation import SlosBackend + if backend == "loqc": + from qibo.backends.photonic_strong_simulation import LoqcStrongBackend - return SlosBackend() + return LoqcStrongBackend() backend_obj = NumpyBackend() backend_obj.set_dtype(dtype=dtype) diff --git a/src/qibo/backends/photonic_strong_simulation.py b/src/qibo/backends/photonic_strong_simulation.py index 0dd6b3a6cc..caf8672d23 100644 --- a/src/qibo/backends/photonic_strong_simulation.py +++ b/src/qibo/backends/photonic_strong_simulation.py @@ -24,26 +24,27 @@ def BS(self, theta): return self._cast([[cos, isin], [isin, cos]], dtype=self.dtype) def PS(self, phi): - return self._cast([self.np.exp(1j * phi)], dtype=self.dtype) + return self._cast([np.exp(1j * phi)], dtype=self.dtype) def H(self): npmat = NumpyMatrices(self.dtype) return npmat.H -class SlosBackend(NumpyBackend): +class LoqcStrongBackend(NumpyBackend): def __init__(self): super().__init__() - self.name = "slos" + 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 - def _convert_gate(self, gate): + @staticmethod + def _convert_gate(gate): if isinstance(gate, PhotonicGate): - return gate._pcvl + return gate.photonic_component if isinstance(gate, H): return BS.H() @@ -64,5 +65,3 @@ def execute_circuit(self, circuit: Circuit, initial_state=None, nshots=None): m = MeasurementResult(experiment.m) m.register_samples([list(k) for k in samples]) return m - - From 8fb714db7eca5b6928a3371203ba0553ccd2bffd Mon Sep 17 00:00:00 2001 From: Eric Bertasi Date: Fri, 27 Jun 2025 16:07:51 +0200 Subject: [PATCH 4/7] Introduce QuandelaCloudBackend to run computations on Quandela platforms --- scripts/playground.ipynb | 130 ++++++++++-------- src/qibo/backends/__init__.py | 7 +- .../backends/photonic_strong_simulation.py | 30 +++- 3 files changed, 110 insertions(+), 57 deletions(-) diff --git a/scripts/playground.ipynb b/scripts/playground.ipynb index 2d00259c67..668730b0cd 100644 --- a/scripts/playground.ipynb +++ b/scripts/playground.ipynb @@ -5,8 +5,8 @@ "id": "ed697cdf", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T13:43:21.569153Z", - "start_time": "2025-06-27T13:43:20.520720Z" + "end_time": "2025-06-27T14:03:42.791971Z", + "start_time": "2025-06-27T14:03:41.396925Z" } }, "source": [ @@ -24,8 +24,8 @@ "id": "107e6780", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T13:43:21.580381Z", - "start_time": "2025-06-27T13:43:21.569153Z" + "end_time": "2025-06-27T14:03:42.999261Z", + "start_time": "2025-06-27T14:03:42.984853Z" } }, "source": "set_backend(\"loqc\")", @@ -34,7 +34,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "[Qibo 0.2.19|INFO|2025-06-27 15:43:21]: Using LOQC strong simulation backend on /CPU:0\n" + "[Qibo 0.2.19|INFO|2025-06-27 16:03:42]: Using LOQC strong simulation backend on /CPU:0\n" ] } ], @@ -45,8 +45,8 @@ "id": "3f931e08", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T13:43:21.708282Z", - "start_time": "2025-06-27T13:43:21.703625Z" + "end_time": "2025-06-27T14:03:43.361503Z", + "start_time": "2025-06-27T14:03:43.356573Z" } }, "source": [ @@ -69,8 +69,8 @@ "id": "7342e8df", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T13:43:21.719398Z", - "start_time": "2025-06-27T13:43:21.714315Z" + "end_time": "2025-06-27T14:03:43.383232Z", + "start_time": "2025-06-27T14:03:43.368392Z" } }, "source": [ @@ -104,8 +104,8 @@ "id": "28dfa5c1", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T13:43:21.731734Z", - "start_time": "2025-06-27T13:43:21.724439Z" + "end_time": "2025-06-27T14:03:43.403158Z", + "start_time": "2025-06-27T14:03:43.395110Z" } }, "source": [ @@ -127,8 +127,8 @@ "id": "45900543", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T13:43:21.763446Z", - "start_time": "2025-06-27T13:43:21.758464Z" + "end_time": "2025-06-27T14:03:43.412662Z", + "start_time": "2025-06-27T14:03:43.408302Z" } }, "source": [ @@ -162,8 +162,8 @@ "id": "69e98485", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T13:43:21.778355Z", - "start_time": "2025-06-27T13:43:21.768824Z" + "end_time": "2025-06-27T14:03:43.432179Z", + "start_time": "2025-06-27T14:03:43.426773Z" } }, "source": [ @@ -191,8 +191,8 @@ "id": "d9207ff4", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T13:43:21.796712Z", - "start_time": "2025-06-27T13:43:21.784029Z" + "end_time": "2025-06-27T14:03:43.456369Z", + "start_time": "2025-06-27T14:03:43.442768Z" } }, "source": [ @@ -217,8 +217,8 @@ "id": "d92b37c4", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T13:43:21.812991Z", - "start_time": "2025-06-27T13:43:21.805645Z" + "end_time": "2025-06-27T14:03:43.547657Z", + "start_time": "2025-06-27T14:03:43.537818Z" } }, "source": [ @@ -228,105 +228,105 @@ { "data": { "text/plain": [ - "[[0, 1, 1],\n", + "[[0, 0, 2],\n", " [1, 1, 0],\n", - " [0, 2, 0],\n", - " [1, 1, 0],\n", - " [1, 0, 1],\n", " [0, 0, 2],\n", " [1, 1, 0],\n", + " [0, 1, 1],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", + " [0, 2, 0],\n", + " [0, 1, 1],\n", " [1, 1, 0],\n", + " [0, 0, 2],\n", + " [0, 0, 2],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", - " [0, 1, 1],\n", " [1, 0, 1],\n", + " [1, 1, 0],\n", " [1, 0, 1],\n", - " [0, 2, 0],\n", + " [0, 1, 1],\n", + " [0, 1, 1],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", - " [0, 1, 1],\n", " [1, 0, 1],\n", - " [0, 0, 2],\n", - " [1, 1, 0],\n", + " [0, 2, 0],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", - " [0, 1, 1],\n", " [0, 2, 0],\n", - " [0, 1, 1],\n", + " [1, 1, 0],\n", " [0, 2, 0],\n", " [1, 1, 0],\n", - " [0, 1, 1],\n", " [0, 0, 2],\n", - " [1, 1, 0],\n", + " [0, 0, 2],\n", " [0, 0, 2],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", - " [1, 0, 1],\n", - " [0, 1, 1],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", - " [0, 1, 1],\n", " [1, 1, 0],\n", + " [0, 2, 0],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", + " [0, 1, 1],\n", " [1, 1, 0],\n", " [0, 0, 2],\n", + " [0, 1, 1],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", - " [0, 2, 0],\n", " [1, 0, 1],\n", " [0, 1, 1],\n", - " [0, 0, 2],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", + " [0, 1, 1],\n", " [1, 0, 1],\n", - " [1, 1, 0],\n", - " [1, 1, 0],\n", " [0, 1, 1],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", - " [1, 1, 0],\n", - " [0, 2, 0],\n", " [0, 2, 0],\n", " [0, 1, 1],\n", - " [0, 2, 0],\n", - " [1, 1, 0],\n", - " [1, 0, 1],\n", " [1, 1, 0],\n", + " [0, 2, 0],\n", " [1, 1, 0],\n", " [0, 1, 1],\n", " [0, 1, 1],\n", + " [1, 0, 1],\n", + " [1, 1, 0],\n", " [1, 1, 0],\n", " [0, 2, 0],\n", + " [1, 0, 1],\n", " [1, 1, 0],\n", " [1, 0, 1],\n", + " [1, 1, 0],\n", + " [0, 2, 0],\n", + " [1, 0, 1],\n", + " [0, 2, 0],\n", " [0, 1, 1],\n", " [0, 1, 1],\n", " [0, 1, 1],\n", - " [0, 1, 1],\n", + " [0, 0, 2],\n", " [1, 1, 0],\n", - " [0, 1, 1],\n", " [1, 1, 0],\n", - " [0, 1, 1],\n", + " [0, 0, 2],\n", " [1, 1, 0],\n", " [0, 1, 1],\n", - " [0, 1, 1],\n", - " [1, 1, 0],\n", " [1, 1, 0],\n", + " [0, 2, 0],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", " [0, 2, 0],\n", " [1, 1, 0],\n", " [1, 1, 0],\n", - " [0, 2, 0],\n", + " [1, 1, 0],\n", + " [0, 1, 1],\n", + " [1, 1, 0],\n", + " [1, 1, 0],\n", " [1, 1, 0]]" ] }, @@ -342,13 +342,33 @@ "id": "74846079", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T13:43:21.822317Z", - "start_time": "2025-06-27T13:43:21.820089Z" + "end_time": "2025-06-27T14:03:49.885702Z", + "start_time": "2025-06-27T14:03:43.621635Z" } }, - "source": [], - "outputs": [], - "execution_count": null + "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", + "print(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-27 16:03:43]: Using Quandela Cloud backend on /CPU:0\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0], [1, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [0, 0, 1], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 1], [0, 1, 0], [0, 0, 1], [0, 0, 1], [0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 0, 1], [0, 1, 0], [0, 0, 1], [1, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [1, 0, 0], [0, 0, 1], [0, 0, 1], [0, 1, 0], [0, 1, 0], [1, 0, 1], [1, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 1], [1, 0, 0], [0, 0, 1], [1, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0]]\n" + ] + } + ], + "execution_count": 10 } ], "metadata": { diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index ce49b4445a..e6001f9588 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -12,7 +12,7 @@ from qibo.config import log, raise_error from qibo.backends.modality import Modality -QIBO_NATIVE_BACKENDS = ("numpy", "qulacs", "loqc") +QIBO_NATIVE_BACKENDS = ("numpy", "qulacs", "loqc", "quandela") class MissingBackend(ValueError): @@ -64,6 +64,11 @@ def load(backend: str, **kwargs) -> Backend: 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/photonic_strong_simulation.py b/src/qibo/backends/photonic_strong_simulation.py index caf8672d23..ca27f1cdab 100644 --- a/src/qibo/backends/photonic_strong_simulation.py +++ b/src/qibo/backends/photonic_strong_simulation.py @@ -1,5 +1,6 @@ from qibo.backends.numpy import NumpyBackend, NumpyMatrices -from perceval import Experiment, Processor, BasicState, probs_to_samples, BS +from perceval import Experiment, Processor, BasicState, probs_to_samples, BS, RemoteProcessor +from perceval.algorithm import Sampler from perceval import __version__ as pcvl_version from qibo import __version__, Circuit from qibo.measurements import MeasurementResult @@ -65,3 +66,30 @@ def execute_circuit(self, circuit: Circuit, initial_state=None, nshots=None): 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 From d7f92bff79e2272a101b5b03547d1cdac854a0ca Mon Sep 17 00:00:00 2001 From: Eric Bertasi Date: Fri, 27 Jun 2025 16:28:39 +0200 Subject: [PATCH 5/7] Re-add perceval-quandela dependency (should be an extra) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) 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 From 0e6c1dc1fd7b387de7567c49edf8cd5608a88111 Mon Sep 17 00:00:00 2001 From: Eric Bertasi Date: Mon, 30 Jun 2025 16:18:09 +0200 Subject: [PATCH 6/7] Change when perceval components are generated in PhotonicGates --- scripts/playground.ipynb | 290 +++++++++++++++++++++---------- src/qibo/gates/photonic_gates.py | 35 ++-- src/qibo/models/circuit.py | 2 +- 3 files changed, 223 insertions(+), 104 deletions(-) diff --git a/scripts/playground.ipynb b/scripts/playground.ipynb index 668730b0cd..df2fdb98da 100644 --- a/scripts/playground.ipynb +++ b/scripts/playground.ipynb @@ -5,8 +5,8 @@ "id": "ed697cdf", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T14:03:42.791971Z", - "start_time": "2025-06-27T14:03:41.396925Z" + "end_time": "2025-06-30T14:16:42.504441Z", + "start_time": "2025-06-30T14:16:41.539299Z" } }, "source": [ @@ -24,8 +24,8 @@ "id": "107e6780", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T14:03:42.999261Z", - "start_time": "2025-06-27T14:03:42.984853Z" + "end_time": "2025-06-30T14:16:43.042382Z", + "start_time": "2025-06-30T14:16:42.510792Z" } }, "source": "set_backend(\"loqc\")", @@ -34,7 +34,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "[Qibo 0.2.19|INFO|2025-06-27 16:03:42]: Using LOQC strong simulation backend on /CPU:0\n" + "[Qibo 0.2.19|INFO|2025-06-30 16:16:43]: Using LOQC strong simulation backend on /CPU:0\n" ] } ], @@ -45,8 +45,8 @@ "id": "3f931e08", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T14:03:43.361503Z", - "start_time": "2025-06-27T14:03:43.356573Z" + "end_time": "2025-06-30T14:16:43.455276Z", + "start_time": "2025-06-30T14:16:43.450564Z" } }, "source": [ @@ -69,17 +69,19 @@ "id": "7342e8df", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T14:03:43.383232Z", - "start_time": "2025-06-27T14:03:43.368392Z" + "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)))" @@ -91,8 +93,12 @@ "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" ] } @@ -104,8 +110,8 @@ "id": "28dfa5c1", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T14:03:43.403158Z", - "start_time": "2025-06-27T14:03:43.395110Z" + "end_time": "2025-06-30T14:16:43.483826Z", + "start_time": "2025-06-30T14:16:43.479508Z" } }, "source": [ @@ -114,7 +120,7 @@ "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, 1))\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" @@ -127,8 +133,8 @@ "id": "45900543", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T14:03:43.412662Z", - "start_time": "2025-06-27T14:03:43.408302Z" + "end_time": "2025-06-30T14:16:43.508948Z", + "start_time": "2025-06-30T14:16:43.503128Z" } }, "source": [ @@ -150,8 +156,8 @@ "h: 1\n", "\n", "0: ─H─PS─X────────\n", - "1: ──────X─X────X─\n", - "2: ────────X─PS─X─\n" + "1: ──────|─X────X─\n", + "2: ──────X─X─PS─X─\n" ] } ], @@ -162,8 +168,8 @@ "id": "69e98485", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T14:03:43.432179Z", - "start_time": "2025-06-27T14:03:43.426773Z" + "end_time": "2025-06-30T14:16:43.611185Z", + "start_time": "2025-06-30T14:16:43.590177Z" } }, "source": [ @@ -174,13 +180,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "pcvl.Experiment(3)\n", - " .add(0, BS.H())\n", - " .add(0, PS(0.3))\n", - " .add(0, BS(1.5707963267948966))\n", - " .add(1, BS(1.5707963267948966))\n", - " .add(2, PS(0.8))\n", - " .add(1, BS(1.5707963267948966))\n" + "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" ] } ], @@ -191,8 +197,8 @@ "id": "d9207ff4", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T14:03:43.456369Z", - "start_time": "2025-06-27T14:03:43.442768Z" + "end_time": "2025-06-30T14:16:43.637014Z", + "start_time": "2025-06-30T14:16:43.621759Z" } }, "source": [ @@ -217,8 +223,8 @@ "id": "d92b37c4", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T14:03:43.547657Z", - "start_time": "2025-06-27T14:03:43.537818Z" + "end_time": "2025-06-30T14:16:43.679822Z", + "start_time": "2025-06-30T14:16:43.669093Z" } }, "source": [ @@ -228,105 +234,105 @@ { "data": { "text/plain": [ - "[[0, 0, 2],\n", - " [1, 1, 0],\n", + "[[0, 1, 1],\n", + " [2, 0, 0],\n", + " [1, 0, 1],\n", " [0, 0, 2],\n", " [1, 1, 0],\n", - " [0, 1, 1],\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", - " [1, 1, 0],\n", - " [1, 0, 1],\n", - " [1, 1, 0],\n", - " [1, 0, 1],\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", - " [1, 0, 1],\n", - " [0, 2, 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, 2, 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", - " [1, 1, 0],\n", - " [0, 2, 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", - " [1, 1, 0],\n", - " [1, 1, 0],\n", - " [1, 0, 1],\n", " [0, 1, 1],\n", - " [1, 1, 0],\n", - " [1, 1, 0],\n", - " [1, 1, 0],\n", + " [0, 2, 0],\n", + " [0, 0, 2],\n", + " [0, 0, 2],\n", + " [0, 0, 2],\n", " [0, 1, 1],\n", - " [1, 0, 1],\n", " [0, 1, 1],\n", - " [1, 1, 0],\n", - " [1, 1, 0],\n", - " [0, 2, 0],\n", + " [2, 0, 0],\n", " [0, 1, 1],\n", - " [1, 1, 0],\n", - " [0, 2, 0],\n", - " [1, 1, 0],\n", " [0, 1, 1],\n", " [0, 1, 1],\n", - " [1, 0, 1],\n", - " [1, 1, 0],\n", - " [1, 1, 0],\n", - " [0, 2, 0],\n", - " [1, 0, 1],\n", - " [1, 1, 0],\n", - " [1, 0, 1],\n", - " [1, 1, 0],\n", " [0, 2, 0],\n", - " [1, 0, 1],\n", - " [0, 2, 0],\n", - " [0, 1, 1],\n", " [0, 1, 1],\n", " [0, 1, 1],\n", - " [0, 0, 2],\n", - " [1, 1, 0],\n", + " [2, 0, 0],\n", " [1, 1, 0],\n", - " [0, 0, 2],\n", " [1, 1, 0],\n", " [0, 1, 1],\n", - " [1, 1, 0],\n", - " [0, 2, 0],\n", - " [1, 1, 0],\n", - " [1, 1, 0],\n", + " [0, 1, 1],\n", + " [2, 0, 0],\n", + " [2, 0, 0],\n", + " [1, 0, 1],\n", " [0, 2, 0],\n", - " [1, 1, 0],\n", - " [1, 1, 0],\n", - " [1, 1, 0],\n", + " [2, 0, 0],\n", " [0, 1, 1],\n", - " [1, 1, 0],\n", - " [1, 1, 0],\n", " [1, 1, 0]]" ] }, @@ -342,30 +348,132 @@ "id": "74846079", "metadata": { "ExecuteTime": { - "end_time": "2025-06-27T14:03:49.885702Z", - "start_time": "2025-06-27T14:03:43.621635Z" + "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", + "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", - "print(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)" + "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-27 16:03:43]: Using Quandela Cloud backend on /CPU:0\n" + "[Qibo 0.2.19|INFO|2025-06-30 16:16:43]: Using Quandela Cloud backend on /CPU:0\n" ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0], [1, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [0, 0, 1], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [1, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 1], [0, 1, 0], [0, 0, 1], [0, 0, 1], [0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 0, 1], [0, 1, 0], [0, 0, 1], [1, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [1, 0, 0], [0, 0, 1], [0, 0, 1], [0, 1, 0], [0, 1, 0], [1, 0, 1], [1, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 1], [1, 0, 0], [0, 0, 1], [1, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 1, 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 diff --git a/src/qibo/gates/photonic_gates.py b/src/qibo/gates/photonic_gates.py index db64c562f0..a5fe36ec77 100644 --- a/src/qibo/gates/photonic_gates.py +++ b/src/qibo/gates/photonic_gates.py @@ -1,21 +1,24 @@ +from abc import abstractmethod +import numpy as np + from .abstract import ParametrizedGate from .gates import H -import perceval as pcvl -import numpy as np class PhotonicGate: def __init__(self, wires: tuple[int, ...]): - self._pcvl: pcvl.AComponent + import perceval as pcvl self._wires = wires + self._pcvl = pcvl @property def wires(self): return self._wires @property + @abstractmethod def photonic_component(self): - return self._pcvl + """return a Perceval photonic component from the gate internal data""" class PS(ParametrizedGate, PhotonicGate): @@ -24,10 +27,6 @@ def __init__(self, wire: int, phi, trainable: bool = True): self.nparams = 1 self.parameter_names = [phi] self.parameters = phi, - if isinstance(phi, str): - self._pcvl = pcvl.PS(pcvl.Parameter(phi)) - else: - self._pcvl = pcvl.PS(phi) PhotonicGate.__init__(self, (wire,)) self.target_qubits = (wire,) @@ -36,6 +35,13 @@ def __init__(self, wire: int, phi, trainable: bool = True): 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): @@ -43,19 +49,24 @@ def __init__(self, q0, q1, theta=np.pi / 2, trainable: bool = True): self.nparams = 1 self.parameter_names = [theta] self.parameters = theta, - if isinstance(theta, str): - self._pcvl = pcvl.BS(pcvl.Parameter(theta)) - else: - self._pcvl = pcvl.BS(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 287e537ce0..5b5e6b5f70 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -1243,7 +1243,7 @@ def to_pcvl(self): pcvl_class = gate.__class__.__name__ if pcvl_class == "H": pcvl_class = "BS.H" - code += f"\n .add({wires[0]}, {pcvl_class}({', '.join(map(str, gate.init_args[len(wires):]))}))" + code += f"\\\n .add({tuple(wires)}, {pcvl_class}({', '.join(map(str, gate.init_args[len(wires):]))}))" return code From 6d2c55e3b2691b9323e1dda55853b9a4385de211 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 14:33:44 +0000 Subject: [PATCH 7/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/backends/__init__.py | 2 +- src/qibo/backends/abstract.py | 2 ++ src/qibo/backends/modality.py | 2 +- .../backends/photonic_strong_simulation.py | 30 ++++++++++++------- src/qibo/gates/__init__.py | 1 + src/qibo/gates/photonic_gates.py | 6 ++-- src/qibo/models/circuit.py | 23 +++++++++----- 7 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index e6001f9588..30f960d155 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -7,10 +7,10 @@ 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 -from qibo.backends.modality import Modality QIBO_NATIVE_BACKENDS = ("numpy", "qulacs", "loqc", "quandela") diff --git a/src/qibo/backends/abstract.py b/src/qibo/backends/abstract.py index fecab2f832..23963709ca 100644 --- a/src/qibo/backends/abstract.py +++ b/src/qibo/backends/abstract.py @@ -2,8 +2,10 @@ from typing import Optional, Union from qibo.config import raise_error + from .modality import Modality + class Backend(abc.ABC): def __init__(self): super().__init__() diff --git a/src/qibo/backends/modality.py b/src/qibo/backends/modality.py index b26031b56e..6e32e36772 100644 --- a/src/qibo/backends/modality.py +++ b/src/qibo/backends/modality.py @@ -1,5 +1,6 @@ from enum import Enum + class Modality(Enum): GATE = "gate" PHOTONIC = "photonic" @@ -21,4 +22,3 @@ def is_photonic(self) -> bool: 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 index ca27f1cdab..cf63651beb 100644 --- a/src/qibo/backends/photonic_strong_simulation.py +++ b/src/qibo/backends/photonic_strong_simulation.py @@ -1,13 +1,23 @@ -from qibo.backends.numpy import NumpyBackend, NumpyMatrices -from perceval import Experiment, Processor, BasicState, probs_to_samples, BS, RemoteProcessor -from perceval.algorithm import Sampler -from perceval import __version__ as pcvl_version -from qibo import __version__, Circuit -from qibo.measurements import MeasurementResult -from .modality import Modality 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: @@ -18,12 +28,12 @@ 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) @@ -62,7 +72,7 @@ def execute_circuit(self, circuit: Circuit, initial_state=None, nshots=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 diff --git a/src/qibo/gates/__init__.py b/src/qibo/gates/__init__.py index fc50514e65..111fa108a4 100644 --- a/src/qibo/gates/__init__.py +++ b/src/qibo/gates/__init__.py @@ -2,4 +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/photonic_gates.py b/src/qibo/gates/photonic_gates.py index a5fe36ec77..eef4ed6c8f 100644 --- a/src/qibo/gates/photonic_gates.py +++ b/src/qibo/gates/photonic_gates.py @@ -1,4 +1,5 @@ from abc import abstractmethod + import numpy as np from .abstract import ParametrizedGate @@ -8,6 +9,7 @@ class PhotonicGate: def __init__(self, wires: tuple[int, ...]): import perceval as pcvl + self._wires = wires self._pcvl = pcvl @@ -26,7 +28,7 @@ def __init__(self, wire: int, phi, trainable: bool = True): ParametrizedGate.__init__(self, trainable=trainable) self.nparams = 1 self.parameter_names = [phi] - self.parameters = phi, + self.parameters = (phi,) PhotonicGate.__init__(self, (wire,)) self.target_qubits = (wire,) @@ -48,7 +50,7 @@ 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, + self.parameters = (theta,) PhotonicGate.__init__(self, (q0, q1)) self.name = "Beam splitter" diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 5b5e6b5f70..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, Modality +from qibo.backends import Modality, _check_backend, _Global from qibo.config import raise_error -from qibo.gates import ParametrizedGate, PhotonicGate, PHOTONIC_GATE_TYPES +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,7 +178,7 @@ def __init__( accelerators=None, density_matrix: bool = False, wire_names: Optional[list] = None, - modality: Optional[Modality] = None + modality: Optional[Modality] = None, ): nqubits, wire_names = _resolve_qubits(nqubits, wire_names) self.nqubits = nqubits @@ -187,7 +187,7 @@ def __init__( "accelerators": accelerators, "density_matrix": density_matrix, "wire_names": wire_names, - "modality": modality + "modality": modality, } self.wire_names = wire_names self.queue = _Queue(nqubits) @@ -216,7 +216,9 @@ 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 + self._modality: Modality = ( + modality or Modality.GATE + ) # For compatibility, the default is gate based if Modality.is_photonic: self._wire_type = "mode" else: @@ -654,7 +656,8 @@ def add(self, gate): 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." + TypeError, + f"Gate {gate.name} cannot be used with photonic modality.", ) if isinstance(gate, gates.M): @@ -1095,8 +1098,12 @@ def execute(self, initial_state=None, nshots: int = 1000, **kwargs): backend = _Global.backend() 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." + 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] else: