Skip to content

Commit f96e6ff

Browse files
Merge pull request #37 from qiboteam/pytorch_autodiff
Pytorch interface autodifferentiation
2 parents 51037fd + 920889f commit f96e6ff

15 files changed

+1586
-935
lines changed

poetry.lock

+902-558
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ tensorflow = { version = "^2.16.1", markers = "sys_platform == 'linux' or sys_pl
1818
# TODO: the marker is a temporary solution due to the lack of the tensorflow-io 0.32.0's wheels for Windows, this package is one of
1919
# the tensorflow requirements
2020
torch = { version = "^2.3.1", optional = true }
21-
qibo = {git="https://github.com/qiboteam/qibo", branch="qiboml_models_updates"}
21+
qibo = {git="https://github.com/qiboteam/qibo"}
2222
jax = "^0.4.25"
2323
jaxlib = "^0.4.25"
2424

src/qiboml/backends/jax.py

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def __init__(self):
2020

2121
self.np = jnp
2222
self.tensor_types = (jnp.ndarray, numpy.ndarray)
23+
self.matrices.np = jnp
2324

2425
def set_precision(self, precision):
2526
if precision != self.precision:
@@ -43,6 +44,13 @@ def cast(self, x, dtype=None, copy=False):
4344
return x.astype(dtype)
4445
return self.np.array(x, dtype=dtype, copy=copy)
4546

47+
def to_numpy(self, x):
48+
49+
if isinstance(x, list) or isinstance(x, tuple):
50+
return self.numpy.asarray([self.to_numpy(i) for i in x])
51+
52+
return self.numpy.asarray(x)
53+
4654
# TODO: using numpy's rng for now. Shall we use Jax's?
4755
def set_seed(self, seed):
4856
self.numpy.random.seed(seed)

src/qiboml/models/abstract.py

-45
This file was deleted.

src/qiboml/models/ansatze.py

+12-16
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
1-
from dataclasses import dataclass
1+
import random
22

33
import numpy as np
44
from qibo import Circuit, gates
55

6-
from qiboml.models.abstract import QuantumCircuitLayer
76

7+
def ReuploadingCircuit(nqubits: int, qubits: list[int] = None) -> Circuit:
8+
if qubits is None:
9+
qubits = list(range(nqubits))
810

9-
@dataclass
10-
class ReuploadingLayer(QuantumCircuitLayer):
11-
12-
def __post_init__(self):
13-
super().__post_init__()
14-
for q in self.qubits:
15-
self.circuit.add(gates.RY(q, theta=0.0))
16-
self.circuit.add(gates.RZ(q, theta=0.0))
17-
for i, q in enumerate(self.qubits[:-2]):
18-
self.circuit.add(gates.CNOT(q0=q, q1=self.qubits[i + 1]))
19-
self.circuit.add(gates.CNOT(q0=self.qubits[-1], q1=self.qubits[0]))
20-
21-
def forward(self, x: Circuit) -> Circuit:
22-
return x + self.circuit
11+
circuit = Circuit(nqubits)
12+
for q in qubits:
13+
circuit.add(gates.RY(q, theta=random.random() * np.pi, trainable=True))
14+
circuit.add(gates.RZ(q, theta=random.random() * np.pi, trainable=True))
15+
for i, q in enumerate(qubits[:-2]):
16+
circuit.add(gates.CNOT(q0=q, q1=qubits[i + 1]))
17+
circuit.add(gates.CNOT(q0=qubits[-1], q1=qubits[0]))
18+
return circuit

src/qiboml/models/decoding.py

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
from dataclasses import dataclass
2+
from typing import Union
3+
4+
from qibo import Circuit, gates
5+
from qibo.backends import Backend, _check_backend
6+
from qibo.config import raise_error
7+
from qibo.hamiltonians import Hamiltonian
8+
9+
from qiboml import ndarray
10+
11+
12+
@dataclass
13+
class QuantumDecoding:
14+
15+
nqubits: int
16+
qubits: list[int] = None
17+
nshots: int = 1000
18+
analytic: bool = True
19+
backend: Backend = None
20+
_circuit: Circuit = None
21+
22+
def __post_init__(self):
23+
if self.qubits is None:
24+
self.qubits = list(range(self.nqubits))
25+
self._circuit = Circuit(self.nqubits)
26+
self.backend = _check_backend(self.backend)
27+
self._circuit.add(gates.M(*self.qubits))
28+
29+
def __call__(self, x: Circuit) -> "CircuitResult":
30+
return self.backend.execute_circuit(x + self._circuit, nshots=self.nshots)
31+
32+
@property
33+
def circuit(
34+
self,
35+
):
36+
return self._circuit
37+
38+
def set_backend(self, backend):
39+
self.backend = backend
40+
41+
@property
42+
def output_shape(self):
43+
raise_error(NotImplementedError)
44+
45+
46+
@dataclass
47+
class Probabilities(QuantumDecoding):
48+
49+
def __call__(self, x: Circuit) -> ndarray:
50+
return super().__call__(x).probabilities()
51+
52+
@property
53+
def output_shape(self):
54+
return (1, 2**self.nqubits)
55+
56+
57+
@dataclass
58+
class Expectation(QuantumDecoding):
59+
60+
observable: Union[ndarray, Hamiltonian] = None
61+
analytic: bool = False
62+
63+
def __post_init__(self):
64+
if self.observable is None:
65+
raise_error(
66+
RuntimeError,
67+
"Please provide an observable for expectation value calculation.",
68+
)
69+
super().__post_init__()
70+
71+
def __call__(self, x: Circuit) -> ndarray:
72+
if self.analytic:
73+
return self.observable.expectation(
74+
super().__call__(x).state(),
75+
).reshape(1, 1)
76+
else:
77+
return self.observable.expectation_from_samples(
78+
super().__call__(x).frequencies(),
79+
qubit_map=self.qubits,
80+
).reshape(1, 1)
81+
82+
@property
83+
def output_shape(self):
84+
return (1, 1)
85+
86+
def set_backend(self, backend):
87+
super().set_backend(backend)
88+
self.observable.backend = backend
89+
90+
91+
@dataclass
92+
class State(QuantumDecoding):
93+
94+
def __call__(self, x: Circuit) -> ndarray:
95+
state = super().__call__(x).state()
96+
return self.backend.np.vstack(
97+
(self.backend.np.real(state), self.backend.np.imag(state))
98+
).reshape(self.output_shape)
99+
100+
@property
101+
def output_shape(self):
102+
return (2, 1, 2**self.nqubits)
103+
104+
105+
@dataclass
106+
class Samples(QuantumDecoding):
107+
108+
def __post_init__(self):
109+
super().__post_init__()
110+
self.analytic = False
111+
112+
def forward(self, x: Circuit) -> ndarray:
113+
return self.backend.cast(super().__call__(x).samples(), self.backend.precision)
114+
115+
@property
116+
def output_shape(self):
117+
return (self.nshots, len(self.qubits))

src/qiboml/models/encoding.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from abc import ABC, abstractmethod
2+
from dataclasses import dataclass
3+
4+
import numpy as np
5+
from qibo import Circuit, gates
6+
from qibo.config import raise_error
7+
8+
from qiboml import ndarray
9+
10+
11+
@dataclass
12+
class QuantumEncoding(ABC):
13+
14+
nqubits: int
15+
qubits: list[int] = None
16+
_circuit: Circuit = None
17+
18+
def __post_init__(
19+
self,
20+
):
21+
if self.qubits is None:
22+
self.qubits = list(range(self.nqubits))
23+
self._circuit = Circuit(self.nqubits)
24+
25+
@abstractmethod
26+
def __call__(self, x: ndarray) -> Circuit:
27+
pass
28+
29+
@property
30+
def circuit(
31+
self,
32+
):
33+
return self._circuit
34+
35+
36+
@dataclass
37+
class PhaseEncoding(QuantumEncoding):
38+
39+
def __post_init__(
40+
self,
41+
):
42+
super().__post_init__()
43+
for q in self.qubits:
44+
self._circuit.add(gates.RY(q, theta=0.0, trainable=False))
45+
46+
def _set_phases(self, x: ndarray):
47+
for gate, phase in zip(self._circuit.parametrized_gates, x.ravel()):
48+
gate.parameters = phase
49+
50+
def __call__(self, x: ndarray) -> Circuit:
51+
self._set_phases(x)
52+
return self._circuit
53+
54+
55+
@dataclass
56+
class BinaryEncoding(QuantumEncoding):
57+
58+
def __call__(self, x: ndarray) -> Circuit:
59+
if x.shape[-1] != len(self.qubits):
60+
raise_error(
61+
RuntimeError,
62+
f"Invalid input dimension {x.shape[-1]}, but the allocated qubits are {self.qubits}.",
63+
)
64+
circuit = self.circuit.copy()
65+
ones = np.flatnonzero(x.ravel() == 1)
66+
for bit in ones:
67+
circuit.add(gates.X(self.qubits[bit]))
68+
return circuit

0 commit comments

Comments
 (0)