-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implements the structure of a general qiboml
model
#20
Changes from 66 commits
420ac26
7e828ff
0e8656b
2c79d18
e015465
12c53e8
aa38656
06d4a16
a6ac851
3c09554
82a7079
ddec82a
10f533b
76a2b2b
86969bd
e10ffe6
5ab8583
6aeaa1b
3f166e3
8fff5b8
c27cfdf
542f64a
c9e48b8
d69b130
2e0c1bd
42c0a1b
87ea094
474ad93
ce108f9
2469037
7b6b3a2
2a2d881
1c049d1
9bffae7
13f6a31
c78f189
2ac5168
6213bfe
3dacd8d
7b6b7af
0313bb2
ffc37ea
6916600
c0120ff
8b8db4d
af8001e
71286eb
59aadf7
26944a6
e9f3e32
3e2a0e5
bf116f0
eb39ca8
2726853
ef91fc2
a610cf4
606cbb9
a44942e
97ef336
e516fb5
87d0f02
bd7d1a0
f1f43fd
6820b25
c0fd516
51037fd
a09f9b2
ac117d3
6bd798b
bdd803d
f0c8455
0857d1e
1b6ee4a
e1fd300
8f14b37
11b54ff
a4f6b32
9f4b4ac
44f76dd
5d2af1a
9c115d7
6b5769b
a42a9d8
2ec6606
b3a94b2
40ba22c
bb5543e
a56a671
123f053
e4e62c1
f6510f0
d01ff34
e57a0ec
1118ca5
920889f
f96e6ff
e328f44
2f7d9c2
3dc291e
4bb6a5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# A single CI script with github workflow | ||
name: Build wheels and deploy | ||
|
||
on: | ||
workflow_dispatch: | ||
push: | ||
merge_group: | ||
release: | ||
types: | ||
- published | ||
|
||
jobs: | ||
build: | ||
strategy: | ||
matrix: | ||
os: [ubuntu-latest, macos-latest, windows-latest] | ||
python-version: [ 3.9, '3.10', '3.11', '3.12'] | ||
uses: qiboteam/workflows/.github/workflows/deploy-pip-poetry.yml@v1 | ||
with: | ||
os: ${{ matrix.os }} | ||
python-version: ${{ matrix.python-version }} | ||
publish: ${{ github.event_name == 'release' && github.event.action == 'published' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10' }} | ||
poetry-extras: "--with tests" | ||
secrets: inherit |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,28 @@ | ||
import importlib.metadata as im | ||
from typing import Union | ||
|
||
import numpy.typing as npt | ||
|
||
from qiboml.backends.__init__ import MetaBackend | ||
|
||
__version__ = im.version(__package__) | ||
|
||
ndarray = npt.NDArray | ||
|
||
try: | ||
from tensorflow import Tensor as tf_tensor | ||
|
||
from qiboml.models import keras | ||
|
||
ndarray = Union[ndarray, tf_tensor] | ||
except ImportError: | ||
pass | ||
|
||
try: | ||
from torch import Tensor as pt_tensor | ||
|
||
from qiboml.models import pytorch | ||
|
||
ndarray = Union[ndarray, pt_tensor] | ||
except ImportError: | ||
pass | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should never import from a level directly above yours, and the package I'd suggest you to move this definition in a standalone module (though I still a bit undecided about which would be the best course of action for this...). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
"""Defines the general structure of a qiboml module""" | ||
|
||
from abc import ABC, abstractmethod | ||
from dataclasses import dataclass | ||
|
||
from qibo import Circuit | ||
from qibo.backends import Backend | ||
from qibo.config import raise_error | ||
from qibo.gates import abstract | ||
|
||
from qiboml import ndarray | ||
from qiboml.backends import JaxBackend | ||
|
||
|
||
@dataclass | ||
class QuantumCircuitLayer(ABC): | ||
|
||
nqubits: int | ||
qubits: list[int] = None | ||
_circuit: Circuit = None | ||
backend: Backend = JaxBackend() | ||
|
||
def __post_init__(self) -> None: | ||
if self.qubits is None: | ||
self.qubits = list(range(self.nqubits)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of this, I believe it would be more elegant to use a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem I see here, though, is that then you will need to construct the layer as:
if you want to specify the qubits. I am not sure if there's any workaround with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interestingly enough, you can do it with Pydantic: class Ciao(BaseModel):
name_: str = Field(alias="name")
@property
def name(self):
print(f"Ciao, {self.name_}")
return self.name_
c = Ciao(name="come va?")
c.name (though you can not use |
||
self._circuit = Circuit(self.nqubits) | ||
|
||
@abstractmethod | ||
def forward(self, x): # pragma: no cover | ||
pass | ||
|
||
def __call__(self, x): | ||
return self.forward(x) | ||
|
||
@property | ||
def parameters(self) -> ndarray: | ||
return self.backend.cast(self.circuit.get_parameters()) | ||
|
||
@parameters.setter | ||
def parameters(self, params: ndarray): | ||
self._circuit.set_parameters(self.backend.cast(params.ravel())) | ||
|
||
@property | ||
def circuit(self) -> Circuit: | ||
return self._circuit |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed, this is fine as long it is temporary: we need a combined training-encoding strategy to compute something actually useful. But yeah, we opened issues for this and we know. So Fine as it is for this PR. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,22 @@ | ||
from dataclasses import dataclass | ||
|
||
import numpy as np | ||
from qibo import Circuit, gates | ||
|
||
from qiboml.models.abstract import QuantumCircuitLayer | ||
|
||
|
||
@dataclass | ||
class ReuploadingLayer(QuantumCircuitLayer): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would be more explicit for the reuploading models. It doesn't have so much sense to define a reuploading model without setting the number of layers. Of course we can do it in another PR. Also, if this is intended to be 1 layer of a reuploading scheme, then it is ok. |
||
|
||
def __post_init__(self): | ||
super().__post_init__() | ||
for q in self.qubits: | ||
self.circuit.add(gates.RY(q, theta=0.0)) | ||
self.circuit.add(gates.RZ(q, theta=0.0)) | ||
for i, q in enumerate(self.qubits[:-2]): | ||
self.circuit.add(gates.CNOT(q0=q, q1=self.qubits[i + 1])) | ||
self.circuit.add(gates.CNOT(q0=self.qubits[-1], q1=self.qubits[0])) | ||
BrunoLiegiBastonLiegi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def reuploading_circuit(nqubits, nlayers): | ||
c = Circuit(nqubits) | ||
for _ in range(nlayers): | ||
for q in range(nqubits): | ||
c.add(gates.RY(q, 0)) | ||
c.add(gates.RZ(q, 0)) | ||
for q in range(0, nqubits - 1, 1): | ||
c.add(gates.CNOT(q0=q, q1=q + 1)) | ||
c.add(gates.CNOT(q0=nqubits - 1, q1=0)) | ||
c.add(gates.M(*range(nqubits))) | ||
return c | ||
def forward(self, x: Circuit) -> Circuit: | ||
return x + self.circuit | ||
BrunoLiegiBastonLiegi marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
"""Some standard encoding and decoding layers""" | ||
|
||
from dataclasses import dataclass | ||
from typing import Union | ||
|
||
import numpy as np | ||
from qibo import Circuit, gates | ||
from qibo.config import raise_error | ||
from qibo.hamiltonians import Hamiltonian | ||
|
||
from qiboml import ndarray | ||
from qiboml.models.abstract import QuantumCircuitLayer | ||
|
||
|
||
@dataclass | ||
class QuantumEncodingLayer(QuantumCircuitLayer): | ||
pass | ||
|
||
|
||
@dataclass | ||
class BinaryEncodingLayer(QuantumEncodingLayer): | ||
|
||
def forward(self, x: ndarray) -> Circuit: | ||
if x.shape[-1] != len(self.qubits): | ||
raise_error( | ||
RuntimeError, | ||
f"Invalid input dimension {x.shape[-1]}, but the allocated qubits are {self.qubits}.", | ||
) | ||
circuit = self.circuit.copy() | ||
ones = np.flatnonzero(x.ravel() == 1) | ||
for bit in ones: | ||
circuit.add(gates.X(self.qubits[bit])) | ||
return circuit | ||
|
||
|
||
@dataclass | ||
class PhaseEncodingLayer(QuantumEncodingLayer): | ||
|
||
def __post_init__(self): | ||
super().__post_init__() | ||
for q in self.qubits: | ||
self.circuit.add(gates.RZ(q, theta=0.0)) | ||
|
||
def forward(self, x: ndarray) -> Circuit: | ||
self.parameters = x | ||
return self.circuit | ||
|
||
|
||
@dataclass | ||
class QuantumDecodingLayer(QuantumCircuitLayer): | ||
|
||
nshots: int = 1000 | ||
|
||
def __post_init__(self): | ||
super().__post_init__() | ||
self.circuit.add(gates.M(*self.qubits)) | ||
|
||
def forward(self, x: Circuit) -> "CircuitResult": | ||
return self.backend.execute_circuit(x + self.circuit, nshots=self.nshots) | ||
|
||
|
||
class ProbabilitiesLayer(QuantumDecodingLayer): | ||
|
||
def __post_init__(self): | ||
super().__post_init__() | ||
|
||
def forward(self, x: Circuit) -> ndarray: | ||
return super().forward(x).probabilities(self.qubits).reshape(1, -1) | ||
|
||
@property | ||
def output_shape(self): | ||
return (1, 2 ** len(self.qubits)) | ||
|
||
|
||
class SamplesLayer(QuantumDecodingLayer): | ||
|
||
def forward(self, x: Circuit) -> ndarray: | ||
return self.backend.cast(super().forward(x).samples(), dtype=np.float64) | ||
|
||
@property | ||
def output_shape(self): | ||
return (self.nshots, len(self.qubits)) | ||
|
||
|
||
class StateLayer(QuantumDecodingLayer): | ||
|
||
def forward(self, x: Circuit) -> ndarray: | ||
state = super().forward(x).state() | ||
return self.backend.np.vstack( | ||
(self.backend.np.real(state), self.backend.np.imag(state)) | ||
) | ||
|
||
@property | ||
def output_shape(self): | ||
return (2, 2**self.nqubits) | ||
|
||
|
||
@dataclass | ||
class ExpectationLayer(QuantumDecodingLayer): | ||
|
||
observable: Union[ndarray, Hamiltonian] = None | ||
analytic: bool = False | ||
|
||
def __post_init__(self): | ||
if self.observable is None: | ||
raise_error( | ||
RuntimeError, | ||
"Please provide an observable for expectation value calculation.", | ||
) | ||
super().__post_init__() | ||
|
||
def forward(self, x: Circuit) -> ndarray: | ||
if self.analytic: | ||
return self.observable.expectation( | ||
super().forward(x).state(), | ||
).reshape(1, 1) | ||
else: | ||
return self.backend.cast( | ||
self.observable.expectation_from_samples( | ||
super().forward(x).frequencies(), | ||
qubit_map=self.qubits, | ||
).reshape(1, 1), | ||
dtype=np.float64, | ||
) | ||
|
||
@property | ||
def output_shape(self): | ||
return (1, 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the lock in my PSR branch to the purpose of this PR fine with this!