qadence 1.7.0__py3-none-any.whl → 1.7.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/backends/api.py +47 -60
- qadence/backends/gpsr.py +1 -0
- qadence/backends/pyqtorch/backend.py +1 -2
- qadence/backends/pyqtorch/config.py +5 -0
- qadence/backends/pyqtorch/convert_ops.py +79 -8
- qadence/backends/utils.py +17 -3
- qadence/blocks/abstract.py +7 -0
- qadence/blocks/embedding.py +17 -12
- qadence/execution.py +11 -3
- qadence/extensions.py +62 -36
- qadence/ml_tools/config.py +9 -3
- qadence/ml_tools/constructors.py +53 -31
- qadence/ml_tools/models.py +51 -17
- qadence/ml_tools/printing.py +5 -2
- qadence/ml_tools/saveload.py +36 -12
- qadence/ml_tools/train_grad.py +45 -7
- qadence/model.py +164 -2
- qadence/operations/ham_evo.py +10 -0
- qadence/parameters.py +10 -1
- qadence/register.py +98 -22
- qadence/types.py +2 -0
- qadence/utils.py +2 -8
- {qadence-1.7.0.dist-info → qadence-1.7.1.dist-info}/METADATA +7 -6
- {qadence-1.7.0.dist-info → qadence-1.7.1.dist-info}/RECORD +26 -26
- {qadence-1.7.0.dist-info → qadence-1.7.1.dist-info}/WHEEL +1 -1
- {qadence-1.7.0.dist-info → qadence-1.7.1.dist-info}/licenses/LICENSE +0 -0
qadence/model.py
CHANGED
@@ -37,6 +37,36 @@ class QuantumModel(nn.Module):
|
|
37
37
|
This class should be used as base class for any new quantum model supported in the qadence
|
38
38
|
framework for information on the implementation of custom models see
|
39
39
|
[here](../tutorials/advanced_tutorials/custom-models.md).
|
40
|
+
|
41
|
+
Example:
|
42
|
+
```python exec="on" source="material-block" result="json"
|
43
|
+
import torch
|
44
|
+
from qadence import QuantumModel, QuantumCircuit, RX, RY, Z, PI, chain, kron
|
45
|
+
from qadence import FeatureParameter, VariationalParameter
|
46
|
+
|
47
|
+
theta = VariationalParameter("theta")
|
48
|
+
phi = FeatureParameter("phi")
|
49
|
+
|
50
|
+
block = chain(
|
51
|
+
kron(RX(0, theta), RY(1, theta)),
|
52
|
+
kron(RX(0, phi), RY(1, phi)),
|
53
|
+
)
|
54
|
+
|
55
|
+
circuit = QuantumCircuit(2, block)
|
56
|
+
|
57
|
+
observable = Z(0) + Z(1)
|
58
|
+
|
59
|
+
model = QuantumModel(circuit, observable)
|
60
|
+
values = {"phi": torch.tensor([PI, PI/2]), "theta": torch.tensor([PI, PI/2])}
|
61
|
+
|
62
|
+
wf = model.run(values)
|
63
|
+
xs = model.sample(values, n_shots=100)
|
64
|
+
ex = model.expectation(values)
|
65
|
+
print(wf)
|
66
|
+
print(xs)
|
67
|
+
print(ex)
|
68
|
+
```
|
69
|
+
```
|
40
70
|
"""
|
41
71
|
|
42
72
|
backend: Backend | DifferentiableBackend
|
@@ -120,6 +150,7 @@ class QuantumModel(nn.Module):
|
|
120
150
|
|
121
151
|
@property
|
122
152
|
def vparams(self) -> OrderedDict:
|
153
|
+
"""Variational parameters."""
|
123
154
|
return OrderedDict({k: v.data for k, v in self._params.items() if v.requires_grad})
|
124
155
|
|
125
156
|
@property
|
@@ -145,9 +176,26 @@ class QuantumModel(nn.Module):
|
|
145
176
|
return len(self.vals_vparams)
|
146
177
|
|
147
178
|
def circuit(self, circuit: QuantumCircuit) -> ConvertedCircuit:
|
179
|
+
"""Get backend-converted circuit.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
circuit: QuantumCircuit instance.
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
Backend circuit.
|
186
|
+
"""
|
148
187
|
return self.backend.circuit(circuit)
|
149
188
|
|
150
189
|
def observable(self, observable: AbstractBlock, n_qubits: int) -> Any:
|
190
|
+
"""Get backend observable.
|
191
|
+
|
192
|
+
Args:
|
193
|
+
observable: Observable block.
|
194
|
+
n_qubits: Number of qubits
|
195
|
+
|
196
|
+
Returns:
|
197
|
+
Backend observable.
|
198
|
+
"""
|
151
199
|
return self.backend.observable(observable, n_qubits)
|
152
200
|
|
153
201
|
def reset_vparams(self, values: Sequence) -> None:
|
@@ -161,6 +209,11 @@ class QuantumModel(nn.Module):
|
|
161
209
|
current_vparams[k].data = torch.tensor([values[i]])
|
162
210
|
|
163
211
|
def forward(self, *args: Any, **kwargs: Any) -> Tensor:
|
212
|
+
"""Calls run method with arguments.
|
213
|
+
|
214
|
+
Returns:
|
215
|
+
Tensor: A torch.Tensor representing output.
|
216
|
+
"""
|
164
217
|
return self.run(*args, **kwargs)
|
165
218
|
|
166
219
|
def run(
|
@@ -169,9 +222,26 @@ class QuantumModel(nn.Module):
|
|
169
222
|
state: Tensor | None = None,
|
170
223
|
endianness: Endianness = Endianness.BIG,
|
171
224
|
) -> Tensor:
|
225
|
+
r"""Run model.
|
226
|
+
|
227
|
+
Given an input state $| \psi_0 \rangle$,
|
228
|
+
a set of variational parameters $\vec{\theta}$
|
229
|
+
and the unitary representation of the model $U(\vec{\theta})$
|
230
|
+
we return $U(\vec{\theta}) | \psi_0 \rangle$.
|
231
|
+
|
232
|
+
Arguments:
|
233
|
+
values: Values dict which contains values for the parameters.
|
234
|
+
state: Optional input state to apply model on.
|
235
|
+
endianness: Storage convention for binary information.
|
236
|
+
|
237
|
+
Returns:
|
238
|
+
A torch.Tensor representing output.
|
239
|
+
"""
|
172
240
|
if values is None:
|
173
241
|
values = {}
|
242
|
+
|
174
243
|
params = self.embedding_fn(self._params, values)
|
244
|
+
|
175
245
|
return self.backend.run(self._circuit, params, state=state, endianness=endianness)
|
176
246
|
|
177
247
|
def sample(
|
@@ -183,6 +253,19 @@ class QuantumModel(nn.Module):
|
|
183
253
|
mitigation: Mitigations | None = None,
|
184
254
|
endianness: Endianness = Endianness.BIG,
|
185
255
|
) -> list[Counter]:
|
256
|
+
"""Obtain samples from model.
|
257
|
+
|
258
|
+
Arguments:
|
259
|
+
values: Values dict which contains values for the parameters.
|
260
|
+
n_shots: Observable part of the expectation.
|
261
|
+
state: Optional input state to apply model on.
|
262
|
+
noise: A noise model to use.
|
263
|
+
mitigation: A mitigation protocol to use.
|
264
|
+
endianness: Storage convention for binary information.
|
265
|
+
|
266
|
+
Returns:
|
267
|
+
A list of Counter instances with the sample results.
|
268
|
+
"""
|
186
269
|
params = self.embedding_fn(self._params, values)
|
187
270
|
if noise is None:
|
188
271
|
noise = self._noise
|
@@ -208,7 +291,27 @@ class QuantumModel(nn.Module):
|
|
208
291
|
mitigation: Mitigations | None = None,
|
209
292
|
endianness: Endianness = Endianness.BIG,
|
210
293
|
) -> Tensor:
|
211
|
-
"""Compute expectation using the given backend.
|
294
|
+
r"""Compute expectation using the given backend.
|
295
|
+
|
296
|
+
|
297
|
+
|
298
|
+
Given an input state $|\psi_0 \rangle$,
|
299
|
+
a set of variational parameters $\vec{\theta}$
|
300
|
+
and the unitary representation of the model $U(\vec{\theta})$
|
301
|
+
we return $\langle \psi_0 | U(\vec{\theta}) | \psi_0 \rangle$.
|
302
|
+
|
303
|
+
Arguments:
|
304
|
+
values: Values dict which contains values for the parameters.
|
305
|
+
observable: Observable part of the expectation.
|
306
|
+
state: Optional input state.
|
307
|
+
measurement: Optional measurement protocol. If None, use
|
308
|
+
exact expectation value with a statevector simulator.
|
309
|
+
noise: A noise model to use.
|
310
|
+
mitigation: A mitigation protocol to use.
|
311
|
+
endianness: Storage convention for binary information.
|
312
|
+
|
313
|
+
Raises:
|
314
|
+
ValueError: when no observable is set.
|
212
315
|
|
213
316
|
Returns:
|
214
317
|
A torch.Tensor of shape n_batches x n_obs
|
@@ -243,9 +346,22 @@ class QuantumModel(nn.Module):
|
|
243
346
|
)
|
244
347
|
|
245
348
|
def overlap(self) -> Tensor:
|
349
|
+
"""Overlap of model.
|
350
|
+
|
351
|
+
Raises:
|
352
|
+
NotImplementedError: The overlap method is not implemented for this model.
|
353
|
+
"""
|
246
354
|
raise NotImplementedError("The overlap method is not implemented for this model.")
|
247
355
|
|
248
356
|
def _to_dict(self, save_params: bool = False) -> dict[str, Any]:
|
357
|
+
"""Convert QuantumModel to a dictionary for serialization.
|
358
|
+
|
359
|
+
Arguments:
|
360
|
+
save_params: Optionally save parameters. Defaults to False.
|
361
|
+
|
362
|
+
Returns:
|
363
|
+
The dictionary
|
364
|
+
"""
|
249
365
|
d = dict()
|
250
366
|
try:
|
251
367
|
if isinstance(self._observable, list):
|
@@ -275,6 +391,15 @@ class QuantumModel(nn.Module):
|
|
275
391
|
|
276
392
|
@classmethod
|
277
393
|
def _from_dict(cls, d: dict, as_torch: bool = False) -> QuantumModel:
|
394
|
+
"""Initialize instance of QuantumModel from dictionary.
|
395
|
+
|
396
|
+
Args:
|
397
|
+
d: Dictionary.
|
398
|
+
as_torch: Load parameters as torch tensors. Defaults to False.
|
399
|
+
|
400
|
+
Returns:
|
401
|
+
QuantumModel instance
|
402
|
+
"""
|
278
403
|
from qadence.serialization import deserialize
|
279
404
|
|
280
405
|
qm: QuantumModel
|
@@ -310,6 +435,16 @@ class QuantumModel(nn.Module):
|
|
310
435
|
def save(
|
311
436
|
self, folder: str | Path, file_name: str = "quantum_model.pt", save_params: bool = True
|
312
437
|
) -> None:
|
438
|
+
"""Save model.
|
439
|
+
|
440
|
+
Arguments:
|
441
|
+
folder: Folder where model is saved.
|
442
|
+
file_name: File name for saving model. Defaults to "quantum_model.pt".
|
443
|
+
save_params: Save parameters if True. Defaults to True.
|
444
|
+
|
445
|
+
Raises:
|
446
|
+
FileNotFoundError: If folder is not a directory.
|
447
|
+
"""
|
313
448
|
if not os.path.isdir(folder):
|
314
449
|
raise FileNotFoundError
|
315
450
|
try:
|
@@ -321,6 +456,16 @@ class QuantumModel(nn.Module):
|
|
321
456
|
def load(
|
322
457
|
cls, file_path: str | Path, as_torch: bool = False, map_location: str | torch.device = "cpu"
|
323
458
|
) -> QuantumModel:
|
459
|
+
"""Load QuantumModel.
|
460
|
+
|
461
|
+
Arguments:
|
462
|
+
file_path: File path to load model from.
|
463
|
+
as_torch: Load parameters as torch tensor. Defaults to False.
|
464
|
+
map_location (str | torch.device, optional): Location for loading. Defaults to "cpu".
|
465
|
+
|
466
|
+
Returns:
|
467
|
+
QuantumModel from file_path.
|
468
|
+
"""
|
324
469
|
qm_pt = {}
|
325
470
|
if isinstance(file_path, str):
|
326
471
|
file_path = Path(file_path)
|
@@ -336,11 +481,23 @@ class QuantumModel(nn.Module):
|
|
336
481
|
return cls._from_dict(qm_pt, as_torch)
|
337
482
|
|
338
483
|
def assign_parameters(self, values: dict[str, Tensor]) -> Any:
|
339
|
-
"""Return the final, assigned circuit that is used in e.g. `backend.run`.
|
484
|
+
"""Return the final, assigned circuit that is used in e.g. `backend.run`.
|
485
|
+
|
486
|
+
Arguments:
|
487
|
+
values: Values dict which contains values for the parameters.
|
488
|
+
|
489
|
+
Returns:
|
490
|
+
Final, assigned circuit that is used in e.g. `backend.run`
|
491
|
+
"""
|
340
492
|
params = self.embedding_fn(self._params, values)
|
341
493
|
return self.backend.assign_parameters(self._circuit, params)
|
342
494
|
|
343
495
|
def to(self, *args: Any, **kwargs: Any) -> QuantumModel:
|
496
|
+
"""Conversion method for device or types.
|
497
|
+
|
498
|
+
Returns:
|
499
|
+
QuantumModel with conversions.
|
500
|
+
"""
|
344
501
|
from pyqtorch import QuantumCircuit as PyQCircuit
|
345
502
|
|
346
503
|
try:
|
@@ -369,6 +526,11 @@ class QuantumModel(nn.Module):
|
|
369
526
|
|
370
527
|
@property
|
371
528
|
def device(self) -> torch.device:
|
529
|
+
"""Get device.
|
530
|
+
|
531
|
+
Returns:
|
532
|
+
torch.device
|
533
|
+
"""
|
372
534
|
return (
|
373
535
|
self._circuit.native.device
|
374
536
|
if self.backend.backend.name == "pyqtorch" # type: ignore[union-attr]
|
qadence/operations/ham_evo.py
CHANGED
@@ -43,6 +43,7 @@ class HamEvo(TimeEvolutionBlock):
|
|
43
43
|
generator: Either a AbstractBlock, torch.Tensor or numpy.ndarray.
|
44
44
|
parameter: A scalar or vector of numeric or torch.Tensor type.
|
45
45
|
qubit_support: The qubits on which the evolution will be performed on.
|
46
|
+
duration: duration of evolution in case of time-dependent generator
|
46
47
|
|
47
48
|
Examples:
|
48
49
|
|
@@ -66,6 +67,7 @@ class HamEvo(TimeEvolutionBlock):
|
|
66
67
|
generator: Union[TGenerator, AbstractBlock],
|
67
68
|
parameter: TParameter,
|
68
69
|
qubit_support: tuple[int, ...] = None,
|
70
|
+
duration: float | None = None,
|
69
71
|
):
|
70
72
|
gen_exprs = {}
|
71
73
|
if qubit_support is None and not isinstance(generator, AbstractBlock):
|
@@ -75,6 +77,10 @@ class HamEvo(TimeEvolutionBlock):
|
|
75
77
|
qubit_support = generator.qubit_support
|
76
78
|
if generator.is_parametric:
|
77
79
|
gen_exprs = {str(e): e for e in expressions(generator)}
|
80
|
+
|
81
|
+
if generator.is_time_dependent and duration is None:
|
82
|
+
raise ValueError("For time-dependent generators, a duration must be specified.")
|
83
|
+
|
78
84
|
elif isinstance(generator, torch.Tensor):
|
79
85
|
msg = "Please provide a square generator."
|
80
86
|
if len(generator.shape) == 2:
|
@@ -99,6 +105,7 @@ class HamEvo(TimeEvolutionBlock):
|
|
99
105
|
ps = {"parameter": Parameter(parameter), **gen_exprs}
|
100
106
|
self.parameters = ParamMap(**ps)
|
101
107
|
self.generator = generator
|
108
|
+
self.duration = duration
|
102
109
|
|
103
110
|
@classmethod
|
104
111
|
def num_parameters(cls) -> int:
|
@@ -197,3 +204,6 @@ class HamEvo(TimeEvolutionBlock):
|
|
197
204
|
raise NotImplementedError(
|
198
205
|
"The current digital decomposition can be applied only to Pauli Hamiltonians."
|
199
206
|
)
|
207
|
+
|
208
|
+
def __matmul__(self, other: AbstractBlock) -> AbstractBlock:
|
209
|
+
return super().__matmul__(other)
|
qadence/parameters.py
CHANGED
@@ -16,7 +16,7 @@ from torch import Tensor, heaviside, no_grad, rand, tensor
|
|
16
16
|
from qadence.types import DifferentiableExpression, Engine, TNumber
|
17
17
|
|
18
18
|
# Modules to be automatically added to the qadence namespace
|
19
|
-
__all__ = ["FeatureParameter", "Parameter", "VariationalParameter", "ParamMap"]
|
19
|
+
__all__ = ["FeatureParameter", "Parameter", "VariationalParameter", "ParamMap", "TimeParameter"]
|
20
20
|
|
21
21
|
logger = getLogger(__name__)
|
22
22
|
|
@@ -61,6 +61,8 @@ class Parameter(Symbol):
|
|
61
61
|
value: TNumber
|
62
62
|
"""(Initial) value of the parameter."""
|
63
63
|
|
64
|
+
is_time: bool
|
65
|
+
|
64
66
|
def __new__(
|
65
67
|
cls, name: str | TNumber | Tensor | Basic | Parameter, **assumptions: Any
|
66
68
|
) -> Parameter | Basic | Expr | Array:
|
@@ -111,6 +113,7 @@ class Parameter(Symbol):
|
|
111
113
|
p.name = name.name
|
112
114
|
p.trainable = name.trainable
|
113
115
|
p.value = name.value
|
116
|
+
p.is_time = name.is_time
|
114
117
|
return p
|
115
118
|
elif isinstance(name, (Basic, Expr)):
|
116
119
|
if name.is_number:
|
@@ -120,6 +123,7 @@ class Parameter(Symbol):
|
|
120
123
|
p = super().__new__(cls, name, **assumptions)
|
121
124
|
p.trainable = assumptions.get("trainable", True)
|
122
125
|
p.value = assumptions.get("value", None)
|
126
|
+
p.is_time = assumptions.get("is_time", False)
|
123
127
|
if p.value is None:
|
124
128
|
p.value = rand(1).item()
|
125
129
|
return p
|
@@ -178,6 +182,11 @@ def VariationalParameter(name: str, **kwargs: Any) -> Parameter:
|
|
178
182
|
return Parameter(name, trainable=True, **kwargs)
|
179
183
|
|
180
184
|
|
185
|
+
def TimeParameter(name: str) -> Parameter:
|
186
|
+
"""Shorthand for `Parameter(..., trainable=False, is_time=True)`."""
|
187
|
+
return Parameter(name, trainable=False, is_time=True)
|
188
|
+
|
189
|
+
|
181
190
|
def extract_original_param_entry(
|
182
191
|
param: Expr,
|
183
192
|
) -> TNumber | Tensor | Expr:
|
qadence/register.py
CHANGED
@@ -38,28 +38,31 @@ class Register:
|
|
38
38
|
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
39
39
|
):
|
40
40
|
"""
|
41
|
-
A
|
41
|
+
A register of qubits including 2D coordinates.
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
Instantiating the Register class directly is only recommended for building custom registers.
|
44
|
+
For most uses where a predefined lattice is desired it is recommended to use the various
|
45
|
+
class methods available, e.g. `Register.triangular_lattice`.
|
46
46
|
|
47
47
|
Arguments:
|
48
|
-
support: A graph or number of qubits. Nodes can include a `"pos"` attribute
|
48
|
+
support: A NetworkX graph or number of qubits. Nodes can include a `"pos"` attribute
|
49
49
|
such that e.g.: `graph.nodes = {0: {"pos": (2,3)}, 1: {"pos": (0,0)}, ...}` which
|
50
|
-
will be used in backends that need qubit coordinates.
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
`Register.all_to_all(n_qubits)`.
|
55
|
-
spacing: Value set as the distance between the two closest qubits.
|
50
|
+
will be used in backends that need qubit coordinates. Passing a number of qubits
|
51
|
+
calls `Register.all_to_all(n_qubits)`.
|
52
|
+
spacing: Value set as the distance between the two closest qubits. The spacing
|
53
|
+
argument is also available for all the class method constructors.
|
56
54
|
|
57
55
|
Examples:
|
58
|
-
```python exec="on" source="material-block"
|
56
|
+
```python exec="on" source="material-block"
|
59
57
|
from qadence import Register
|
60
58
|
|
61
|
-
|
62
|
-
|
59
|
+
reg_all = Register.all_to_all(n_qubits = 4)
|
60
|
+
reg_line = Register.line(n_qubits = 4)
|
61
|
+
reg_circle = Register.circle(n_qubits = 4)
|
62
|
+
reg_squre = Register.square(qubits_side = 2)
|
63
|
+
reg_rect = Register.rectangular_lattice(qubits_row = 2, qubits_col = 2)
|
64
|
+
reg_triang = Register.triangular_lattice(n_cells_row = 2, n_cells_col = 2)
|
65
|
+
reg_honey = Register.honeycomb_lattice(n_cells_row = 2, n_cells_col = 2)
|
63
66
|
```
|
64
67
|
"""
|
65
68
|
if device_specs is not None and not isinstance(device_specs, RydbergDevice):
|
@@ -74,6 +77,7 @@ class Register:
|
|
74
77
|
|
75
78
|
@property
|
76
79
|
def n_qubits(self) -> int:
|
80
|
+
"""Total number of qubits in the register."""
|
77
81
|
return len(self.graph)
|
78
82
|
|
79
83
|
@classmethod
|
@@ -84,6 +88,15 @@ class Register:
|
|
84
88
|
spacing: float | None = None,
|
85
89
|
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
86
90
|
) -> Register:
|
91
|
+
"""
|
92
|
+
Build a register from a list of qubit coordinates.
|
93
|
+
|
94
|
+
Each node is added to the underlying
|
95
|
+
graph with the respective coordinates, but the edges are left empty.
|
96
|
+
|
97
|
+
Arguments:
|
98
|
+
coords: List of qubit coordinate tuples.
|
99
|
+
"""
|
87
100
|
graph = nx.Graph()
|
88
101
|
for i, pos in enumerate(coords):
|
89
102
|
graph.add_node(i, pos=pos)
|
@@ -96,6 +109,12 @@ class Register:
|
|
96
109
|
spacing: float = 1.0,
|
97
110
|
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
98
111
|
) -> Register:
|
112
|
+
"""
|
113
|
+
Build a line register.
|
114
|
+
|
115
|
+
Arguments:
|
116
|
+
n_qubits: Total number of qubits.
|
117
|
+
"""
|
99
118
|
return cls(line_graph(n_qubits), spacing, device_specs)
|
100
119
|
|
101
120
|
@classmethod
|
@@ -105,6 +124,12 @@ class Register:
|
|
105
124
|
spacing: float = 1.0,
|
106
125
|
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
107
126
|
) -> Register:
|
127
|
+
"""
|
128
|
+
Build a circle register.
|
129
|
+
|
130
|
+
Arguments:
|
131
|
+
n_qubits: Total number of qubits.
|
132
|
+
"""
|
108
133
|
graph = nx.grid_2d_graph(n_qubits, 1, periodic=True)
|
109
134
|
graph = nx.relabel_nodes(graph, {(i, 0): i for i in range(n_qubits)})
|
110
135
|
coords = nx.circular_layout(graph)
|
@@ -119,6 +144,12 @@ class Register:
|
|
119
144
|
spacing: float = 1.0,
|
120
145
|
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
121
146
|
) -> Register:
|
147
|
+
"""
|
148
|
+
Build a square register.
|
149
|
+
|
150
|
+
Arguments:
|
151
|
+
qubits_side: Number of qubits on one side of the square.
|
152
|
+
"""
|
122
153
|
n_points = 4 * (qubits_side - 1)
|
123
154
|
|
124
155
|
def gen_points() -> np.ndarray:
|
@@ -152,6 +183,15 @@ class Register:
|
|
152
183
|
spacing: float = 1.0,
|
153
184
|
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
154
185
|
) -> Register:
|
186
|
+
"""
|
187
|
+
Build a register with an all-to-all connectivity graph.
|
188
|
+
|
189
|
+
The graph is projected
|
190
|
+
onto a 2D space and the qubit coordinates are set using a spring layout algorithm.
|
191
|
+
|
192
|
+
Arguments:
|
193
|
+
n_qubits: Total number of qubits.
|
194
|
+
"""
|
155
195
|
return cls(alltoall_graph(n_qubits), spacing, device_specs)
|
156
196
|
|
157
197
|
@classmethod
|
@@ -166,6 +206,13 @@ class Register:
|
|
166
206
|
values = {i: {"pos": node} for (i, node) in enumerate(graph.nodes)}
|
167
207
|
graph = nx.relabel_nodes(graph, {(i, j): k for k, (i, j) in enumerate(graph.nodes)})
|
168
208
|
nx.set_node_attributes(graph, values)
|
209
|
+
"""
|
210
|
+
Build a rectangular lattice register.
|
211
|
+
|
212
|
+
Arguments:
|
213
|
+
qubits_row: Number of qubits in each row.
|
214
|
+
qubits_col: Number of qubits in each column.
|
215
|
+
"""
|
169
216
|
return cls(graph, spacing, device_specs)
|
170
217
|
|
171
218
|
@classmethod
|
@@ -176,6 +223,15 @@ class Register:
|
|
176
223
|
spacing: float = 1.0,
|
177
224
|
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
178
225
|
) -> Register:
|
226
|
+
"""
|
227
|
+
Build a triangular lattice register.
|
228
|
+
|
229
|
+
Each cell is a triangle made up of three qubits.
|
230
|
+
|
231
|
+
Arguments:
|
232
|
+
n_cells_row: Number of cells in each row.
|
233
|
+
n_cells_col: Number of cells in each column.
|
234
|
+
"""
|
179
235
|
return cls(triangular_lattice_graph(n_cells_row, n_cells_col), spacing, device_specs)
|
180
236
|
|
181
237
|
@classmethod
|
@@ -186,6 +242,15 @@ class Register:
|
|
186
242
|
spacing: float = 1.0,
|
187
243
|
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
188
244
|
) -> Register:
|
245
|
+
"""
|
246
|
+
Build a honeycomb lattice register.
|
247
|
+
|
248
|
+
Each cell is an hexagon made up of six qubits.
|
249
|
+
|
250
|
+
Arguments:
|
251
|
+
n_cells_row: Number of cells in each row.
|
252
|
+
n_cells_col: Number of cells in each column.
|
253
|
+
"""
|
189
254
|
graph = nx.hexagonal_lattice_graph(n_cells_row, n_cells_col)
|
190
255
|
graph = nx.relabel_nodes(graph, {(i, j): k for k, (i, j) in enumerate(graph.nodes)})
|
191
256
|
return cls(graph, spacing, device_specs)
|
@@ -195,6 +260,7 @@ class Register:
|
|
195
260
|
return getattr(cls, topology)(*args, **kwargs) # type: ignore[no-any-return]
|
196
261
|
|
197
262
|
def draw(self, show: bool = True) -> None:
|
263
|
+
"""Draw the underlying NetworkX graph representing the register."""
|
198
264
|
coords = {i: n["pos"] for i, n in self.graph.nodes.items()}
|
199
265
|
nx.draw(self.graph, with_labels=True, pos=coords)
|
200
266
|
if show:
|
@@ -205,41 +271,59 @@ class Register:
|
|
205
271
|
|
206
272
|
@property
|
207
273
|
def nodes(self) -> NodeView:
|
274
|
+
"""Return the NodeView of the underlying NetworkX graph."""
|
208
275
|
return self.graph.nodes
|
209
276
|
|
210
277
|
@property
|
211
278
|
def edges(self) -> EdgeView:
|
279
|
+
"""Return the EdgeView of the underlying NetworkX graph."""
|
212
280
|
return self.graph.edges
|
213
281
|
|
214
282
|
@property
|
215
283
|
def support(self) -> set:
|
284
|
+
"""Return the set of qubits in the register."""
|
216
285
|
return set(self.nodes)
|
217
286
|
|
218
287
|
@property
|
219
288
|
def coords(self) -> dict:
|
289
|
+
"""Return the dictionary of qubit coordinates."""
|
220
290
|
return {i: tuple(node.get("pos", ())) for i, node in self.nodes.items()}
|
221
291
|
|
222
292
|
@property
|
223
293
|
def all_node_pairs(self) -> EdgeView:
|
294
|
+
"""Return a list of all possible qubit pairs in the register."""
|
224
295
|
return list(filter(lambda x: x[0] < x[1], product(self.support, self.support)))
|
225
296
|
|
226
297
|
@property
|
227
298
|
def distances(self) -> dict:
|
299
|
+
"""Return a dictionary of distances for all qubit pairs in the register."""
|
228
300
|
coords = self.coords
|
229
301
|
return {edge: dist(coords[edge[0]], coords[edge[1]]) for edge in self.all_node_pairs}
|
230
302
|
|
231
303
|
@property
|
232
304
|
def edge_distances(self) -> dict:
|
305
|
+
"""
|
306
|
+
Return a dictionary of distances for the qubit pairs that are.
|
307
|
+
|
308
|
+
connected by an edge in the underlying NetworkX graph.
|
309
|
+
"""
|
233
310
|
coords = self.coords
|
234
311
|
return {edge: dist(coords[edge[0]], coords[edge[1]]) for edge in self.edges}
|
235
312
|
|
236
313
|
@property
|
237
314
|
def min_distance(self) -> float:
|
315
|
+
"""Return the minimum distance between two qubts in the register."""
|
238
316
|
distances = self.distances
|
239
317
|
value: float = min(self.distances.values()) if len(distances) > 0 else 0.0
|
240
318
|
return value
|
241
319
|
|
242
320
|
def rescale_coords(self, scaling: float) -> Register:
|
321
|
+
"""
|
322
|
+
Rescale the coordinates of all qubits in the register.
|
323
|
+
|
324
|
+
Arguments:
|
325
|
+
scaling: Scaling value.
|
326
|
+
"""
|
243
327
|
g = deepcopy(self.graph)
|
244
328
|
_scale_node_positions(g, min_distance=1.0, spacing=scaling)
|
245
329
|
return Register(g, spacing=None, device_specs=self.device_specs)
|
@@ -272,14 +356,6 @@ class Register:
|
|
272
356
|
|
273
357
|
|
274
358
|
def line_graph(n_qubits: int) -> nx.Graph:
|
275
|
-
"""Create graph representing linear lattice.
|
276
|
-
|
277
|
-
Args:
|
278
|
-
n_qubits (int): number of nodes in the graph
|
279
|
-
|
280
|
-
Returns:
|
281
|
-
graph instance
|
282
|
-
"""
|
283
359
|
graph = nx.Graph()
|
284
360
|
for i in range(n_qubits):
|
285
361
|
graph.add_node(i, pos=(i, 0.0))
|
qadence/types.py
CHANGED
@@ -7,6 +7,7 @@ from typing import Callable, Iterable, Tuple, Union
|
|
7
7
|
import numpy as np
|
8
8
|
import sympy
|
9
9
|
from numpy.typing import ArrayLike
|
10
|
+
from pyqtorch.utils import SolverType
|
10
11
|
from torch import Tensor, pi
|
11
12
|
|
12
13
|
TNumber = Union[int, float, complex, np.int64, np.float64]
|
@@ -47,6 +48,7 @@ __all__ = [
|
|
47
48
|
"AlgoHEvo",
|
48
49
|
"SerializationFormat",
|
49
50
|
"PI",
|
51
|
+
"SolverType",
|
50
52
|
] # type: ignore
|
51
53
|
|
52
54
|
|
qadence/utils.py
CHANGED
@@ -234,18 +234,12 @@ def is_qadence_shape(state: ArrayLike, n_qubits: int) -> bool:
|
|
234
234
|
return state.shape[1] == 2**n_qubits # type: ignore[no-any-return]
|
235
235
|
|
236
236
|
|
237
|
-
def infer_batchsize(param_values: dict[str, Tensor] = None) -> int:
|
238
|
-
"""Infer the batch_size through the length of the parameter tensors."""
|
239
|
-
try:
|
240
|
-
return max([len(tensor) for tensor in param_values.values()]) if param_values else 1
|
241
|
-
except Exception:
|
242
|
-
return 1
|
243
|
-
|
244
|
-
|
245
237
|
def validate_values_and_state(
|
246
238
|
state: ArrayLike | None, n_qubits: int, param_values: dict[str, Tensor] = None
|
247
239
|
) -> None:
|
248
240
|
if state is not None:
|
241
|
+
from qadence.backends.utils import infer_batchsize
|
242
|
+
|
249
243
|
if isinstance(state, Tensor):
|
250
244
|
if state is not None:
|
251
245
|
batch_size_state = (
|
@@ -1,8 +1,8 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: qadence
|
3
|
-
Version: 1.7.
|
3
|
+
Version: 1.7.1
|
4
4
|
Summary: Pasqal interface for circuit-based quantum computing SDKs
|
5
|
-
Author-email: Aleksander Wennersteen <aleksander.wennersteen@pasqal.com>, Gert-Jan Both <gert-jan.both@pasqal.com>, Niklas Heim <niklas.heim@pasqal.com>, Mario Dagrada <mario.dagrada@pasqal.com>, Vincent Elfving <vincent.elfving@pasqal.com>, Dominik Seitz <dominik.seitz@pasqal.com>, Roland Guichard <roland.guichard@pasqal.com>, "Joao P. Moutinho" <joao.moutinho@pasqal.com>, Vytautas Abramavicius <vytautas.abramavicius@pasqal.com>, Gergana Velikova <gergana.velikova@pasqal.com>, Eduardo Maschio <eduardo.maschio@pasqal.com>, Smit Chaudhary <smit.chaudhary@pasqal.com>
|
5
|
+
Author-email: Aleksander Wennersteen <aleksander.wennersteen@pasqal.com>, Gert-Jan Both <gert-jan.both@pasqal.com>, Niklas Heim <niklas.heim@pasqal.com>, Mario Dagrada <mario.dagrada@pasqal.com>, Vincent Elfving <vincent.elfving@pasqal.com>, Dominik Seitz <dominik.seitz@pasqal.com>, Roland Guichard <roland.guichard@pasqal.com>, "Joao P. Moutinho" <joao.moutinho@pasqal.com>, Vytautas Abramavicius <vytautas.abramavicius@pasqal.com>, Gergana Velikova <gergana.velikova@pasqal.com>, Eduardo Maschio <eduardo.maschio@pasqal.com>, Smit Chaudhary <smit.chaudhary@pasqal.com>, Ignacio Fernández Graña <ignacio.fernandez-grana@pasqal.com>, Charles Moussa <charles.moussa@pasqal.com>
|
6
6
|
License: Apache 2.0
|
7
7
|
License-File: LICENSE
|
8
8
|
Classifier: License :: OSI Approved :: Apache Software License
|
@@ -22,10 +22,11 @@ Requires-Dist: matplotlib
|
|
22
22
|
Requires-Dist: nevergrad
|
23
23
|
Requires-Dist: numpy
|
24
24
|
Requires-Dist: openfermion
|
25
|
-
Requires-Dist: pyqtorch==1.2.
|
25
|
+
Requires-Dist: pyqtorch==1.2.5
|
26
26
|
Requires-Dist: pyyaml
|
27
27
|
Requires-Dist: rich
|
28
28
|
Requires-Dist: scipy
|
29
|
+
Requires-Dist: sympy<1.13
|
29
30
|
Requires-Dist: sympytorch>=0.1.2
|
30
31
|
Requires-Dist: tensorboard>=2.12.0
|
31
32
|
Requires-Dist: torch
|
@@ -53,9 +54,9 @@ Requires-Dist: qadence-libs; extra == 'libs'
|
|
53
54
|
Provides-Extra: protocols
|
54
55
|
Requires-Dist: qadence-protocols; extra == 'protocols'
|
55
56
|
Provides-Extra: pulser
|
56
|
-
Requires-Dist: pasqal-cloud==0.
|
57
|
-
Requires-Dist: pulser-core==0.
|
58
|
-
Requires-Dist: pulser-simulation==0.
|
57
|
+
Requires-Dist: pasqal-cloud==0.11.1; extra == 'pulser'
|
58
|
+
Requires-Dist: pulser-core==0.19.0; extra == 'pulser'
|
59
|
+
Requires-Dist: pulser-simulation==0.19.0; extra == 'pulser'
|
59
60
|
Provides-Extra: visualization
|
60
61
|
Requires-Dist: graphviz; extra == 'visualization'
|
61
62
|
Description-Content-Type: text/markdown
|