qadence 1.7.8__py3-none-any.whl → 1.9.0__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 -1
- qadence/analog/device.py +1 -1
- qadence/analog/parse_analog.py +1 -2
- qadence/backend.py +3 -3
- qadence/backends/gpsr.py +8 -2
- qadence/backends/horqrux/backend.py +3 -3
- qadence/backends/pulser/backend.py +21 -38
- qadence/backends/pulser/convert_ops.py +2 -2
- qadence/backends/pyqtorch/backend.py +85 -10
- qadence/backends/pyqtorch/config.py +10 -3
- qadence/backends/pyqtorch/convert_ops.py +245 -233
- qadence/backends/utils.py +9 -1
- qadence/blocks/abstract.py +1 -1
- qadence/blocks/embedding.py +21 -11
- qadence/blocks/matrix.py +3 -1
- qadence/blocks/primitive.py +37 -11
- qadence/circuit.py +1 -1
- qadence/constructors/__init__.py +2 -1
- qadence/constructors/ansatze.py +176 -0
- qadence/engines/differentiable_backend.py +3 -3
- qadence/engines/jax/differentiable_backend.py +2 -2
- qadence/engines/jax/differentiable_expectation.py +2 -2
- qadence/engines/torch/differentiable_backend.py +2 -2
- qadence/engines/torch/differentiable_expectation.py +2 -2
- qadence/execution.py +14 -16
- qadence/extensions.py +1 -1
- qadence/log_config.yaml +10 -0
- qadence/measurements/shadow.py +101 -133
- qadence/measurements/tomography.py +2 -2
- qadence/measurements/utils.py +4 -4
- qadence/mitigations/analog_zne.py +8 -7
- qadence/mitigations/protocols.py +2 -2
- qadence/mitigations/readout.py +14 -5
- qadence/ml_tools/__init__.py +4 -8
- qadence/ml_tools/callbacks/__init__.py +30 -0
- qadence/ml_tools/callbacks/callback.py +451 -0
- qadence/ml_tools/callbacks/callbackmanager.py +214 -0
- qadence/ml_tools/{saveload.py → callbacks/saveload.py} +11 -11
- qadence/ml_tools/callbacks/writer_registry.py +430 -0
- qadence/ml_tools/config.py +132 -258
- qadence/ml_tools/constructors.py +2 -2
- qadence/ml_tools/data.py +7 -3
- qadence/ml_tools/loss/__init__.py +10 -0
- qadence/ml_tools/loss/loss.py +87 -0
- qadence/ml_tools/models.py +7 -7
- qadence/ml_tools/optimize_step.py +45 -10
- qadence/ml_tools/stages.py +46 -0
- qadence/ml_tools/train_utils/__init__.py +7 -0
- qadence/ml_tools/train_utils/base_trainer.py +548 -0
- qadence/ml_tools/train_utils/config_manager.py +184 -0
- qadence/ml_tools/trainer.py +692 -0
- qadence/model.py +6 -6
- qadence/noise/__init__.py +2 -2
- qadence/noise/protocols.py +188 -36
- qadence/operations/control_ops.py +37 -22
- qadence/operations/ham_evo.py +88 -26
- qadence/operations/parametric.py +32 -10
- qadence/operations/primitive.py +61 -29
- qadence/overlap.py +0 -6
- qadence/parameters.py +3 -2
- qadence/transpile/__init__.py +2 -1
- qadence/transpile/noise.py +53 -0
- qadence/types.py +39 -3
- {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/METADATA +5 -9
- {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/RECORD +67 -63
- {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/WHEEL +1 -1
- qadence/backends/braket/__init__.py +0 -4
- qadence/backends/braket/backend.py +0 -234
- qadence/backends/braket/config.py +0 -22
- qadence/backends/braket/convert_ops.py +0 -116
- qadence/ml_tools/printing.py +0 -153
- qadence/ml_tools/train_grad.py +0 -395
- qadence/ml_tools/train_no_grad.py +0 -199
- qadence/noise/readout.py +0 -218
- {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/licenses/LICENSE +0 -0
qadence/model.py
CHANGED
@@ -24,7 +24,7 @@ from qadence.circuit import QuantumCircuit
|
|
24
24
|
from qadence.engines.differentiable_backend import DifferentiableBackend
|
25
25
|
from qadence.measurements import Measurements
|
26
26
|
from qadence.mitigations import Mitigations
|
27
|
-
from qadence.noise import
|
27
|
+
from qadence.noise import NoiseHandler
|
28
28
|
from qadence.parameters import Parameter
|
29
29
|
from qadence.types import DiffMode, Endianness
|
30
30
|
|
@@ -83,7 +83,7 @@ class QuantumModel(nn.Module):
|
|
83
83
|
backend: BackendName | str = BackendName.PYQTORCH,
|
84
84
|
diff_mode: DiffMode = DiffMode.AD,
|
85
85
|
measurement: Measurements | None = None,
|
86
|
-
noise:
|
86
|
+
noise: NoiseHandler | None = None,
|
87
87
|
mitigation: Mitigations | None = None,
|
88
88
|
configuration: BackendConfiguration | dict | None = None,
|
89
89
|
):
|
@@ -249,7 +249,7 @@ class QuantumModel(nn.Module):
|
|
249
249
|
values: dict[str, torch.Tensor] = {},
|
250
250
|
n_shots: int = 1000,
|
251
251
|
state: torch.Tensor | None = None,
|
252
|
-
noise:
|
252
|
+
noise: NoiseHandler | None = None,
|
253
253
|
mitigation: Mitigations | None = None,
|
254
254
|
endianness: Endianness = Endianness.BIG,
|
255
255
|
) -> list[Counter]:
|
@@ -287,7 +287,7 @@ class QuantumModel(nn.Module):
|
|
287
287
|
observable: list[ConvertedObservable] | ConvertedObservable | None = None,
|
288
288
|
state: Optional[Tensor] = None,
|
289
289
|
measurement: Measurements | None = None,
|
290
|
-
noise:
|
290
|
+
noise: NoiseHandler | None = None,
|
291
291
|
mitigation: Mitigations | None = None,
|
292
292
|
endianness: Endianness = Endianness.BIG,
|
293
293
|
) -> Tensor:
|
@@ -415,7 +415,7 @@ class QuantumModel(nn.Module):
|
|
415
415
|
backend=qm_dict["backend"],
|
416
416
|
diff_mode=qm_dict["diff_mode"],
|
417
417
|
measurement=Measurements._from_dict(qm_dict["measurement"]),
|
418
|
-
noise=
|
418
|
+
noise=NoiseHandler._from_dict(qm_dict["noise"]),
|
419
419
|
configuration=config_factory(qm_dict["backend"], qm_dict["backend_configuration"]),
|
420
420
|
)
|
421
421
|
|
@@ -514,7 +514,7 @@ class QuantumModel(nn.Module):
|
|
514
514
|
if isinstance(file_path, str):
|
515
515
|
file_path = Path(file_path)
|
516
516
|
if os.path.isdir(file_path):
|
517
|
-
from qadence.ml_tools.saveload import get_latest_checkpoint_name
|
517
|
+
from qadence.ml_tools.callbacks.saveload import get_latest_checkpoint_name
|
518
518
|
|
519
519
|
file_path = file_path / get_latest_checkpoint_name(file_path, "model")
|
520
520
|
|
qadence/noise/__init__.py
CHANGED
qadence/noise/protocols.py
CHANGED
@@ -1,54 +1,206 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import
|
4
|
-
from
|
5
|
-
from typing import Callable, Counter, cast
|
3
|
+
from itertools import compress
|
4
|
+
from typing import Any
|
6
5
|
|
7
|
-
|
8
|
-
"readout": "qadence.noise.readout",
|
9
|
-
}
|
6
|
+
from qadence.types import NoiseEnum, NoiseProtocol
|
10
7
|
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
DEPHASING = "dephasing"
|
15
|
-
DEPOLARIZING = "depolarizing"
|
16
|
-
READOUT = "readout"
|
9
|
+
class NoiseHandler:
|
10
|
+
"""A container for multiple sources of noise.
|
17
11
|
|
18
|
-
|
19
|
-
|
20
|
-
|
12
|
+
Note `NoiseProtocol.ANALOG` and `NoiseProtocol.DIGITAL` sources cannot be both present.
|
13
|
+
Also `NoiseProtocol.READOUT` can only be present once as the last noise sources, and only
|
14
|
+
exclusively with `NoiseProtocol.DIGITAL` sources.
|
21
15
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
16
|
+
Args:
|
17
|
+
protocol: The protocol(s) applied. To be defined from `NoiseProtocol`.
|
18
|
+
options: A list of options defining the protocol.
|
19
|
+
For `NoiseProtocol.ANALOG`, options should contain a field `noise_probs`.
|
20
|
+
For `NoiseProtocol.DIGITAL`, options should contain a field `error_probability`.
|
21
|
+
|
22
|
+
Examples:
|
23
|
+
```
|
24
|
+
from qadence import NoiseProtocol, NoiseHandler
|
25
|
+
|
26
|
+
analog_options = {"noise_probs": 0.1}
|
27
|
+
digital_options = {"error_probability": 0.1}
|
28
|
+
readout_options = {"error_probability": 0.1, "seed": 0}
|
29
|
+
|
30
|
+
# single noise sources
|
31
|
+
analog_noise = NoiseHandler(NoiseProtocol.ANALOG.DEPOLARIZING, analog_options)
|
32
|
+
digital_depo_noise = NoiseHandler(NoiseProtocol.DIGITAL.DEPOLARIZING, digital_options)
|
33
|
+
readout_noise = NoiseHandler(NoiseProtocol.READOUT, readout_options)
|
34
|
+
|
35
|
+
# init from multiple sources
|
36
|
+
protocols: list = [NoiseProtocol.DIGITAL.DEPOLARIZING, NoiseProtocol.READOUT]
|
37
|
+
options: list = [digital_options, readout_noise]
|
38
|
+
noise_combination = NoiseHandler(protocols, options)
|
39
|
+
|
40
|
+
# Appending noise sources
|
41
|
+
noise_combination = NoiseHandler(NoiseProtocol.DIGITAL.BITFLIP, digital_options)
|
42
|
+
noise_combination.append([digital_depo_noise, readout_noise])
|
43
|
+
```
|
44
|
+
"""
|
45
|
+
|
46
|
+
def __init__(
|
47
|
+
self,
|
48
|
+
protocol: NoiseEnum | list[NoiseEnum],
|
49
|
+
options: dict | list[dict] = dict(),
|
50
|
+
) -> None:
|
51
|
+
self.protocol = protocol if isinstance(protocol, list) else [protocol]
|
52
|
+
self.options = options if isinstance(options, list) else [options] * len(self.protocol)
|
53
|
+
self.verify_all_protocols()
|
54
|
+
|
55
|
+
def _verify_single_protocol(self, protocol: NoiseEnum, option: dict) -> None:
|
56
|
+
if not isinstance(protocol, NoiseProtocol.READOUT): # type: ignore[arg-type]
|
57
|
+
name_mandatory_option = (
|
58
|
+
"noise_probs" if isinstance(protocol, NoiseProtocol.ANALOG) else "error_probability"
|
59
|
+
)
|
60
|
+
noise_probs = option.get(name_mandatory_option, None)
|
61
|
+
if noise_probs is None:
|
62
|
+
error_txt = f"A `{name_mandatory_option}` option"
|
63
|
+
error_txt += f"should be passed for protocol {protocol}."
|
64
|
+
raise KeyError(error_txt)
|
65
|
+
|
66
|
+
def verify_all_protocols(self) -> None:
|
67
|
+
"""Make sure all protocols are correct in terms and their combination too."""
|
68
|
+
|
69
|
+
if len(self.protocol) == 0:
|
70
|
+
raise ValueError("NoiseHandler should be specified with one valid configuration.")
|
71
|
+
|
72
|
+
if len(self.protocol) != len(self.options):
|
73
|
+
raise ValueError("Specify lists of same length when defining noises.")
|
74
|
+
|
75
|
+
for protocol, option in zip(self.protocol, self.options):
|
76
|
+
self._verify_single_protocol(protocol, option)
|
77
|
+
|
78
|
+
types = [type(p) for p in self.protocol]
|
79
|
+
unique_types = set(types)
|
80
|
+
if NoiseProtocol.DIGITAL in unique_types and NoiseProtocol.ANALOG in unique_types:
|
81
|
+
raise ValueError("Cannot define a config with both Digital and Analog noises.")
|
82
|
+
|
83
|
+
if NoiseProtocol.ANALOG in unique_types:
|
84
|
+
if NoiseProtocol.READOUT in unique_types:
|
85
|
+
raise ValueError("Cannot define a config with both READOUT and Analog noises.")
|
86
|
+
if types.count(NoiseProtocol.ANALOG) > 1:
|
87
|
+
raise ValueError("Multiple Analog Noises are not supported yet.")
|
88
|
+
|
89
|
+
if NoiseProtocol.READOUT in unique_types:
|
90
|
+
if (
|
91
|
+
not isinstance(self.protocol[-1], NoiseProtocol.READOUT)
|
92
|
+
or types.count(NoiseProtocol.READOUT) > 1
|
93
|
+
):
|
94
|
+
raise ValueError("Only define a NoiseHandler with one READOUT as the last Noise.")
|
95
|
+
|
96
|
+
def __repr__(self) -> str:
|
97
|
+
return "\n".join(
|
98
|
+
[
|
99
|
+
f"Noise({protocol}, {str(option)})"
|
100
|
+
for protocol, option in zip(self.protocol, self.options)
|
101
|
+
]
|
102
|
+
)
|
103
|
+
|
104
|
+
def append(self, other: NoiseHandler | list[NoiseHandler]) -> None:
|
105
|
+
"""Append noises.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
other (NoiseHandler | list[NoiseHandler]): The noises to add.
|
109
|
+
"""
|
110
|
+
# To avoid overwriting the noise_sources list if an error is raised, make a copy
|
111
|
+
other_list = other if isinstance(other, list) else [other]
|
112
|
+
protocols = self.protocol[:]
|
113
|
+
options = self.options[:]
|
114
|
+
|
115
|
+
for noise in other_list:
|
116
|
+
protocols += noise.protocol
|
117
|
+
options += noise.options
|
118
|
+
|
119
|
+
# init may raise an error
|
120
|
+
temp_handler = NoiseHandler(protocols, options)
|
121
|
+
# if verify passes, replace protocols and options
|
122
|
+
self.protocol = temp_handler.protocol
|
123
|
+
self.options = temp_handler.options
|
124
|
+
|
125
|
+
def __eq__(self, other: object) -> bool:
|
126
|
+
if not isinstance(other, NoiseHandler):
|
127
|
+
raise TypeError(f"Cant compare {type(self)} to {type(other)}")
|
128
|
+
if isinstance(other, type(self)):
|
129
|
+
protocols_equal = all([p1 == p2 for p1, p2 in zip(self.protocol, other.protocol)])
|
130
|
+
options_equal = all([o1 == o2 for o1, o2 in zip(self.options, other.options)])
|
131
|
+
return protocols_equal and options_equal
|
132
|
+
|
133
|
+
return False
|
29
134
|
|
30
135
|
def _to_dict(self) -> dict:
|
31
|
-
return {
|
136
|
+
return {
|
137
|
+
"protocol": self.protocol,
|
138
|
+
"options": self.options,
|
139
|
+
}
|
32
140
|
|
33
141
|
@classmethod
|
34
|
-
def _from_dict(cls, d: dict) ->
|
35
|
-
if d:
|
36
|
-
return cls(d["protocol"],
|
142
|
+
def _from_dict(cls, d: dict | None) -> NoiseHandler | None:
|
143
|
+
if d is not None and d.get("protocol", None):
|
144
|
+
return cls(d["protocol"], d["options"])
|
37
145
|
return None
|
38
146
|
|
39
147
|
@classmethod
|
40
148
|
def list(cls) -> list:
|
41
149
|
return list(filter(lambda el: not el.startswith("__"), dir(cls)))
|
42
150
|
|
151
|
+
def filter(self, protocol: NoiseEnum) -> NoiseHandler | None:
|
152
|
+
protocol_matches: list = [isinstance(p, protocol) for p in self.protocol] # type: ignore[arg-type]
|
153
|
+
|
154
|
+
# if we have at least a match
|
155
|
+
if True in protocol_matches:
|
156
|
+
return NoiseHandler(
|
157
|
+
list(compress(self.protocol, protocol_matches)),
|
158
|
+
list(compress(self.options, protocol_matches)),
|
159
|
+
)
|
160
|
+
return None
|
161
|
+
|
162
|
+
def bitflip(self, *args: Any, **kwargs: Any) -> NoiseHandler:
|
163
|
+
self.append(NoiseHandler(NoiseProtocol.DIGITAL.BITFLIP, *args, **kwargs))
|
164
|
+
return self
|
165
|
+
|
166
|
+
def phaseflip(self, *args: Any, **kwargs: Any) -> NoiseHandler:
|
167
|
+
self.append(NoiseHandler(NoiseProtocol.DIGITAL.PHASEFLIP, *args, **kwargs))
|
168
|
+
return self
|
169
|
+
|
170
|
+
def digital_depolarizing(self, *args: Any, **kwargs: Any) -> NoiseHandler:
|
171
|
+
self.append(NoiseHandler(NoiseProtocol.DIGITAL.DEPOLARIZING, *args, **kwargs))
|
172
|
+
return self
|
173
|
+
|
174
|
+
def pauli_channel(self, *args: Any, **kwargs: Any) -> NoiseHandler:
|
175
|
+
self.append(NoiseHandler(NoiseProtocol.DIGITAL.PAULI_CHANNEL, *args, **kwargs))
|
176
|
+
return self
|
177
|
+
|
178
|
+
def amplitude_damping(self, *args: Any, **kwargs: Any) -> NoiseHandler:
|
179
|
+
self.append(NoiseHandler(NoiseProtocol.DIGITAL.AMPLITUDE_DAMPING, *args, **kwargs))
|
180
|
+
return self
|
181
|
+
|
182
|
+
def phase_damping(self, *args: Any, **kwargs: Any) -> NoiseHandler:
|
183
|
+
self.append(NoiseHandler(NoiseProtocol.DIGITAL.PHASE_DAMPING, *args, **kwargs))
|
184
|
+
return self
|
185
|
+
|
186
|
+
def generalized_amplitude_damping(self, *args: Any, **kwargs: Any) -> NoiseHandler:
|
187
|
+
self.append(
|
188
|
+
NoiseHandler(NoiseProtocol.DIGITAL.GENERALIZED_AMPLITUDE_DAMPING, *args, **kwargs)
|
189
|
+
)
|
190
|
+
return self
|
191
|
+
|
192
|
+
def analog_depolarizing(self, *args: Any, **kwargs: Any) -> NoiseHandler:
|
193
|
+
self.append(NoiseHandler(NoiseProtocol.ANALOG.DEPOLARIZING, *args, **kwargs))
|
194
|
+
return self
|
195
|
+
|
196
|
+
def dephasing(self, *args: Any, **kwargs: Any) -> NoiseHandler:
|
197
|
+
self.append(NoiseHandler(NoiseProtocol.ANALOG.DEPHASING, *args, **kwargs))
|
198
|
+
return self
|
199
|
+
|
200
|
+
def readout_independent(self, *args: Any, **kwargs: Any) -> NoiseHandler:
|
201
|
+
self.append(NoiseHandler(NoiseProtocol.READOUT.INDEPENDENT, *args, **kwargs))
|
202
|
+
return self
|
43
203
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
# Get the number of qubits from the sample keys.
|
48
|
-
n_qubits = len(list(samples[0].keys())[0])
|
49
|
-
# Get the number of shots from the sample values.
|
50
|
-
n_shots = sum(samples[0].values())
|
51
|
-
noisy_samples: list = error_fn(
|
52
|
-
counters=samples, n_qubits=n_qubits, options=noise.options, n_shots=n_shots
|
53
|
-
)
|
54
|
-
return noisy_samples
|
204
|
+
def readout_correlated(self, *args: Any, **kwargs: Any) -> NoiseHandler:
|
205
|
+
self.append(NoiseHandler(NoiseProtocol.READOUT.CORRELATED, *args, **kwargs))
|
206
|
+
return self
|
@@ -18,6 +18,7 @@ from qadence.blocks.utils import (
|
|
18
18
|
chain,
|
19
19
|
kron,
|
20
20
|
)
|
21
|
+
from qadence.noise import NoiseHandler
|
21
22
|
from qadence.parameters import (
|
22
23
|
Parameter,
|
23
24
|
evaluate,
|
@@ -35,9 +36,9 @@ class CNOT(ControlBlock):
|
|
35
36
|
|
36
37
|
name = OpName.CNOT
|
37
38
|
|
38
|
-
def __init__(self, control: int, target: int) -> None:
|
39
|
+
def __init__(self, control: int, target: int, noise: NoiseHandler | None = None) -> None:
|
39
40
|
self.generator = kron(N(control), X(target) - I(target))
|
40
|
-
super().__init__((control,), X(target))
|
41
|
+
super().__init__((control,), X(target), noise=noise)
|
41
42
|
|
42
43
|
@property
|
43
44
|
def eigenvalues_generator(self) -> Tensor:
|
@@ -63,9 +64,11 @@ class CNOT(ControlBlock):
|
|
63
64
|
class MCZ(ControlBlock):
|
64
65
|
name = OpName.MCZ
|
65
66
|
|
66
|
-
def __init__(
|
67
|
+
def __init__(
|
68
|
+
self, control: tuple[int, ...], target: int, noise: NoiseHandler | None = None
|
69
|
+
) -> None:
|
67
70
|
self.generator = kron(*[N(qubit) for qubit in control], Z(target) - I(target))
|
68
|
-
super().__init__(control, Z(target))
|
71
|
+
super().__init__(control, Z(target), noise=noise)
|
69
72
|
|
70
73
|
@property
|
71
74
|
def eigenvalues_generator(self) -> Tensor:
|
@@ -93,8 +96,8 @@ class CZ(MCZ):
|
|
93
96
|
|
94
97
|
name = OpName.CZ
|
95
98
|
|
96
|
-
def __init__(self, control: int, target: int) -> None:
|
97
|
-
super().__init__((control,), target)
|
99
|
+
def __init__(self, control: int, target: int, noise: NoiseHandler | None = None) -> None:
|
100
|
+
super().__init__((control,), target, noise=noise)
|
98
101
|
|
99
102
|
|
100
103
|
class MCRX(ParametricControlBlock):
|
@@ -105,9 +108,10 @@ class MCRX(ParametricControlBlock):
|
|
105
108
|
control: tuple[int, ...],
|
106
109
|
target: int,
|
107
110
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
111
|
+
noise: NoiseHandler | None = None,
|
108
112
|
) -> None:
|
109
113
|
self.generator = kron(*[N(qubit) for qubit in control], X(target))
|
110
|
-
super().__init__(control, RX(target, parameter))
|
114
|
+
super().__init__(control, RX(target, parameter), noise=noise)
|
111
115
|
|
112
116
|
@classmethod
|
113
117
|
def num_parameters(cls) -> int:
|
@@ -136,8 +140,9 @@ class CRX(MCRX):
|
|
136
140
|
control: int,
|
137
141
|
target: int,
|
138
142
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
143
|
+
noise: NoiseHandler | None = None,
|
139
144
|
):
|
140
|
-
super().__init__((control,), target, parameter)
|
145
|
+
super().__init__((control,), target, parameter, noise=noise)
|
141
146
|
|
142
147
|
|
143
148
|
class MCRY(ParametricControlBlock):
|
@@ -148,9 +153,10 @@ class MCRY(ParametricControlBlock):
|
|
148
153
|
control: tuple[int, ...],
|
149
154
|
target: int,
|
150
155
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
156
|
+
noise: NoiseHandler | None = None,
|
151
157
|
) -> None:
|
152
158
|
self.generator = kron(*[N(qubit) for qubit in control], Y(target))
|
153
|
-
super().__init__(control, RY(target, parameter))
|
159
|
+
super().__init__(control, RY(target, parameter), noise=noise)
|
154
160
|
|
155
161
|
@classmethod
|
156
162
|
def num_parameters(cls) -> int:
|
@@ -175,12 +181,9 @@ class CRY(MCRY):
|
|
175
181
|
name = OpName.CRY
|
176
182
|
|
177
183
|
def __init__(
|
178
|
-
self,
|
179
|
-
control: int,
|
180
|
-
target: int,
|
181
|
-
parameter: TParameter,
|
184
|
+
self, control: int, target: int, parameter: TParameter, noise: NoiseHandler | None = None
|
182
185
|
):
|
183
|
-
super().__init__((control,), target, parameter)
|
186
|
+
super().__init__((control,), target, parameter, noise=noise)
|
184
187
|
|
185
188
|
|
186
189
|
class MCRZ(ParametricControlBlock):
|
@@ -191,9 +194,10 @@ class MCRZ(ParametricControlBlock):
|
|
191
194
|
control: tuple[int, ...],
|
192
195
|
target: int,
|
193
196
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
197
|
+
noise: NoiseHandler | None = None,
|
194
198
|
) -> None:
|
195
199
|
self.generator = kron(*[N(qubit) for qubit in control], Z(target))
|
196
|
-
super().__init__(control, RZ(target, parameter))
|
200
|
+
super().__init__(control, RZ(target, parameter), noise=noise)
|
197
201
|
|
198
202
|
@classmethod
|
199
203
|
def num_parameters(cls) -> int:
|
@@ -222,8 +226,9 @@ class CRZ(MCRZ):
|
|
222
226
|
control: int,
|
223
227
|
target: int,
|
224
228
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
229
|
+
noise: NoiseHandler | None = None,
|
225
230
|
):
|
226
|
-
super().__init__((control,), target, parameter)
|
231
|
+
super().__init__((control,), target, parameter, noise=noise)
|
227
232
|
|
228
233
|
|
229
234
|
class MCPHASE(ParametricControlBlock):
|
@@ -234,9 +239,10 @@ class MCPHASE(ParametricControlBlock):
|
|
234
239
|
control: tuple[int, ...],
|
235
240
|
target: int,
|
236
241
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
242
|
+
noise: NoiseHandler | None = None,
|
237
243
|
) -> None:
|
238
244
|
self.generator = kron(*[N(qubit) for qubit in control], Z(target) - I(target))
|
239
|
-
super().__init__(control, PHASE(target, parameter))
|
245
|
+
super().__init__(control, PHASE(target, parameter), noise=noise)
|
240
246
|
|
241
247
|
@classmethod
|
242
248
|
def num_parameters(cls) -> int:
|
@@ -276,8 +282,9 @@ class CPHASE(MCPHASE):
|
|
276
282
|
control: int,
|
277
283
|
target: int,
|
278
284
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
285
|
+
noise: NoiseHandler | None = None,
|
279
286
|
):
|
280
|
-
super().__init__((control,), target, parameter)
|
287
|
+
super().__init__((control,), target, parameter, noise=noise)
|
281
288
|
|
282
289
|
|
283
290
|
class CSWAP(ControlBlock):
|
@@ -285,7 +292,13 @@ class CSWAP(ControlBlock):
|
|
285
292
|
|
286
293
|
name = OpName.CSWAP
|
287
294
|
|
288
|
-
def __init__(
|
295
|
+
def __init__(
|
296
|
+
self,
|
297
|
+
control: int | tuple[int, ...],
|
298
|
+
target1: int,
|
299
|
+
target2: int,
|
300
|
+
noise: NoiseHandler | None = None,
|
301
|
+
) -> None:
|
289
302
|
if isinstance(control, tuple):
|
290
303
|
control = control[0]
|
291
304
|
|
@@ -303,7 +316,7 @@ class CSWAP(ControlBlock):
|
|
303
316
|
+ kron(a00p, a21, a12)
|
304
317
|
)
|
305
318
|
self.generator = no_effect + swap_effect
|
306
|
-
super().__init__((control,), SWAP(target1, target2))
|
319
|
+
super().__init__((control,), SWAP(target1, target2), noise=noise)
|
307
320
|
|
308
321
|
@property
|
309
322
|
def eigenvalues_generator(self) -> Tensor:
|
@@ -321,9 +334,11 @@ class CSWAP(ControlBlock):
|
|
321
334
|
class Toffoli(ControlBlock):
|
322
335
|
name = OpName.TOFFOLI
|
323
336
|
|
324
|
-
def __init__(
|
337
|
+
def __init__(
|
338
|
+
self, control: tuple[int, ...], target: int, noise: NoiseHandler | None = None
|
339
|
+
) -> None:
|
325
340
|
self.generator = kron(*[N(qubit) for qubit in control], X(target) - I(target))
|
326
|
-
super().__init__(control, X(target))
|
341
|
+
super().__init__(control, X(target), noise=noise)
|
327
342
|
|
328
343
|
@property
|
329
344
|
def n_qubits(self) -> int:
|
qadence/operations/ham_evo.py
CHANGED
@@ -33,29 +33,59 @@ logger = getLogger(__name__)
|
|
33
33
|
|
34
34
|
class HamEvo(TimeEvolutionBlock):
|
35
35
|
"""
|
36
|
-
|
36
|
+
The Hamiltonian evolution operator U(t).
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
For time-independent Hamiltonians the solution is exact:
|
39
|
+
|
40
|
+
U(t) = exp(-iGt)
|
41
|
+
|
42
|
+
where G represents an Hermitian generator, or Hamiltonian and t represents the
|
43
|
+
time parameter. For time-dependent Hamiltonians, the solution is obtained by
|
44
|
+
numerical integration of the Schrodinger equation.
|
41
45
|
|
42
46
|
Arguments:
|
43
|
-
generator:
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
generator: Hamiltonian generator, either symbolic as an AbstractBlock,
|
48
|
+
or as a torch.Tensor or numpy.ndarray.
|
49
|
+
parameter: The time parameter for evolution operator. For the time-independent
|
50
|
+
case, it represents the actual value for which the evolution will be
|
51
|
+
evaluated. For the time-dependent case, it should be an instance of
|
52
|
+
TimeParameter to signal the solver the variable that will be integrated over.
|
53
|
+
qubit_support: The qubits on which the evolution will be performed on. Only
|
54
|
+
required for generators that are not a composition of blocks.
|
55
|
+
duration: (optional) duration of the evolution in case of time-dependent
|
56
|
+
generator. By default, a FeatureParameter with tag "duration" will
|
57
|
+
be initialized, and the value will then be required in the values dict.
|
58
|
+
noise_operators: (optional) the list of jump operators to use when using
|
59
|
+
a shrodinger solver, allowing to perform noisy simulations.
|
47
60
|
|
48
61
|
Examples:
|
49
62
|
|
50
63
|
```python exec="on" source="material-block" result="json"
|
51
|
-
from qadence import
|
64
|
+
from qadence import X, HamEvo, PI, add, run
|
65
|
+
from qadence import FeatureParameter, TimeParameter
|
52
66
|
import torch
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
67
|
+
|
68
|
+
n_qubits = 3
|
69
|
+
|
70
|
+
# Hamiltonian as a block composition
|
71
|
+
hamiltonian = add(X(i) for i in range(n_qubits))
|
72
|
+
hevo = HamEvo(hamiltonian, parameter=torch.rand(2))
|
73
|
+
state = run(hevo)
|
74
|
+
|
75
|
+
# Hamiltonian as a random matrix
|
76
|
+
hamiltonian = torch.rand(2, 2, dtype=torch.complex128)
|
77
|
+
hevo = HamEvo(hamiltonian, parameter=torch.rand(2), qubit_support=(0,))
|
78
|
+
state = run(hevo)
|
79
|
+
|
80
|
+
# Time-dependent Hamiltonian
|
81
|
+
t = TimeParameter("t")
|
82
|
+
hamiltonian = t * add(X(i) for i in range(n_qubits))
|
83
|
+
hevo = HamEvo(hamiltonian, parameter=t)
|
84
|
+
state = run(hevo, values = {"duration": torch.tensor(1.0)})
|
85
|
+
|
86
|
+
# Adding noise operators
|
87
|
+
noise_ops = [X(0)]
|
88
|
+
hevo = HamEvo(hamiltonian, parameter=t, noise_operators=noise_ops)
|
59
89
|
```
|
60
90
|
"""
|
61
91
|
|
@@ -67,21 +97,31 @@ class HamEvo(TimeEvolutionBlock):
|
|
67
97
|
generator: Union[TGenerator, AbstractBlock],
|
68
98
|
parameter: TParameter,
|
69
99
|
qubit_support: tuple[int, ...] = None,
|
70
|
-
duration:
|
100
|
+
duration: TParameter | None = None,
|
101
|
+
noise_operators: list[AbstractBlock] = list(),
|
71
102
|
):
|
72
|
-
|
103
|
+
params = {}
|
73
104
|
if qubit_support is None and not isinstance(generator, AbstractBlock):
|
74
105
|
raise ValueError("You have to supply a qubit support for non-block generators.")
|
75
106
|
super().__init__(qubit_support if qubit_support else generator.qubit_support)
|
76
107
|
if isinstance(generator, AbstractBlock):
|
77
108
|
qubit_support = generator.qubit_support
|
78
109
|
if generator.is_parametric:
|
79
|
-
|
80
|
-
|
81
|
-
if
|
82
|
-
|
83
|
-
|
110
|
+
params = {str(e): e for e in expressions(generator)}
|
111
|
+
if generator.is_time_dependent:
|
112
|
+
if isinstance(duration, str):
|
113
|
+
duration = Parameter(duration, trainable=False)
|
114
|
+
elif duration is None:
|
115
|
+
duration = Parameter("duration", trainable=False)
|
116
|
+
if not generator.is_time_dependent and duration is not None:
|
117
|
+
raise TypeError(
|
118
|
+
"Duration argument is only supported for time-dependent generators."
|
119
|
+
)
|
84
120
|
elif isinstance(generator, torch.Tensor):
|
121
|
+
if duration is not None:
|
122
|
+
raise TypeError(
|
123
|
+
"Duration argument is only supported for time-dependent generators."
|
124
|
+
)
|
85
125
|
msg = "Please provide a square generator."
|
86
126
|
if len(generator.shape) == 2:
|
87
127
|
assert generator.shape[0] == generator.shape[1], msg
|
@@ -94,19 +134,41 @@ class HamEvo(TimeEvolutionBlock):
|
|
94
134
|
In case of a 3D generator, the batch dim\
|
95
135
|
is expected to be at dim 0."
|
96
136
|
)
|
97
|
-
|
137
|
+
params = {str(generator.__hash__()): generator}
|
98
138
|
elif isinstance(generator, (sympy.Basic, sympy.Array)):
|
99
|
-
|
139
|
+
if duration is not None:
|
140
|
+
raise TypeError(
|
141
|
+
"Duration argument is only supported for time-dependent generators."
|
142
|
+
)
|
143
|
+
params = {str(generator): generator}
|
100
144
|
else:
|
101
145
|
raise TypeError(
|
102
146
|
f"Generator of type {type(generator)} not supported.\
|
103
147
|
If you're using a numpy.ndarray, please cast it to a torch tensor."
|
104
148
|
)
|
105
|
-
|
106
|
-
|
149
|
+
if duration is not None:
|
150
|
+
params = {"duration": Parameter(duration), **params}
|
151
|
+
params = {"parameter": Parameter(parameter), **params}
|
152
|
+
self.parameters = ParamMap(**params)
|
153
|
+
self.time_param = parameter
|
107
154
|
self.generator = generator
|
108
155
|
self.duration = duration
|
109
156
|
|
157
|
+
if len(noise_operators) > 0:
|
158
|
+
if not all(
|
159
|
+
[
|
160
|
+
len(set(op.qubit_support + self.qubit_support) - set(self.qubit_support)) == 0
|
161
|
+
for op in noise_operators
|
162
|
+
]
|
163
|
+
):
|
164
|
+
raise ValueError(
|
165
|
+
"Noise operators should be defined"
|
166
|
+
" over the same or a subset of the qubit support"
|
167
|
+
)
|
168
|
+
if True in [op.is_parametric for op in noise_operators]:
|
169
|
+
raise ValueError("Parametric operators are not supported")
|
170
|
+
self.noise_operators = noise_operators
|
171
|
+
|
110
172
|
@classmethod
|
111
173
|
def num_parameters(cls) -> int:
|
112
174
|
return 2
|