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
@@ -6,7 +6,7 @@ from collections.abc import Callable
|
|
6
6
|
from math import isclose, pi
|
7
7
|
from typing import Union
|
8
8
|
|
9
|
-
from sympy import Function, acos
|
9
|
+
from sympy import Basic, Function, acos
|
10
10
|
|
11
11
|
from qadence.blocks import AbstractBlock, KronBlock, chain, kron, tag
|
12
12
|
from qadence.logger import get_logger
|
@@ -36,65 +36,12 @@ RS_FUNC_DICT = {
|
|
36
36
|
}
|
37
37
|
|
38
38
|
|
39
|
-
def
|
40
|
-
|
41
|
-
support: tuple[int, ...] | None = None,
|
39
|
+
def fm_parameter(
|
40
|
+
fm_type: BasisSet | type[Function] | str,
|
42
41
|
param: Parameter | str = "phi",
|
43
|
-
op: RotationTypes = RX,
|
44
|
-
fm_type: BasisSet | type[Function] | str = BasisSet.FOURIER,
|
45
|
-
reupload_scaling: ReuploadScaling | Callable | str = ReuploadScaling.CONSTANT,
|
46
42
|
feature_range: tuple[float, float] | None = None,
|
47
43
|
target_range: tuple[float, float] | None = None,
|
48
|
-
|
49
|
-
) -> KronBlock:
|
50
|
-
"""Construct a feature map of a given type.
|
51
|
-
|
52
|
-
Arguments:
|
53
|
-
n_qubits: Number of qubits the feature map covers. Results in `support=range(n_qubits)`.
|
54
|
-
support: Puts one feature-encoding rotation gate on every qubit in `support`. n_qubits in
|
55
|
-
this case specifies the total overall qubits of the circuit, which may be wider than the
|
56
|
-
support itself, but not narrower.
|
57
|
-
param: Parameter of the feature map; you can pass a string or Parameter;
|
58
|
-
it will be set as non-trainable (FeatureParameter) regardless.
|
59
|
-
op: Rotation operation of the feature map; choose from RX, RY, RZ or PHASE.
|
60
|
-
fm_type: Basis set for data encoding; choose from `BasisSet.FOURIER` for Fourier
|
61
|
-
encoding, or `BasisSet.CHEBYSHEV` for Chebyshev polynomials of the first kind.
|
62
|
-
reupload_scaling: how the feature map scales the data that is re-uploaded for each qubit.
|
63
|
-
choose from `ReuploadScaling` enumeration or provide your own function with a single
|
64
|
-
int as input and int or float as output.
|
65
|
-
feature_range: range of data that the input data is assumed to come from.
|
66
|
-
target_range: range of data the data encoder assumes as the natural range. For example,
|
67
|
-
in Chebyshev polynomials it is (-1, 1), while for Fourier it may be chosen as (0, 2*pi).
|
68
|
-
multiplier: overall multiplier; this is useful for reuploading the feature map serially with
|
69
|
-
different scalings; can be a number or parameter/expression.
|
70
|
-
|
71
|
-
Example:
|
72
|
-
```python exec="on" source="material-block" result="json"
|
73
|
-
from qadence import feature_map, BasisSet, ReuploadScaling
|
74
|
-
|
75
|
-
fm = feature_map(3, fm_type=BasisSet.FOURIER)
|
76
|
-
print(f"{fm = }")
|
77
|
-
|
78
|
-
fm = feature_map(3, fm_type=BasisSet.CHEBYSHEV)
|
79
|
-
print(f"{fm = }")
|
80
|
-
|
81
|
-
fm = feature_map(3, fm_type=BasisSet.FOURIER, reupload_scaling = ReuploadScaling.TOWER)
|
82
|
-
print(f"{fm = }")
|
83
|
-
```
|
84
|
-
"""
|
85
|
-
|
86
|
-
# Process input
|
87
|
-
if support is None:
|
88
|
-
support = tuple(range(n_qubits))
|
89
|
-
elif len(support) != n_qubits:
|
90
|
-
raise ValueError("Wrong qubit support supplied")
|
91
|
-
|
92
|
-
if op not in ROTATIONS:
|
93
|
-
raise ValueError(
|
94
|
-
f"Operation {op} not supported. "
|
95
|
-
f"Please provide one from {[rot.__name__ for rot in ROTATIONS]}."
|
96
|
-
)
|
97
|
-
|
44
|
+
) -> Parameter | Basic:
|
98
45
|
# Backwards compatibility
|
99
46
|
if fm_type in ("fourier", "chebyshev", "tower"):
|
100
47
|
logger.warning(
|
@@ -108,7 +55,6 @@ def feature_map(
|
|
108
55
|
fm_type = BasisSet.CHEBYSHEV
|
109
56
|
elif fm_type == "tower":
|
110
57
|
fm_type = BasisSet.CHEBYSHEV
|
111
|
-
reupload_scaling = ReuploadScaling.TOWER
|
112
58
|
|
113
59
|
if isinstance(param, Parameter):
|
114
60
|
fparam = param
|
@@ -144,8 +90,12 @@ def feature_map(
|
|
144
90
|
"the given feature parameter with."
|
145
91
|
)
|
146
92
|
|
147
|
-
|
93
|
+
return transformed_feature
|
94
|
+
|
148
95
|
|
96
|
+
def fm_reupload_scaling_fn(
|
97
|
+
reupload_scaling: ReuploadScaling | Callable | str = ReuploadScaling.CONSTANT,
|
98
|
+
) -> tuple[Callable, str]:
|
149
99
|
# Set reupload scaling function
|
150
100
|
if callable(reupload_scaling):
|
151
101
|
rs_func = reupload_scaling
|
@@ -163,8 +113,82 @@ def feature_map(
|
|
163
113
|
else:
|
164
114
|
rs_tag = reupload_scaling
|
165
115
|
|
116
|
+
return rs_func, rs_tag
|
117
|
+
|
118
|
+
|
119
|
+
def feature_map(
|
120
|
+
n_qubits: int,
|
121
|
+
support: tuple[int, ...] | None = None,
|
122
|
+
param: Parameter | str = "phi",
|
123
|
+
op: RotationTypes = RX,
|
124
|
+
fm_type: BasisSet | type[Function] | str = BasisSet.FOURIER,
|
125
|
+
reupload_scaling: ReuploadScaling | Callable | str = ReuploadScaling.CONSTANT,
|
126
|
+
feature_range: tuple[float, float] | None = None,
|
127
|
+
target_range: tuple[float, float] | None = None,
|
128
|
+
multiplier: Parameter | TParameter | None = None,
|
129
|
+
) -> KronBlock:
|
130
|
+
"""Construct a feature map of a given type.
|
131
|
+
|
132
|
+
Arguments:
|
133
|
+
n_qubits: Number of qubits the feature map covers. Results in `support=range(n_qubits)`.
|
134
|
+
support: Puts one feature-encoding rotation gate on every qubit in `support`. n_qubits in
|
135
|
+
this case specifies the total overall qubits of the circuit, which may be wider than the
|
136
|
+
support itself, but not narrower.
|
137
|
+
param: Parameter of the feature map; you can pass a string or Parameter;
|
138
|
+
it will be set as non-trainable (FeatureParameter) regardless.
|
139
|
+
op: Rotation operation of the feature map; choose from RX, RY, RZ or PHASE.
|
140
|
+
fm_type: Basis set for data encoding; choose from `BasisSet.FOURIER` for Fourier
|
141
|
+
encoding, or `BasisSet.CHEBYSHEV` for Chebyshev polynomials of the first kind.
|
142
|
+
reupload_scaling: how the feature map scales the data that is re-uploaded for each qubit.
|
143
|
+
choose from `ReuploadScaling` enumeration or provide your own function with a single
|
144
|
+
int as input and int or float as output.
|
145
|
+
feature_range: range of data that the input data is assumed to come from.
|
146
|
+
target_range: range of data the data encoder assumes as the natural range. For example,
|
147
|
+
in Chebyshev polynomials it is (-1, 1), while for Fourier it may be chosen as (0, 2*pi).
|
148
|
+
multiplier: overall multiplier; this is useful for reuploading the feature map serially with
|
149
|
+
different scalings; can be a number or parameter/expression.
|
150
|
+
|
151
|
+
Example:
|
152
|
+
```python exec="on" source="material-block" result="json"
|
153
|
+
from qadence import feature_map, BasisSet, ReuploadScaling
|
154
|
+
|
155
|
+
fm = feature_map(3, fm_type=BasisSet.FOURIER)
|
156
|
+
print(f"{fm = }")
|
157
|
+
|
158
|
+
fm = feature_map(3, fm_type=BasisSet.CHEBYSHEV)
|
159
|
+
print(f"{fm = }")
|
160
|
+
|
161
|
+
fm = feature_map(3, fm_type=BasisSet.FOURIER, reupload_scaling = ReuploadScaling.TOWER)
|
162
|
+
print(f"{fm = }")
|
163
|
+
```
|
164
|
+
"""
|
165
|
+
|
166
|
+
# Process input
|
167
|
+
if support is None:
|
168
|
+
support = tuple(range(n_qubits))
|
169
|
+
elif len(support) != n_qubits:
|
170
|
+
raise ValueError("Wrong qubit support supplied")
|
171
|
+
|
172
|
+
if op not in ROTATIONS:
|
173
|
+
raise ValueError(
|
174
|
+
f"Operation {op} not supported. "
|
175
|
+
f"Please provide one from {[rot.__name__ for rot in ROTATIONS]}."
|
176
|
+
)
|
177
|
+
|
178
|
+
transformed_feature = fm_parameter(
|
179
|
+
fm_type, param, feature_range=feature_range, target_range=target_range
|
180
|
+
)
|
181
|
+
|
182
|
+
# Backwards compatibility
|
183
|
+
if fm_type == "tower":
|
184
|
+
logger.warning("Forcing reupload scaling strategy to TOWER")
|
185
|
+
reupload_scaling = ReuploadScaling.TOWER
|
186
|
+
|
187
|
+
basis_tag = fm_type.value if isinstance(fm_type, BasisSet) else str(fm_type)
|
188
|
+
rs_func, rs_tag = fm_reupload_scaling_fn(reupload_scaling)
|
189
|
+
|
166
190
|
# Set overall multiplier
|
167
|
-
multiplier = 1 if multiplier is None else multiplier
|
191
|
+
multiplier = 1 if multiplier is None else Parameter(multiplier)
|
168
192
|
|
169
193
|
# Build feature map
|
170
194
|
op_list = []
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import
|
4
|
-
from typing import List, Tuple, Type, Union
|
3
|
+
from typing import List, Type, Union
|
5
4
|
|
6
5
|
import numpy as np
|
7
6
|
from torch import Tensor, double, ones, rand
|
@@ -57,8 +56,7 @@ def hamiltonian_factory(
|
|
57
56
|
interaction_strength: TArray | str | None = None,
|
58
57
|
detuning_strength: TArray | str | None = None,
|
59
58
|
random_strength: bool = False,
|
60
|
-
|
61
|
-
use_complete_graph: bool = False,
|
59
|
+
use_all_node_pairs: bool = False,
|
62
60
|
) -> AbstractBlock:
|
63
61
|
"""
|
64
62
|
General Hamiltonian creation function.
|
@@ -80,9 +78,8 @@ def hamiltonian_factory(
|
|
80
78
|
Alternatively, some string "x" can be passed, which will create a parameterized
|
81
79
|
detuning for each qubit, each labelled as `"x_i"`.
|
82
80
|
random_strength: set random interaction and detuning strengths between -1 and 1.
|
83
|
-
|
84
|
-
|
85
|
-
independent of the edges in the register. Useful for defining Hamiltonians
|
81
|
+
use_all_node_pairs: computes an interaction term for every pair of nodes in the graph,
|
82
|
+
independent of the edge topology in the register. Useful for defining Hamiltonians
|
86
83
|
where the interaction strength decays with the distance.
|
87
84
|
|
88
85
|
Examples:
|
@@ -121,12 +118,9 @@ def hamiltonian_factory(
|
|
121
118
|
register = Register(register) if isinstance(register, int) else register
|
122
119
|
|
123
120
|
# Get interaction function
|
124
|
-
|
125
|
-
int_fn = INTERACTION_DICT
|
126
|
-
|
127
|
-
if interaction is None:
|
128
|
-
pass
|
129
|
-
else:
|
121
|
+
if interaction is not None:
|
122
|
+
int_fn = INTERACTION_DICT.get(interaction, None)
|
123
|
+
if int_fn is None:
|
130
124
|
raise KeyError(f"Interaction {interaction} not supported.")
|
131
125
|
|
132
126
|
# Check single-qubit detuning
|
@@ -134,37 +128,27 @@ def hamiltonian_factory(
|
|
134
128
|
raise TypeError(f"Detuning of type {type(detuning)} not supported.")
|
135
129
|
|
136
130
|
# Pre-process detuning and interaction strengths and update register
|
137
|
-
|
138
|
-
register, detuning_strength, "nodes",
|
131
|
+
detuning_strength_array = _preprocess_strengths(
|
132
|
+
register, detuning_strength, "nodes", random_strength
|
139
133
|
)
|
140
134
|
|
141
|
-
edge_str = "
|
142
|
-
|
143
|
-
register, interaction_strength, edge_str,
|
135
|
+
edge_str = "all_node_pairs" if use_all_node_pairs else "edges"
|
136
|
+
interaction_strength_array = _preprocess_strengths(
|
137
|
+
register, interaction_strength, edge_str, random_strength
|
144
138
|
)
|
145
139
|
|
146
|
-
if (not has_detuning_strength) or force_update:
|
147
|
-
register = _update_detuning_strength(register, detuning_strength)
|
148
|
-
|
149
|
-
if (not has_interaction_strength) or force_update:
|
150
|
-
register = _update_interaction_strength(register, interaction_strength, use_complete_graph)
|
151
|
-
|
152
140
|
# Create single-qubit detunings:
|
153
141
|
single_qubit_terms: List[AbstractBlock] = []
|
154
142
|
if detuning is not None:
|
155
|
-
for node in register.nodes:
|
156
|
-
|
157
|
-
strength_sq = register.nodes[node]["strength"]
|
158
|
-
single_qubit_terms.append(strength_sq * block_sq)
|
143
|
+
for strength, node in zip(detuning_strength_array, register.nodes):
|
144
|
+
single_qubit_terms.append(strength * detuning(node))
|
159
145
|
|
160
146
|
# Create two-qubit interactions:
|
161
147
|
two_qubit_terms: List[AbstractBlock] = []
|
162
|
-
edge_data = register.
|
163
|
-
if interaction is not None:
|
164
|
-
for edge in edge_data:
|
165
|
-
|
166
|
-
strength_tq = edge_data[edge]["strength"]
|
167
|
-
two_qubit_terms.append(strength_tq * block_tq)
|
148
|
+
edge_data = register.all_node_pairs if use_all_node_pairs else register.edges
|
149
|
+
if interaction is not None and int_fn is not None:
|
150
|
+
for strength, edge in zip(interaction_strength_array, edge_data):
|
151
|
+
two_qubit_terms.append(strength * int_fn(*edge))
|
168
152
|
|
169
153
|
return add(*single_qubit_terms, *two_qubit_terms)
|
170
154
|
|
@@ -173,22 +157,13 @@ def _preprocess_strengths(
|
|
173
157
|
register: Register,
|
174
158
|
strength: TArray | str | None,
|
175
159
|
nodes_or_edges: str,
|
176
|
-
force_update: bool,
|
177
160
|
random_strength: bool,
|
178
|
-
) ->
|
161
|
+
) -> Tensor | list:
|
179
162
|
data = getattr(register, nodes_or_edges)
|
180
163
|
|
181
164
|
# Useful for error messages:
|
182
165
|
strength_target = "detuning" if nodes_or_edges == "nodes" else "interaction"
|
183
166
|
|
184
|
-
# First we check if strength values already exist in the register
|
185
|
-
has_strength = any(["strength" in data[i] for i in data])
|
186
|
-
if has_strength and not force_update:
|
187
|
-
if strength is not None:
|
188
|
-
logger.warning(
|
189
|
-
"Register already includes " + strength_target + " strengths. "
|
190
|
-
"Skipping update. Use `force_update = True` to override them."
|
191
|
-
)
|
192
167
|
# Next we process the strength given in the input arguments
|
193
168
|
if strength is None:
|
194
169
|
if random_strength:
|
@@ -202,8 +177,11 @@ def _preprocess_strengths(
|
|
202
177
|
message = "Array of " + strength_target + " strengths has incorrect size."
|
203
178
|
raise ValueError(message)
|
204
179
|
elif isinstance(strength, str):
|
205
|
-
|
206
|
-
|
180
|
+
prefix = strength
|
181
|
+
if nodes_or_edges == "nodes":
|
182
|
+
strength = [prefix + f"_{node}" for node in data]
|
183
|
+
if nodes_or_edges in ["edges", "all_node_pairs"]:
|
184
|
+
strength = [prefix + f"_{edge[0]}{edge[1]}" for edge in data]
|
207
185
|
else:
|
208
186
|
# If not of the accepted types ARRAYS or str, we error out
|
209
187
|
raise TypeError(
|
@@ -212,69 +190,27 @@ def _preprocess_strengths(
|
|
212
190
|
"parameterized " + strength_target + "s."
|
213
191
|
)
|
214
192
|
|
215
|
-
return
|
216
|
-
|
217
|
-
|
218
|
-
def _update_detuning_strength(register: Register, detuning_strength: TArray | str) -> Register:
|
219
|
-
for node in register.nodes:
|
220
|
-
if isinstance(detuning_strength, str):
|
221
|
-
register.nodes[node]["strength"] = detuning_strength + f"_{node}"
|
222
|
-
elif isinstance(detuning_strength, ARRAYS):
|
223
|
-
register.nodes[node]["strength"] = detuning_strength[node]
|
224
|
-
return register
|
225
|
-
|
226
|
-
|
227
|
-
def _update_interaction_strength(
|
228
|
-
register: Register, interaction_strength: TArray | str, use_complete_graph: bool
|
229
|
-
) -> Register:
|
230
|
-
edge_data = register.all_edges if use_complete_graph else register.edges
|
231
|
-
for idx, edge in enumerate(edge_data):
|
232
|
-
if isinstance(interaction_strength, str):
|
233
|
-
edge_data[edge]["strength"] = interaction_strength + f"_{edge[0]}{edge[1]}"
|
234
|
-
elif isinstance(interaction_strength, ARRAYS):
|
235
|
-
edge_data[edge]["strength"] = interaction_strength[idx]
|
236
|
-
return register
|
237
|
-
|
193
|
+
return strength
|
238
194
|
|
239
|
-
# FIXME: Previous hamiltonian / observable functions, now refactored, to be deprecated:
|
240
195
|
|
241
|
-
|
196
|
+
def total_magnetization(n_qubits: int, z_terms: np.ndarray | list | None = None) -> AbstractBlock:
|
197
|
+
return hamiltonian_factory(n_qubits, detuning=Z, detuning_strength=z_terms)
|
242
198
|
|
243
199
|
|
244
200
|
def single_z(qubit: int = 0, z_coefficient: float = 1.0) -> AbstractBlock:
|
245
|
-
message = DEPRECATION_MESSAGE + "Please use `z_coefficient * Z(qubit)` directly."
|
246
|
-
warnings.warn(message, FutureWarning)
|
247
201
|
return Z(qubit) * z_coefficient
|
248
202
|
|
249
203
|
|
250
|
-
def total_magnetization(n_qubits: int, z_terms: np.ndarray | list | None = None) -> AbstractBlock:
|
251
|
-
message = (
|
252
|
-
DEPRECATION_MESSAGE
|
253
|
-
+ "Please use `hamiltonian_factory(n_qubits, detuning=Z, node_coeff=z_terms)`."
|
254
|
-
)
|
255
|
-
warnings.warn(message, FutureWarning)
|
256
|
-
return hamiltonian_factory(n_qubits, detuning=Z, detuning_strength=z_terms)
|
257
|
-
|
258
|
-
|
259
204
|
def zz_hamiltonian(
|
260
205
|
n_qubits: int,
|
261
206
|
z_terms: np.ndarray | None = None,
|
262
207
|
zz_terms: np.ndarray | None = None,
|
263
208
|
) -> AbstractBlock:
|
264
|
-
message = (
|
265
|
-
DEPRECATION_MESSAGE
|
266
|
-
+ """
|
267
|
-
Please use `hamiltonian_factory(n_qubits, Interaction.ZZ, Z, interaction_strength, z_terms)`. \
|
268
|
-
Note that the argument `zz_terms` in this function is a 2D array of size `(n_qubits, n_qubits)`, \
|
269
|
-
while `interaction_strength` is expected as a 1D array of size `0.5 * n_qubits * (n_qubits - 1)`."""
|
270
|
-
)
|
271
|
-
warnings.warn(message, FutureWarning)
|
272
209
|
if zz_terms is not None:
|
273
210
|
register = Register(n_qubits)
|
274
211
|
interaction_strength = [zz_terms[edge[0], edge[1]] for edge in register.edges]
|
275
212
|
else:
|
276
213
|
interaction_strength = None
|
277
|
-
|
278
214
|
return hamiltonian_factory(n_qubits, Interaction.ZZ, Z, interaction_strength, z_terms)
|
279
215
|
|
280
216
|
|
@@ -284,13 +220,6 @@ def ising_hamiltonian(
|
|
284
220
|
z_terms: np.ndarray | None = None,
|
285
221
|
zz_terms: np.ndarray | None = None,
|
286
222
|
) -> AbstractBlock:
|
287
|
-
message = (
|
288
|
-
DEPRECATION_MESSAGE
|
289
|
-
+ """
|
290
|
-
You can build a general transverse field ising model with the `hamiltonian_factory` function. \
|
291
|
-
Check the hamiltonian construction tutorial in the documentation for more information."""
|
292
|
-
)
|
293
|
-
warnings.warn(message, FutureWarning)
|
294
223
|
zz_ham = zz_hamiltonian(n_qubits, z_terms=z_terms, zz_terms=zz_terms)
|
295
224
|
x_ham = hamiltonian_factory(n_qubits, detuning=X, detuning_strength=x_terms)
|
296
225
|
return zz_ham + x_ham
|
@@ -0,0 +1,113 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Callable
|
4
|
+
|
5
|
+
import numpy as np
|
6
|
+
from sympy import Basic, Function
|
7
|
+
|
8
|
+
from qadence.blocks import AnalogBlock, KronBlock, kron
|
9
|
+
from qadence.constructors.feature_maps import fm_parameter
|
10
|
+
from qadence.logger import get_logger
|
11
|
+
from qadence.operations import AnalogRot, AnalogRX, AnalogRY, AnalogRZ
|
12
|
+
from qadence.parameters import FeatureParameter, Parameter, VariationalParameter
|
13
|
+
from qadence.types import BasisSet, ReuploadScaling, TParameter
|
14
|
+
|
15
|
+
logger = get_logger(__file__)
|
16
|
+
|
17
|
+
AnalogRotationTypes = [AnalogRX, AnalogRY, AnalogRZ]
|
18
|
+
|
19
|
+
|
20
|
+
def rydberg_feature_map(
|
21
|
+
n_qubits: int,
|
22
|
+
param: str = "phi",
|
23
|
+
max_abs_detuning: float = 2 * np.pi * 10,
|
24
|
+
weights: list[float] | None = None,
|
25
|
+
) -> KronBlock:
|
26
|
+
"""Feature map using semi-local addressing patterns.
|
27
|
+
|
28
|
+
If not weights are specified, variational parameters are created
|
29
|
+
for the pattern
|
30
|
+
|
31
|
+
Args:
|
32
|
+
n_qubits (int): number of qubits
|
33
|
+
param: the name of the feature parameter
|
34
|
+
max_abs_detuning: maximum value of absolute detuning for each qubit. Defaulted at 10 MHz.
|
35
|
+
weights: a list of wegiths to assign to each qubit parameter in the feature map
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
The block representing the feature map
|
39
|
+
"""
|
40
|
+
|
41
|
+
tower_coeffs: list[float | Parameter]
|
42
|
+
tower_coeffs = (
|
43
|
+
[VariationalParameter(f"w_{param}_{i}") for i in range(n_qubits)]
|
44
|
+
if weights is None
|
45
|
+
else weights
|
46
|
+
)
|
47
|
+
tower_detuning = max_abs_detuning / (sum(tower_coeffs[i] for i in range(n_qubits)))
|
48
|
+
|
49
|
+
param = FeatureParameter(param)
|
50
|
+
duration = 1000 * param / tower_detuning
|
51
|
+
return kron(
|
52
|
+
AnalogRot(
|
53
|
+
duration=duration,
|
54
|
+
delta=-tower_detuning * tower_coeffs[i],
|
55
|
+
phase=0.0,
|
56
|
+
qubit_support=(i,),
|
57
|
+
)
|
58
|
+
for i in range(n_qubits)
|
59
|
+
)
|
60
|
+
|
61
|
+
|
62
|
+
def rydberg_tower_feature_map(
|
63
|
+
n_qubits: int, param: str = "phi", max_abs_detuning: float = 2 * np.pi * 10
|
64
|
+
) -> KronBlock:
|
65
|
+
weights = list(np.arange(1, n_qubits + 1))
|
66
|
+
return rydberg_feature_map(
|
67
|
+
n_qubits, param=param, max_abs_detuning=max_abs_detuning, weights=weights
|
68
|
+
)
|
69
|
+
|
70
|
+
|
71
|
+
def analog_feature_map(
|
72
|
+
param: str = "phi",
|
73
|
+
op: Callable[[Parameter | Basic], AnalogBlock] = AnalogRX,
|
74
|
+
fm_type: BasisSet | type[Function] | str = BasisSet.FOURIER,
|
75
|
+
reupload_scaling: ReuploadScaling | Callable | str = ReuploadScaling.CONSTANT,
|
76
|
+
feature_range: tuple[float, float] | None = None,
|
77
|
+
target_range: tuple[float, float] | None = None,
|
78
|
+
multiplier: Parameter | TParameter | None = None,
|
79
|
+
) -> AnalogBlock:
|
80
|
+
"""Generate a fully analog feature map.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
param: Parameter of the feature map; you can pass a string or Parameter;
|
84
|
+
it will be set as non-trainable (FeatureParameter) regardless.
|
85
|
+
op: type of operation. Choose among AnalogRX, AnalogRY, AnalogRZ or a custom
|
86
|
+
callable function returning an AnalogBlock instance
|
87
|
+
fm_type: Basis set for data encoding; choose from `BasisSet.FOURIER` for Fourier
|
88
|
+
encoding, or `BasisSet.CHEBYSHEV` for Chebyshev polynomials of the first kind.
|
89
|
+
reupload_scaling: how the feature map scales the data that is re-uploaded. Given that
|
90
|
+
this feature map uses analog rotations, the reuploading works by simply
|
91
|
+
adding additional operations with different scaling factors in the parameter.
|
92
|
+
Choose from `ReuploadScaling` enumeration, currently only CONSTANT works,
|
93
|
+
or provide your own function with the first argument being the given
|
94
|
+
operation `op` and the second argument the feature parameter
|
95
|
+
feature_range: range of data that the input data is assumed to come from.
|
96
|
+
target_range: range of data the data encoder assumes as the natural range. For example,
|
97
|
+
in Chebyshev polynomials it is (-1, 1), while for Fourier it may be chosen as (0, 2*pi).
|
98
|
+
multiplier: overall multiplier; this is useful for reuploading the feature map serially with
|
99
|
+
different scalings; can be a number or parameter/expression.
|
100
|
+
"""
|
101
|
+
transformed_feature = fm_parameter(
|
102
|
+
fm_type, param, feature_range=feature_range, target_range=target_range
|
103
|
+
)
|
104
|
+
multiplier = 1.0 if multiplier is None else Parameter(multiplier)
|
105
|
+
|
106
|
+
if callable(reupload_scaling):
|
107
|
+
return reupload_scaling(op, multiplier * transformed_feature) # type: ignore[no-any-return]
|
108
|
+
elif reupload_scaling == ReuploadScaling.CONSTANT:
|
109
|
+
return op(multiplier * transformed_feature)
|
110
|
+
# TODO: implement tower scaling by reuploading multiple times
|
111
|
+
# using different analog rotations
|
112
|
+
else:
|
113
|
+
raise NotImplementedError(f"Reupload scaling {str(reupload_scaling)} not implemented!")
|
qadence/divergences.py
CHANGED
@@ -36,3 +36,15 @@ def js_divergence(counter_p: Counter, counter_q: Counter) -> float:
|
|
36
36
|
entropy_p = shannon_entropy(counter_p)
|
37
37
|
entropy_q = shannon_entropy(counter_q)
|
38
38
|
return float(average_entropy - (entropy_p + entropy_q) / 2.0)
|
39
|
+
|
40
|
+
|
41
|
+
def norm_difference(counter_p: Counter, counter_q: Counter) -> float:
|
42
|
+
# Normalise counters
|
43
|
+
|
44
|
+
counter_p = np.array([v for v in counter_p.values()])
|
45
|
+
counter_q = np.array([v for v in counter_q.values()])
|
46
|
+
|
47
|
+
prob_p = counter_p / np.sum(counter_p)
|
48
|
+
prob_q = counter_q / np.sum(counter_q)
|
49
|
+
|
50
|
+
return float(np.linalg.norm(prob_p - prob_q))
|
File without changes
|
@@ -0,0 +1,152 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from abc import ABC, abstractmethod
|
4
|
+
from collections import Counter
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from typing import Any, Callable
|
7
|
+
|
8
|
+
from qadence.backend import Backend, Converted, ConvertedCircuit, ConvertedObservable
|
9
|
+
from qadence.blocks import AbstractBlock, PrimitiveBlock
|
10
|
+
from qadence.blocks.utils import uuid_to_block
|
11
|
+
from qadence.circuit import QuantumCircuit
|
12
|
+
from qadence.measurements import Measurements
|
13
|
+
from qadence.mitigations import Mitigations
|
14
|
+
from qadence.noise import Noise
|
15
|
+
from qadence.types import ArrayLike, DiffMode, Endianness, Engine, ParamDictType
|
16
|
+
|
17
|
+
|
18
|
+
@dataclass(frozen=True, eq=True)
|
19
|
+
class DifferentiableBackend(ABC):
|
20
|
+
"""The abstract class which wraps any (non)-natively differentiable QuantumBackend.
|
21
|
+
|
22
|
+
in an automatic differentiation engine.
|
23
|
+
|
24
|
+
Arguments:
|
25
|
+
backend: An instance of the QuantumBackend type perform execution.
|
26
|
+
engine: Which automatic differentiation engine the QuantumBackend runs on.
|
27
|
+
diff_mode: A differentiable mode supported by the differentiation engine.
|
28
|
+
"""
|
29
|
+
|
30
|
+
backend: Backend
|
31
|
+
engine: Engine
|
32
|
+
diff_mode: DiffMode
|
33
|
+
|
34
|
+
# TODO: Add differentiable overlap calculation
|
35
|
+
_overlap: Callable = None # type: ignore [assignment]
|
36
|
+
|
37
|
+
def sample(
|
38
|
+
self,
|
39
|
+
circuit: ConvertedCircuit,
|
40
|
+
param_values: ParamDictType = {},
|
41
|
+
n_shots: int = 100,
|
42
|
+
state: ArrayLike | None = None,
|
43
|
+
noise: Noise | None = None,
|
44
|
+
mitigation: Mitigations | None = None,
|
45
|
+
endianness: Endianness = Endianness.BIG,
|
46
|
+
) -> list[Counter]:
|
47
|
+
"""Sample bitstring from the registered circuit.
|
48
|
+
|
49
|
+
Arguments:
|
50
|
+
circuit: A backend native quantum circuit to be executed.
|
51
|
+
param_values: The values of the parameters after embedding
|
52
|
+
n_shots: The number of shots. Defaults to 1.
|
53
|
+
state: Initial state.
|
54
|
+
noise: A noise model to use.
|
55
|
+
mitigation: A mitigation protocol to apply to noisy samples.
|
56
|
+
endianness: Endianness of the resulting bitstrings.
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
An iterable with all the sampled bitstrings
|
60
|
+
"""
|
61
|
+
|
62
|
+
return self.backend.sample(
|
63
|
+
circuit=circuit,
|
64
|
+
param_values=param_values,
|
65
|
+
n_shots=n_shots,
|
66
|
+
state=state,
|
67
|
+
noise=noise,
|
68
|
+
mitigation=mitigation,
|
69
|
+
endianness=endianness,
|
70
|
+
)
|
71
|
+
|
72
|
+
def run(
|
73
|
+
self,
|
74
|
+
circuit: ConvertedCircuit,
|
75
|
+
param_values: ParamDictType = {},
|
76
|
+
state: ArrayLike | None = None,
|
77
|
+
endianness: Endianness = Endianness.BIG,
|
78
|
+
) -> ArrayLike:
|
79
|
+
"""Run on the underlying backend."""
|
80
|
+
return self.backend.run(
|
81
|
+
circuit=circuit, param_values=param_values, state=state, endianness=endianness
|
82
|
+
)
|
83
|
+
|
84
|
+
@abstractmethod
|
85
|
+
def expectation(
|
86
|
+
self,
|
87
|
+
circuit: ConvertedCircuit,
|
88
|
+
observable: list[ConvertedObservable] | ConvertedObservable,
|
89
|
+
param_values: ParamDictType = {},
|
90
|
+
state: ArrayLike | None = None,
|
91
|
+
measurement: Measurements | None = None,
|
92
|
+
noise: Noise | None = None,
|
93
|
+
mitigation: Mitigations | None = None,
|
94
|
+
endianness: Endianness = Endianness.BIG,
|
95
|
+
) -> Any:
|
96
|
+
"""Compute the expectation value of the `circuit` with the given `observable`.
|
97
|
+
|
98
|
+
Arguments:
|
99
|
+
circuit: A converted circuit as returned by `backend.circuit`.
|
100
|
+
observable: A converted observable as returned by `backend.observable`.
|
101
|
+
param_values: _**Already embedded**_ parameters of the circuit. See
|
102
|
+
[`embedding`][qadence.blocks.embedding.embedding] for more info.
|
103
|
+
state: Initial state.
|
104
|
+
measurement: Optional measurement protocol. If None, use
|
105
|
+
exact expectation value with a statevector simulator.
|
106
|
+
noise: A noise model to use.
|
107
|
+
mitigation: The error mitigation to use.
|
108
|
+
endianness: Endianness of the resulting bit strings.
|
109
|
+
"""
|
110
|
+
raise NotImplementedError(
|
111
|
+
"A DifferentiableBackend needs to override the expectation method."
|
112
|
+
)
|
113
|
+
|
114
|
+
def default_configuration(self) -> Any:
|
115
|
+
return self.backend.default_configuration()
|
116
|
+
|
117
|
+
def circuit(self, circuit: QuantumCircuit) -> ConvertedCircuit:
|
118
|
+
if self.diff_mode == DiffMode.GPSR:
|
119
|
+
parametrized_blocks = list(uuid_to_block(circuit.block).values())
|
120
|
+
non_prim_blocks = filter(
|
121
|
+
lambda b: not isinstance(b, PrimitiveBlock), parametrized_blocks
|
122
|
+
)
|
123
|
+
if len(list(non_prim_blocks)) > 0:
|
124
|
+
raise ValueError(
|
125
|
+
"The circuit contains non-primitive blocks that are currently\
|
126
|
+
not supported by the PSR differentiable mode."
|
127
|
+
)
|
128
|
+
return self.backend.circuit(circuit)
|
129
|
+
|
130
|
+
def observable(self, observable: AbstractBlock, n_qubits: int) -> ConvertedObservable:
|
131
|
+
if self.diff_mode != DiffMode.AD and observable is not None:
|
132
|
+
msg = (
|
133
|
+
f"Differentiation mode '{self.diff_mode}' does not support parametric observables."
|
134
|
+
)
|
135
|
+
if isinstance(observable, list):
|
136
|
+
for obs in observable:
|
137
|
+
if obs.is_parametric:
|
138
|
+
raise ValueError(msg)
|
139
|
+
else:
|
140
|
+
if observable.is_parametric:
|
141
|
+
raise ValueError(msg)
|
142
|
+
return self.backend.observable(observable, n_qubits)
|
143
|
+
|
144
|
+
def convert(
|
145
|
+
self,
|
146
|
+
circuit: QuantumCircuit,
|
147
|
+
observable: list[AbstractBlock] | AbstractBlock | None = None,
|
148
|
+
) -> Converted:
|
149
|
+
return self.backend.convert(circuit, observable)
|
150
|
+
|
151
|
+
def assign_parameters(self, circuit: ConvertedCircuit, param_values: ParamDictType) -> Any:
|
152
|
+
return self.backend.assign_parameters(circuit, param_values)
|