qadence 1.1.1__py3-none-any.whl → 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qadence/__init__.py +1 -0
- qadence/analog/__init__.py +4 -2
- qadence/analog/addressing.py +167 -0
- qadence/analog/constants.py +59 -0
- qadence/analog/device.py +82 -0
- qadence/analog/hamiltonian_terms.py +101 -0
- qadence/analog/parse_analog.py +120 -0
- qadence/backend.py +42 -12
- qadence/backends/__init__.py +1 -2
- qadence/backends/api.py +27 -9
- qadence/backends/braket/backend.py +3 -2
- qadence/backends/horqrux/__init__.py +5 -0
- qadence/backends/horqrux/backend.py +216 -0
- qadence/backends/horqrux/config.py +26 -0
- qadence/backends/horqrux/convert_ops.py +273 -0
- qadence/backends/jax_utils.py +45 -0
- qadence/backends/pulser/__init__.py +0 -1
- qadence/backends/pulser/backend.py +31 -15
- qadence/backends/pulser/config.py +19 -10
- qadence/backends/pulser/devices.py +57 -63
- qadence/backends/pulser/pulses.py +70 -12
- qadence/backends/pyqtorch/backend.py +4 -4
- qadence/backends/pyqtorch/config.py +18 -12
- qadence/backends/pyqtorch/convert_ops.py +15 -7
- qadence/backends/utils.py +5 -9
- qadence/blocks/abstract.py +5 -1
- qadence/blocks/analog.py +18 -9
- qadence/blocks/block_to_tensor.py +11 -0
- qadence/blocks/embedding.py +46 -24
- qadence/blocks/primitive.py +81 -9
- qadence/blocks/utils.py +20 -1
- qadence/circuit.py +3 -9
- qadence/constructors/__init__.py +4 -0
- qadence/constructors/feature_maps.py +84 -60
- qadence/constructors/hamiltonians.py +27 -98
- qadence/constructors/rydberg_feature_maps.py +113 -0
- qadence/divergences.py +12 -0
- qadence/engines/__init__.py +0 -0
- qadence/engines/differentiable_backend.py +152 -0
- qadence/engines/jax/__init__.py +8 -0
- qadence/engines/jax/differentiable_backend.py +73 -0
- qadence/engines/jax/differentiable_expectation.py +94 -0
- qadence/engines/torch/__init__.py +4 -0
- qadence/engines/torch/differentiable_backend.py +85 -0
- qadence/extensions.py +21 -9
- qadence/finitediff.py +47 -0
- qadence/mitigations/readout.py +92 -25
- qadence/ml_tools/models.py +10 -3
- qadence/models/qnn.py +88 -23
- qadence/models/quantum_model.py +13 -2
- qadence/operations.py +55 -70
- qadence/parameters.py +24 -13
- qadence/register.py +91 -43
- qadence/transpile/__init__.py +1 -0
- qadence/transpile/apply_fn.py +40 -0
- qadence/types.py +32 -2
- qadence/utils.py +35 -0
- {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/METADATA +22 -3
- {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/RECORD +62 -44
- {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/WHEEL +1 -1
- qadence/analog/interaction.py +0 -198
- qadence/analog/utils.py +0 -132
- /qadence/{backends/pytorch_wrapper.py → engines/torch/differentiable_expectation.py} +0 -0
- {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/licenses/LICENSE +0 -0
qadence/models/qnn.py
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from collections import Counter
|
3
4
|
from typing import Callable
|
4
5
|
|
6
|
+
import sympy
|
5
7
|
from torch import Tensor
|
6
8
|
|
7
|
-
from qadence.backend import BackendConfiguration
|
9
|
+
from qadence.backend import BackendConfiguration, ConvertedObservable
|
8
10
|
from qadence.blocks.abstract import AbstractBlock
|
9
11
|
from qadence.circuit import QuantumCircuit
|
10
12
|
from qadence.measurements import Measurements
|
13
|
+
from qadence.mitigations import Mitigations
|
11
14
|
from qadence.models.quantum_model import QuantumModel
|
12
15
|
from qadence.noise import Noise
|
13
16
|
from qadence.types import BackendName, DiffMode, Endianness
|
@@ -19,22 +22,25 @@ class QNN(QuantumModel):
|
|
19
22
|
Examples:
|
20
23
|
```python exec="on" source="material-block" result="json"
|
21
24
|
import torch
|
22
|
-
from qadence import QuantumCircuit, QNN
|
23
|
-
from qadence import hea, feature_map, hamiltonian_factory,
|
25
|
+
from qadence import QuantumCircuit, QNN, Z
|
26
|
+
from qadence import hea, feature_map, hamiltonian_factory, kron
|
24
27
|
|
25
28
|
# create the circuit
|
26
29
|
n_qubits, depth = 2, 4
|
27
|
-
fm =
|
30
|
+
fm = kron(
|
31
|
+
feature_map(1, support=(0,), param="x"),
|
32
|
+
feature_map(1, support=(1,), param="y")
|
33
|
+
)
|
28
34
|
ansatz = hea(n_qubits=n_qubits, depth=depth)
|
29
35
|
circuit = QuantumCircuit(n_qubits, fm, ansatz)
|
30
|
-
obs_base = hamiltonian_factory(n_qubits, detuning
|
36
|
+
obs_base = hamiltonian_factory(n_qubits, detuning=Z)
|
31
37
|
|
32
38
|
# the QNN will yield two outputs
|
33
39
|
obs = [2.0 * obs_base, 4.0 * obs_base]
|
34
40
|
|
35
41
|
# initialize and use the model
|
36
|
-
qnn = QNN(circuit, obs,
|
37
|
-
y = qnn
|
42
|
+
qnn = QNN(circuit, obs, inputs=["x", "y"])
|
43
|
+
y = qnn(torch.rand(3, 2))
|
38
44
|
print(str(y)) # markdown-exec: hide
|
39
45
|
```
|
40
46
|
"""
|
@@ -49,6 +55,7 @@ class QNN(QuantumModel):
|
|
49
55
|
measurement: Measurements | None = None,
|
50
56
|
noise: Noise | None = None,
|
51
57
|
configuration: BackendConfiguration | dict | None = None,
|
58
|
+
inputs: list[sympy.Basic | str] | None = None,
|
52
59
|
):
|
53
60
|
"""Initialize the QNN.
|
54
61
|
|
@@ -59,6 +66,9 @@ class QNN(QuantumModel):
|
|
59
66
|
Args:
|
60
67
|
circuit: The quantum circuit to use for the QNN.
|
61
68
|
transform: A transformation applied to the output of the QNN.
|
69
|
+
inputs: Tuple that indicates the order of variables of the tensors that are passed
|
70
|
+
to the model. Given input tensors `xs = torch.rand(batch_size, input_size:=2)` a QNN
|
71
|
+
with `inputs=("t", "x")` will assign `t, x = xs[:,0], xs[:,1]`.
|
62
72
|
backend: The chosen quantum backend.
|
63
73
|
diff_mode: The differentiation engine to use. Choices 'gpsr' or 'ad'.
|
64
74
|
measurement: optional measurement protocol. If None,
|
@@ -67,7 +77,7 @@ class QNN(QuantumModel):
|
|
67
77
|
configuration: optional configuration for the backend
|
68
78
|
"""
|
69
79
|
super().__init__(
|
70
|
-
circuit
|
80
|
+
circuit,
|
71
81
|
observable=observable,
|
72
82
|
backend=backend,
|
73
83
|
diff_mode=diff_mode,
|
@@ -75,12 +85,33 @@ class QNN(QuantumModel):
|
|
75
85
|
configuration=configuration,
|
76
86
|
noise=noise,
|
77
87
|
)
|
78
|
-
|
79
88
|
if self.out_features is None:
|
80
89
|
raise ValueError("You need to provide at least one observable in the QNN constructor")
|
81
|
-
|
82
90
|
self.transform = transform if transform else lambda x: x
|
83
91
|
|
92
|
+
if (inputs is not None) and (len(self.inputs) == len(inputs)):
|
93
|
+
self.inputs = [sympy.symbols(x) if isinstance(x, str) else x for x in inputs] # type: ignore[union-attr]
|
94
|
+
elif (inputs is None) and len(self.inputs) <= 1:
|
95
|
+
self.inputs = [sympy.symbols(x) if isinstance(x, str) else x for x in self.inputs] # type: ignore[union-attr]
|
96
|
+
else:
|
97
|
+
raise ValueError(
|
98
|
+
"""
|
99
|
+
Your QNN has more than one input. Please provide a list of inputs in the order of
|
100
|
+
your tensor domain. For example, if you want to pass
|
101
|
+
`xs = torch.rand(batch_size, input_size:=3)` to you QNN, where
|
102
|
+
```
|
103
|
+
t = x[:,0]
|
104
|
+
x = x[:,1]
|
105
|
+
y = x[:,2]
|
106
|
+
```
|
107
|
+
you have to specify
|
108
|
+
```
|
109
|
+
QNN(circuit, observable, inputs=["t", "x", "y"])
|
110
|
+
```
|
111
|
+
You can also pass a list of sympy symbols.
|
112
|
+
"""
|
113
|
+
)
|
114
|
+
|
84
115
|
def forward(
|
85
116
|
self,
|
86
117
|
values: dict[str, Tensor] | Tensor = None,
|
@@ -103,7 +134,7 @@ class QNN(QuantumModel):
|
|
103
134
|
is instead `n_batches x n_observables`
|
104
135
|
|
105
136
|
Args:
|
106
|
-
values
|
137
|
+
values: the values of the feature parameters
|
107
138
|
state: Initial state.
|
108
139
|
measurement: optional measurement protocol. If None,
|
109
140
|
use exact expectation value with a statevector simulator
|
@@ -114,18 +145,55 @@ class QNN(QuantumModel):
|
|
114
145
|
Tensor: a tensor with the expectation value of the observables passed
|
115
146
|
in the constructor of the model
|
116
147
|
"""
|
148
|
+
return self.expectation(
|
149
|
+
values, state=state, measurement=measurement, noise=noise, endianness=endianness
|
150
|
+
)
|
151
|
+
|
152
|
+
def run(
|
153
|
+
self,
|
154
|
+
values: Tensor | dict[str, Tensor] = None,
|
155
|
+
state: Tensor | None = None,
|
156
|
+
endianness: Endianness = Endianness.BIG,
|
157
|
+
) -> Tensor:
|
158
|
+
return super().run(values=self._format_to_dict(values), state=state, endianness=endianness)
|
159
|
+
|
160
|
+
def sample(
|
161
|
+
self,
|
162
|
+
values: Tensor | dict[str, Tensor] = {},
|
163
|
+
n_shots: int = 1000,
|
164
|
+
state: Tensor | None = None,
|
165
|
+
noise: Noise | None = None,
|
166
|
+
mitigation: Mitigations | None = None,
|
167
|
+
endianness: Endianness = Endianness.BIG,
|
168
|
+
) -> list[Counter]:
|
169
|
+
return super().sample(
|
170
|
+
values=self._format_to_dict(values),
|
171
|
+
n_shots=n_shots,
|
172
|
+
state=state,
|
173
|
+
noise=noise,
|
174
|
+
mitigation=mitigation,
|
175
|
+
endianness=endianness,
|
176
|
+
)
|
177
|
+
|
178
|
+
def expectation(
|
179
|
+
self,
|
180
|
+
values: Tensor | dict[str, Tensor] = {},
|
181
|
+
observable: list[ConvertedObservable] | ConvertedObservable | None = None,
|
182
|
+
state: Tensor | None = None,
|
183
|
+
measurement: Measurements | None = None,
|
184
|
+
noise: Noise | None = None,
|
185
|
+
mitigation: Mitigations | None = None,
|
186
|
+
endianness: Endianness = Endianness.BIG,
|
187
|
+
) -> Tensor:
|
117
188
|
if values is None:
|
118
189
|
values = {}
|
119
|
-
if not isinstance(values, dict):
|
120
|
-
values = self._format_to_dict(values)
|
121
190
|
if measurement is None:
|
122
191
|
measurement = self._measurement
|
123
192
|
if noise is None:
|
124
193
|
noise = self._noise
|
125
|
-
|
126
194
|
return self.transform(
|
127
|
-
|
128
|
-
values=values,
|
195
|
+
super().expectation(
|
196
|
+
values=self._format_to_dict(values),
|
129
197
|
state=state,
|
130
198
|
measurement=measurement,
|
131
199
|
endianness=endianness,
|
@@ -139,6 +207,9 @@ class QNN(QuantumModel):
|
|
139
207
|
The tensor is assumed to have dimensions: n_batches x in_features where in_features
|
140
208
|
corresponds to the number of input features of the QNN
|
141
209
|
"""
|
210
|
+
# for backwards compat...
|
211
|
+
if isinstance(values, dict):
|
212
|
+
return values
|
142
213
|
|
143
214
|
if len(values.size()) == 1:
|
144
215
|
values = values.reshape(-1, 1)
|
@@ -146,10 +217,4 @@ class QNN(QuantumModel):
|
|
146
217
|
assert len(values.size()) == 2, msg
|
147
218
|
assert values.size()[1] == self.in_features, msg
|
148
219
|
|
149
|
-
|
150
|
-
res = {}
|
151
|
-
for i, name in enumerate(names):
|
152
|
-
res[name] = values[:, i]
|
153
|
-
return res
|
154
|
-
|
155
|
-
# TODO: Implement derivatives w.r.t. to inputs
|
220
|
+
return {var.name: values[:, self.inputs.index(var)] for var in self.inputs}
|
qadence/models/quantum_model.py
CHANGED
@@ -18,11 +18,14 @@ from qadence.backend import (
|
|
18
18
|
)
|
19
19
|
from qadence.backends.api import backend_factory, config_factory
|
20
20
|
from qadence.blocks.abstract import AbstractBlock
|
21
|
+
from qadence.blocks.utils import chain, unique_parameters
|
21
22
|
from qadence.circuit import QuantumCircuit
|
23
|
+
from qadence.engines.differentiable_backend import DifferentiableBackend
|
22
24
|
from qadence.logger import get_logger
|
23
25
|
from qadence.measurements import Measurements
|
24
26
|
from qadence.mitigations import Mitigations
|
25
27
|
from qadence.noise import Noise
|
28
|
+
from qadence.parameters import Parameter
|
26
29
|
from qadence.types import DiffMode, Endianness
|
27
30
|
|
28
31
|
logger = get_logger(__name__)
|
@@ -36,7 +39,7 @@ class QuantumModel(nn.Module):
|
|
36
39
|
[here](/advanced_tutorials/custom-models.md).
|
37
40
|
"""
|
38
41
|
|
39
|
-
backend: Backend
|
42
|
+
backend: Backend | DifferentiableBackend
|
40
43
|
embedding_fn: Callable
|
41
44
|
_params: nn.ParameterDict
|
42
45
|
_circuit: ConvertedCircuit
|
@@ -77,7 +80,6 @@ class QuantumModel(nn.Module):
|
|
77
80
|
f"The circuit should be of type '<class QuantumCircuit>'. Got {type(circuit)}."
|
78
81
|
)
|
79
82
|
|
80
|
-
self.inputs = [p for p in circuit.unique_parameters if not p.trainable and not p.is_number]
|
81
83
|
if diff_mode is None:
|
82
84
|
raise ValueError("`diff_mode` cannot be `None` in a `QuantumModel`.")
|
83
85
|
|
@@ -90,6 +92,15 @@ class QuantumModel(nn.Module):
|
|
90
92
|
else:
|
91
93
|
observable = [observable]
|
92
94
|
|
95
|
+
def _is_feature_param(p: Parameter) -> bool:
|
96
|
+
return not p.trainable and not p.is_number
|
97
|
+
|
98
|
+
if observable is None:
|
99
|
+
self.inputs = list(filter(_is_feature_param, circuit.unique_parameters))
|
100
|
+
else:
|
101
|
+
uparams = unique_parameters(chain(circuit.block, *observable))
|
102
|
+
self.inputs = list(filter(_is_feature_param, uparams))
|
103
|
+
|
93
104
|
conv = self.backend.convert(circuit, observable)
|
94
105
|
self.embedding_fn = conv.embedding_fn
|
95
106
|
self._circuit = conv.circuit
|
qadence/operations.py
CHANGED
@@ -31,6 +31,7 @@ from qadence.blocks.analog import (
|
|
31
31
|
WaitBlock,
|
32
32
|
)
|
33
33
|
from qadence.blocks.block_to_tensor import block_to_tensor
|
34
|
+
from qadence.blocks.primitive import ProjectorBlock
|
34
35
|
from qadence.blocks.utils import (
|
35
36
|
add, # noqa
|
36
37
|
block_is_commuting_hamiltonian,
|
@@ -117,9 +118,6 @@ class X(PrimitiveBlock):
|
|
117
118
|
def eigenvalues(self) -> Tensor:
|
118
119
|
return tensor([-1, 1], dtype=cdouble)
|
119
120
|
|
120
|
-
def dagger(self) -> X:
|
121
|
-
return self
|
122
|
-
|
123
121
|
|
124
122
|
class Y(PrimitiveBlock):
|
125
123
|
"""The Y gate."""
|
@@ -141,9 +139,6 @@ class Y(PrimitiveBlock):
|
|
141
139
|
def eigenvalues(self) -> Tensor:
|
142
140
|
return tensor([-1, 1], dtype=cdouble)
|
143
141
|
|
144
|
-
def dagger(self) -> Y:
|
145
|
-
return self
|
146
|
-
|
147
142
|
|
148
143
|
class Z(PrimitiveBlock):
|
149
144
|
"""The Z gate."""
|
@@ -165,17 +160,36 @@ class Z(PrimitiveBlock):
|
|
165
160
|
def eigenvalues(self) -> Tensor:
|
166
161
|
return tensor([-1, 1], dtype=cdouble)
|
167
162
|
|
168
|
-
def dagger(self) -> Z:
|
169
|
-
return self
|
170
163
|
|
164
|
+
class Projector(ProjectorBlock):
|
165
|
+
"""The projector operator."""
|
166
|
+
|
167
|
+
name = OpName.PROJ
|
168
|
+
|
169
|
+
def __init__(
|
170
|
+
self,
|
171
|
+
ket: str,
|
172
|
+
bra: str,
|
173
|
+
qubit_support: int | tuple[int, ...],
|
174
|
+
):
|
175
|
+
super().__init__(ket=ket, bra=bra, qubit_support=qubit_support)
|
176
|
+
|
177
|
+
@property
|
178
|
+
def generator(self) -> None:
|
179
|
+
raise ValueError("Property `generator` not available for non-unitary operator.")
|
180
|
+
|
181
|
+
@property
|
182
|
+
def eigenvalues_generator(self) -> None:
|
183
|
+
raise ValueError("Property `eigenvalues_generator` not available for non-unitary operator.")
|
171
184
|
|
172
|
-
|
185
|
+
|
186
|
+
class N(Projector):
|
173
187
|
"""The N = (1/2)(I-Z) operator."""
|
174
188
|
|
175
189
|
name = OpName.N
|
176
190
|
|
177
|
-
def __init__(self, target: int):
|
178
|
-
super().__init__((target,))
|
191
|
+
def __init__(self, target: int, state: str = "1"):
|
192
|
+
super().__init__(ket=state, bra=state, qubit_support=(target,))
|
179
193
|
|
180
194
|
@property
|
181
195
|
def generator(self) -> None:
|
@@ -189,9 +203,6 @@ class N(PrimitiveBlock):
|
|
189
203
|
def eigenvalues(self) -> Tensor:
|
190
204
|
return tensor([0, 1], dtype=cdouble)
|
191
205
|
|
192
|
-
def dagger(self) -> N:
|
193
|
-
return self
|
194
|
-
|
195
206
|
|
196
207
|
class S(PrimitiveBlock):
|
197
208
|
"""The S / Phase gate."""
|
@@ -296,9 +307,6 @@ class I(PrimitiveBlock):
|
|
296
307
|
def __ascii__(self, console: Console) -> Padding:
|
297
308
|
return Padding("──────", (1, 1, 1, 1))
|
298
309
|
|
299
|
-
def dagger(self) -> I:
|
300
|
-
return I(*self.qubit_support)
|
301
|
-
|
302
310
|
|
303
311
|
TPauliBlock = Union[X, Y, Z, I, N]
|
304
312
|
|
@@ -320,9 +328,6 @@ class H(PrimitiveBlock):
|
|
320
328
|
def eigenvalues(self) -> Tensor:
|
321
329
|
return torch.tensor([-1, 1], dtype=cdouble)
|
322
330
|
|
323
|
-
def dagger(self) -> H:
|
324
|
-
return H(*self.qubit_support)
|
325
|
-
|
326
331
|
|
327
332
|
class Zero(PrimitiveBlock):
|
328
333
|
name = OpName.ZERO
|
@@ -363,9 +368,6 @@ class Zero(PrimitiveBlock):
|
|
363
368
|
def __pow__(self, power: int) -> AbstractBlock:
|
364
369
|
return self
|
365
370
|
|
366
|
-
def dagger(self) -> Zero:
|
367
|
-
return Zero()
|
368
|
-
|
369
371
|
|
370
372
|
class RX(ParametricBlock):
|
371
373
|
"""The Rx gate."""
|
@@ -668,7 +670,7 @@ class CNOT(ControlBlock):
|
|
668
670
|
name = OpName.CNOT
|
669
671
|
|
670
672
|
def __init__(self, control: int, target: int) -> None:
|
671
|
-
self.generator = kron((
|
673
|
+
self.generator = kron(N(control), X(target) - I(target))
|
672
674
|
super().__init__((control,), X(target))
|
673
675
|
|
674
676
|
@property
|
@@ -691,17 +693,12 @@ class CNOT(ControlBlock):
|
|
691
693
|
tree.add(self._block_title)
|
692
694
|
return tree
|
693
695
|
|
694
|
-
def dagger(self) -> CNOT:
|
695
|
-
return CNOT(*self.qubit_support)
|
696
|
-
|
697
696
|
|
698
697
|
class MCZ(ControlBlock):
|
699
698
|
name = OpName.MCZ
|
700
699
|
|
701
700
|
def __init__(self, control: tuple[int, ...], target: int) -> None:
|
702
|
-
self.generator = kron(
|
703
|
-
*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], Z(target) - I(target)
|
704
|
-
)
|
701
|
+
self.generator = kron(*[N(qubit) for qubit in control], Z(target) - I(target))
|
705
702
|
super().__init__(control, Z(target))
|
706
703
|
|
707
704
|
@property
|
@@ -724,9 +721,6 @@ class MCZ(ControlBlock):
|
|
724
721
|
tree.add(self._block_title)
|
725
722
|
return tree
|
726
723
|
|
727
|
-
def dagger(self) -> MCZ:
|
728
|
-
return MCZ(self.qubit_support[:-1], self.qubit_support[-1])
|
729
|
-
|
730
724
|
|
731
725
|
class CZ(MCZ):
|
732
726
|
"""The CZ gate."""
|
@@ -736,9 +730,6 @@ class CZ(MCZ):
|
|
736
730
|
def __init__(self, control: int, target: int) -> None:
|
737
731
|
super().__init__((control,), target)
|
738
732
|
|
739
|
-
def dagger(self) -> CZ:
|
740
|
-
return CZ(self.qubit_support[-2], self.qubit_support[-1])
|
741
|
-
|
742
733
|
|
743
734
|
class MCRX(ParametricControlBlock):
|
744
735
|
name = OpName.MCRX
|
@@ -749,7 +740,7 @@ class MCRX(ParametricControlBlock):
|
|
749
740
|
target: int,
|
750
741
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
751
742
|
) -> None:
|
752
|
-
self.generator = kron(*[(
|
743
|
+
self.generator = kron(*[N(qubit) for qubit in control], X(target))
|
753
744
|
super().__init__(control, RX(target, parameter))
|
754
745
|
|
755
746
|
@classmethod
|
@@ -792,7 +783,7 @@ class MCRY(ParametricControlBlock):
|
|
792
783
|
target: int,
|
793
784
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
794
785
|
) -> None:
|
795
|
-
self.generator = kron(*[(
|
786
|
+
self.generator = kron(*[N(qubit) for qubit in control], Y(target))
|
796
787
|
super().__init__(control, RY(target, parameter))
|
797
788
|
|
798
789
|
@classmethod
|
@@ -821,7 +812,7 @@ class CRY(MCRY):
|
|
821
812
|
self,
|
822
813
|
control: int,
|
823
814
|
target: int,
|
824
|
-
parameter:
|
815
|
+
parameter: TParameter,
|
825
816
|
):
|
826
817
|
super().__init__((control,), target, parameter)
|
827
818
|
|
@@ -835,7 +826,7 @@ class MCRZ(ParametricControlBlock):
|
|
835
826
|
target: int,
|
836
827
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
837
828
|
) -> None:
|
838
|
-
self.generator = kron(*[(
|
829
|
+
self.generator = kron(*[N(qubit) for qubit in control], Z(target))
|
839
830
|
super().__init__(control, RZ(target, parameter))
|
840
831
|
|
841
832
|
@classmethod
|
@@ -878,10 +869,10 @@ class CSWAP(ControlBlock):
|
|
878
869
|
if isinstance(control, tuple):
|
879
870
|
control = control[0]
|
880
871
|
|
881
|
-
a00m =
|
882
|
-
a00p = -
|
883
|
-
a11 =
|
884
|
-
a22 = -
|
872
|
+
a00m = -N(target=control)
|
873
|
+
a00p = -N(target=control, state="0")
|
874
|
+
a11 = -N(target=target1)
|
875
|
+
a22 = -N(target=target2, state="0")
|
885
876
|
a12 = 0.5 * (chain(X(target1), Z(target1)) + X(target1))
|
886
877
|
a21 = 0.5 * (chain(Z(target2), X(target2)) + X(target2))
|
887
878
|
no_effect = kron(a00m, I(target1), I(target2))
|
@@ -906,9 +897,6 @@ class CSWAP(ControlBlock):
|
|
906
897
|
def nqubits(self) -> int:
|
907
898
|
return 3
|
908
899
|
|
909
|
-
def dagger(self) -> CSWAP:
|
910
|
-
return CSWAP(*self.qubit_support)
|
911
|
-
|
912
900
|
|
913
901
|
class T(PrimitiveBlock):
|
914
902
|
"""The T gate."""
|
@@ -994,9 +982,6 @@ class SWAP(PrimitiveBlock):
|
|
994
982
|
s = f"{self.name}({c}, {t})"
|
995
983
|
return s if self.tag is None else (s + rf" \[tag: {self.tag}]")
|
996
984
|
|
997
|
-
def dagger(self) -> SWAP:
|
998
|
-
return SWAP(*self.qubit_support)
|
999
|
-
|
1000
985
|
|
1001
986
|
class AnalogSWAP(HamEvo):
|
1002
987
|
"""
|
@@ -1031,9 +1016,7 @@ class MCPHASE(ParametricControlBlock):
|
|
1031
1016
|
target: int,
|
1032
1017
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
1033
1018
|
) -> None:
|
1034
|
-
self.generator = kron(
|
1035
|
-
*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], Z(target) - I(target)
|
1036
|
-
)
|
1019
|
+
self.generator = kron(*[N(qubit) for qubit in control], Z(target) - I(target))
|
1037
1020
|
super().__init__(control, PHASE(target, parameter))
|
1038
1021
|
|
1039
1022
|
@classmethod
|
@@ -1082,14 +1065,9 @@ class Toffoli(ControlBlock):
|
|
1082
1065
|
name = OpName.TOFFOLI
|
1083
1066
|
|
1084
1067
|
def __init__(self, control: tuple[int, ...], target: int) -> None:
|
1085
|
-
self.generator = kron(
|
1086
|
-
*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], X(target) - I(target)
|
1087
|
-
)
|
1068
|
+
self.generator = kron(*[N(qubit) for qubit in control], X(target) - I(target))
|
1088
1069
|
super().__init__(control, X(target))
|
1089
1070
|
|
1090
|
-
def dagger(self) -> Toffoli:
|
1091
|
-
return Toffoli(self.qubit_support[:-1], self.qubit_support[-1])
|
1092
|
-
|
1093
1071
|
@property
|
1094
1072
|
def n_qubits(self) -> int:
|
1095
1073
|
return len(self.qubit_support)
|
@@ -1129,6 +1107,7 @@ def _cast(T: Any, val: Any) -> Any:
|
|
1129
1107
|
def wait(
|
1130
1108
|
duration: TNumber | sympy.Basic,
|
1131
1109
|
qubit_support: str | QubitSupport | tuple = "global",
|
1110
|
+
add_pattern: bool = True,
|
1132
1111
|
) -> WaitBlock:
|
1133
1112
|
"""Constructs a [`WaitBlock`][qadence.blocks.analog.WaitBlock].
|
1134
1113
|
|
@@ -1142,7 +1121,7 @@ def wait(
|
|
1142
1121
|
"""
|
1143
1122
|
q = _cast(QubitSupport, qubit_support)
|
1144
1123
|
ps = ParamMap(duration=duration)
|
1145
|
-
return WaitBlock(parameters=ps, qubit_support=q)
|
1124
|
+
return WaitBlock(parameters=ps, qubit_support=q, add_pattern=add_pattern)
|
1146
1125
|
|
1147
1126
|
|
1148
1127
|
def entangle(
|
@@ -1160,6 +1139,7 @@ def AnalogRot(
|
|
1160
1139
|
delta: float | str | Parameter = 0,
|
1161
1140
|
phase: float | str | Parameter = 0,
|
1162
1141
|
qubit_support: str | QubitSupport | Tuple = "global",
|
1142
|
+
add_pattern: bool = True,
|
1163
1143
|
) -> ConstantAnalogRotation:
|
1164
1144
|
"""General analog rotation operation.
|
1165
1145
|
|
@@ -1174,18 +1154,20 @@ def AnalogRot(
|
|
1174
1154
|
ConstantAnalogRotation
|
1175
1155
|
"""
|
1176
1156
|
q = _cast(QubitSupport, qubit_support)
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1157
|
+
duration = Parameter(duration)
|
1158
|
+
omega = Parameter(omega)
|
1159
|
+
delta = Parameter(delta)
|
1160
|
+
phase = Parameter(phase)
|
1161
|
+
alpha = duration * sympy.sqrt(omega**2 + delta**2) / 1000
|
1181
1162
|
ps = ParamMap(alpha=alpha, duration=duration, omega=omega, delta=delta, phase=phase)
|
1182
|
-
return ConstantAnalogRotation(parameters=ps, qubit_support=q)
|
1163
|
+
return ConstantAnalogRotation(parameters=ps, qubit_support=q, add_pattern=add_pattern)
|
1183
1164
|
|
1184
1165
|
|
1185
1166
|
def _analog_rot(
|
1186
1167
|
angle: float | str | Parameter,
|
1187
1168
|
qubit_support: str | QubitSupport | Tuple,
|
1188
1169
|
phase: float,
|
1170
|
+
add_pattern: bool = True,
|
1189
1171
|
) -> ConstantAnalogRotation:
|
1190
1172
|
q = _cast(QubitSupport, qubit_support)
|
1191
1173
|
# assuming some arbitrary omega = π rad/μs
|
@@ -1200,12 +1182,13 @@ def _analog_rot(
|
|
1200
1182
|
# and compute omega like this:
|
1201
1183
|
# omega = alpha / duration * 1000
|
1202
1184
|
ps = ParamMap(alpha=alpha, duration=duration, omega=omega, delta=0, phase=phase)
|
1203
|
-
return ConstantAnalogRotation(parameters=ps, qubit_support=q)
|
1185
|
+
return ConstantAnalogRotation(parameters=ps, qubit_support=q, add_pattern=add_pattern)
|
1204
1186
|
|
1205
1187
|
|
1206
1188
|
def AnalogRX(
|
1207
1189
|
angle: float | str | Parameter,
|
1208
1190
|
qubit_support: str | QubitSupport | Tuple = "global",
|
1191
|
+
add_pattern: bool = True,
|
1209
1192
|
) -> ConstantAnalogRotation:
|
1210
1193
|
"""Analog X rotation.
|
1211
1194
|
|
@@ -1223,12 +1206,13 @@ def AnalogRX(
|
|
1223
1206
|
Returns:
|
1224
1207
|
ConstantAnalogRotation
|
1225
1208
|
"""
|
1226
|
-
return _analog_rot(angle, qubit_support, phase=0)
|
1209
|
+
return _analog_rot(angle, qubit_support, phase=0, add_pattern=add_pattern)
|
1227
1210
|
|
1228
1211
|
|
1229
1212
|
def AnalogRY(
|
1230
1213
|
angle: float | str | Parameter,
|
1231
1214
|
qubit_support: str | QubitSupport | Tuple = "global",
|
1215
|
+
add_pattern: bool = True,
|
1232
1216
|
) -> ConstantAnalogRotation:
|
1233
1217
|
"""Analog Y rotation.
|
1234
1218
|
|
@@ -1245,12 +1229,13 @@ def AnalogRY(
|
|
1245
1229
|
Returns:
|
1246
1230
|
ConstantAnalogRotation
|
1247
1231
|
"""
|
1248
|
-
return _analog_rot(angle, qubit_support, phase=-np.pi / 2)
|
1232
|
+
return _analog_rot(angle, qubit_support, phase=-np.pi / 2, add_pattern=add_pattern)
|
1249
1233
|
|
1250
1234
|
|
1251
1235
|
def AnalogRZ(
|
1252
1236
|
angle: float | str | Parameter,
|
1253
1237
|
qubit_support: str | QubitSupport | Tuple = "global",
|
1238
|
+
add_pattern: bool = True,
|
1254
1239
|
) -> ConstantAnalogRotation:
|
1255
1240
|
"""Analog Z rotation. Shorthand for [`AnalogRot`][qadence.operations.AnalogRot]:
|
1256
1241
|
```
|
@@ -1263,7 +1248,7 @@ def AnalogRZ(
|
|
1263
1248
|
delta = np.pi
|
1264
1249
|
duration = alpha / delta * 1000
|
1265
1250
|
ps = ParamMap(alpha=alpha, duration=duration, omega=0, delta=delta, phase=0.0)
|
1266
|
-
return ConstantAnalogRotation(qubit_support=q, parameters=ps)
|
1251
|
+
return ConstantAnalogRotation(qubit_support=q, parameters=ps, add_pattern=add_pattern)
|
1267
1252
|
|
1268
1253
|
|
1269
1254
|
# gate sets
|
@@ -1288,4 +1273,4 @@ analog_gateset = [
|
|
1288
1273
|
entangle,
|
1289
1274
|
wait,
|
1290
1275
|
]
|
1291
|
-
non_unitary_gateset = [Zero, N]
|
1276
|
+
non_unitary_gateset = [Zero, N, Projector]
|
qadence/parameters.py
CHANGED
@@ -8,17 +8,19 @@ import numpy as np
|
|
8
8
|
import sympy
|
9
9
|
from sympy import *
|
10
10
|
from sympy import Array, Basic, Expr, Symbol, sympify
|
11
|
-
from
|
12
|
-
from
|
11
|
+
from sympy.physics.quantum.dagger import Dagger
|
12
|
+
from sympytorch import SymPyModule as torchSympyModule
|
13
|
+
from torch import Tensor, heaviside, no_grad, rand, tensor
|
13
14
|
|
14
15
|
from qadence.logger import get_logger
|
15
|
-
from qadence.types import TNumber
|
16
|
+
from qadence.types import DifferentiableExpression, Engine, TNumber
|
16
17
|
|
17
18
|
# Modules to be automatically added to the qadence namespace
|
18
19
|
__all__ = ["FeatureParameter", "Parameter", "VariationalParameter"]
|
19
20
|
|
20
21
|
logger = get_logger(__file__)
|
21
22
|
|
23
|
+
dagger_expression = Dagger
|
22
24
|
|
23
25
|
ParameterJSONSchema = {
|
24
26
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
@@ -188,17 +190,26 @@ def extract_original_param_entry(
|
|
188
190
|
return param if not param.is_number else evaluate(param)
|
189
191
|
|
190
192
|
|
191
|
-
def
|
192
|
-
|
193
|
-
|
193
|
+
def heaviside_func(x: Tensor, _: Any) -> Tensor:
|
194
|
+
with no_grad():
|
195
|
+
res = heaviside(x, tensor(0.5))
|
196
|
+
return res
|
194
197
|
|
195
|
-
expr: An expression consisting of Parameters.
|
196
198
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
199
|
+
def torchify(expr: Expr) -> torchSympyModule:
|
200
|
+
extra_funcs = {sympy.core.numbers.ImaginaryUnit: 1.0j, sympy.Heaviside: heaviside_func}
|
201
|
+
return torchSympyModule(expressions=[sympy.N(expr)], extra_funcs=extra_funcs)
|
202
|
+
|
203
|
+
|
204
|
+
def make_differentiable(expr: Expr, engine: Engine = Engine.TORCH) -> DifferentiableExpression:
|
205
|
+
diff_expr: DifferentiableExpression
|
206
|
+
if engine == Engine.JAX:
|
207
|
+
from qadence.backends.jax_utils import jaxify
|
208
|
+
|
209
|
+
diff_expr = jaxify(expr)
|
210
|
+
else:
|
211
|
+
diff_expr = torchify(expr)
|
212
|
+
return diff_expr
|
202
213
|
|
203
214
|
|
204
215
|
def sympy_to_numeric(expr: Basic) -> TNumber:
|
@@ -253,7 +264,7 @@ def evaluate(expr: Expr, values: dict = {}, as_torch: bool = False) -> TNumber |
|
|
253
264
|
else:
|
254
265
|
raise ValueError(f"No value provided for symbol {s.name}")
|
255
266
|
if as_torch:
|
256
|
-
res_value =
|
267
|
+
res_value = make_differentiable(expr)(**{s.name: tensor(v) for s, v in query.items()})
|
257
268
|
else:
|
258
269
|
res = expr.subs(query)
|
259
270
|
res_value = sympy_to_numeric(res)
|