qadence 1.9.1__py3-none-any.whl → 1.10.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/analog/device.py CHANGED
@@ -5,6 +5,8 @@ from dataclasses import dataclass, fields
5
5
  from qadence.analog import AddressingPattern
6
6
  from qadence.types import PI, DeviceType, Interaction
7
7
 
8
+ from .constants import C6_DICT
9
+
8
10
 
9
11
  @dataclass(frozen=True, eq=True)
10
12
  class RydbergDevice:
@@ -41,6 +43,11 @@ class RydbergDevice:
41
43
  type: DeviceType = DeviceType.IDEALIZED
42
44
  """DeviceType.IDEALIZED or REALISTIC to convert to the Pulser backend."""
43
45
 
46
+ @property
47
+ def coeff_ising(self) -> float:
48
+ """Value of C_6."""
49
+ return C6_DICT[self.rydberg_level]
50
+
44
51
  def __post_init__(self) -> None:
45
52
  # FIXME: Currently not supporting custom interaction functions.
46
53
  if self.interaction not in [Interaction.NN, Interaction.XY]:
qadence/backends/api.py CHANGED
@@ -42,9 +42,9 @@ def backend_factory(
42
42
 
43
43
  # Instantiate the backend
44
44
  backend_inst = BackendCls( # type: ignore[operator]
45
- config=configuration
46
- if configuration is not None
47
- else BackendCls.default_configuration()
45
+ config=(
46
+ configuration if configuration is not None else BackendCls.default_configuration()
47
+ )
48
48
  )
49
49
  set_backend_config(backend_inst, diff_mode)
50
50
  # Wrap the quantum Backend in a DifferentiableBackend if a diff_mode is passed.
@@ -12,7 +12,7 @@ from horqrux.apply import apply_gate
12
12
  from horqrux.parametric import RX, RY, RZ
13
13
  from horqrux.primitive import NOT, SWAP, H, I, X, Y, Z
14
14
  from horqrux.primitive import Primitive as Gate
15
- from horqrux.utils import inner
15
+ from horqrux.utils import ControlQubits, TargetQubits, inner
16
16
  from jax import Array
17
17
  from jax.scipy.linalg import expm
18
18
  from jax.tree_util import register_pytree_node_class
@@ -102,6 +102,8 @@ def convert_observable(
102
102
  def convert_block(
103
103
  block: AbstractBlock, n_qubits: int = None, config: Configuration = Configuration()
104
104
  ) -> list:
105
+ control: ControlQubits
106
+ target: TargetQubits
105
107
  if n_qubits is None:
106
108
  n_qubits = max(block.qubit_support) + 1
107
109
  ops = []
@@ -259,7 +259,7 @@ class Backend(BackendInterface):
259
259
  for i, param_values_el in enumerate(vals):
260
260
  sequence = self.assign_parameters(circuit, param_values_el)
261
261
  sim_result: CoherentResults = simulate_sequence(sequence, self.config, state)
262
- final_state = sim_result.get_final_state().data.toarray()
262
+ final_state = sim_result.get_final_state().data.to_array()
263
263
  batched_dm[i] = np.flip(final_state)
264
264
  return torch.from_numpy(batched_dm)
265
265
 
@@ -264,7 +264,7 @@ def convert_block(
264
264
  duration=duration,
265
265
  solver=config.ode_solver,
266
266
  steps=config.n_steps_hevo,
267
- noise_operators=noise_operators,
267
+ noise=noise_operators if len(noise_operators) > 0 else None,
268
268
  )
269
269
  ]
270
270
 
@@ -351,22 +351,22 @@ def convert_block(
351
351
  )
352
352
 
353
353
 
354
- def convert_digital_noise(noise: NoiseHandler) -> pyq.noise.NoiseProtocol | None:
354
+ def convert_digital_noise(noise: NoiseHandler) -> pyq.noise.DigitalNoiseProtocol | None:
355
355
  """Convert the digital noise into pyqtorch NoiseProtocol.
356
356
 
357
357
  Args:
358
358
  noise (NoiseHandler): Noise to convert.
359
359
 
360
360
  Returns:
361
- pyq.noise.NoiseProtocol | None: Pyqtorch native noise protocol
361
+ pyq.noise.DigitalNoiseProtocol | None: Pyqtorch native noise protocol
362
362
  if there are any digital noise protocols.
363
363
  """
364
364
  digital_part = noise.filter(NoiseProtocol.DIGITAL)
365
365
  if digital_part is None:
366
366
  return None
367
- return pyq.noise.NoiseProtocol(
367
+ return pyq.noise.DigitalNoiseProtocol(
368
368
  [
369
- pyq.noise.NoiseProtocol(proto, option.get("error_probability"))
369
+ pyq.noise.DigitalNoiseProtocol(proto, option.get("error_probability"))
370
370
  for proto, option in zip(digital_part.protocol, digital_part.options)
371
371
  ]
372
372
  )
qadence/backends/utils.py CHANGED
@@ -110,9 +110,23 @@ def to_list_of_dicts(param_values: ParamDictType) -> list[ParamDictType]:
110
110
 
111
111
 
112
112
  def pyqify(state: Tensor, n_qubits: int = None) -> ArrayLike:
113
- """Convert a state of shape (batch_size, 2**n_qubits) to [2] * n_qubits + [batch_size]."""
113
+ """Convert a state of shape (batch_size, 2**n_qubits) to [2] * n_qubits + [batch_size].
114
+
115
+ Or set the batch_size of a density matrix as the last dimension for PyQTorch.
116
+ """
114
117
  if n_qubits is None:
115
118
  n_qubits = int(log2(state.shape[1]))
119
+ if isinstance(state, DensityMatrix):
120
+ if (
121
+ len(state.shape) != 3
122
+ or (state.shape[1] != 2**n_qubits)
123
+ or (state.shape[1] != state.shape[2])
124
+ ):
125
+ raise ValueError(
126
+ "The initial state must be composed of tensors/arrays of size "
127
+ f"(batch_size, 2**n_qubits, 2**n_qubits). Found: {state.shape = }."
128
+ )
129
+ return torch.einsum("kij->ijk", state)
116
130
  if len(state.shape) != 2 or (state.shape[1] != 2**n_qubits):
117
131
  raise ValueError(
118
132
  "The initial state must be composed of tensors/arrays of size "
@@ -41,7 +41,10 @@ def unique(x: Iterable) -> List:
41
41
 
42
42
  def embedding(
43
43
  block: AbstractBlock, to_gate_params: bool = False, engine: Engine = Engine.TORCH
44
- ) -> tuple[ParamDictType, Callable[[ParamDictType, ParamDictType], ParamDictType],]:
44
+ ) -> tuple[
45
+ ParamDictType,
46
+ Callable[[ParamDictType, ParamDictType], ParamDictType],
47
+ ]:
45
48
  """Construct embedding function which maps user-facing parameters to either *expression-level*.
46
49
 
47
50
  parameters or *gate-level* parameters. The constructed embedding function has the signature:
@@ -147,11 +150,9 @@ def embedding(
147
150
  if k not in embedded_params:
148
151
  embedded_params[k] = v
149
152
  out = {
150
- stringify(k)
151
- if not isinstance(k, str)
152
- else k: as_tensor(v)[None]
153
- if as_tensor(v).ndim == 0
154
- else v
153
+ stringify(k) if not isinstance(k, str) else k: (
154
+ as_tensor(v)[None] if as_tensor(v).ndim == 0 else v
155
+ )
155
156
  for k, v in embedded_params.items()
156
157
  }
157
158
  return out
qadence/blocks/utils.py CHANGED
@@ -305,9 +305,11 @@ def uuid_to_eigen(
305
305
  else:
306
306
  result[uuid] = (
307
307
  b.eigenvalues_generator * 2.0,
308
- 1.0 / (b.eigenvalues_generator.item() * 2.0)
309
- if len(b.eigenvalues_generator) == 1
310
- else 1.0,
308
+ (
309
+ 1.0 / (b.eigenvalues_generator.item() * 2.0)
310
+ if len(b.eigenvalues_generator) == 1
311
+ else 1.0
312
+ ),
311
313
  )
312
314
  else:
313
315
  result[uuid] = (b.eigenvalues_generator, 1.0)
@@ -5,9 +5,9 @@ from .feature_maps import (
5
5
  exp_fourier_feature_map,
6
6
  )
7
7
 
8
- from .ansatze import hea, alt
9
-
10
- from .iia import identity_initialized_ansatz
8
+ from .hea import hea
9
+ from .ala import ala
10
+ from .iia import identity_initialized_ansatz, iia
11
11
 
12
12
  from .daqc import daqc_transform
13
13
 
@@ -29,8 +29,9 @@ __all__ = [
29
29
  "feature_map",
30
30
  "exp_fourier_feature_map",
31
31
  "hea",
32
- "alt",
32
+ "ala",
33
33
  "identity_initialized_ansatz",
34
+ "iia",
34
35
  "hamiltonian_factory",
35
36
  "ising_hamiltonian",
36
37
  "ObservableConfig",
@@ -0,0 +1,270 @@
1
+ from __future__ import annotations
2
+
3
+ import itertools
4
+ from typing import Any, Type, Union
5
+
6
+ from qadence.blocks import AbstractBlock, chain, kron, tag
7
+ from qadence.operations import CNOT, CPHASE, CRX, CRY, CRZ, CZ, RX, RY
8
+ from qadence.types import Strategy
9
+
10
+ DigitalEntanglers = Union[CNOT, CZ, CRZ, CRY, CRX]
11
+
12
+
13
+ def ala(
14
+ n_qubits: int,
15
+ m_block_qubits: int,
16
+ depth: int = 1,
17
+ param_prefix: str = "theta",
18
+ support: tuple[int, ...] | None = None,
19
+ strategy: Strategy = Strategy.DIGITAL,
20
+ **strategy_args: Any,
21
+ ) -> AbstractBlock:
22
+ """
23
+ Factory function for the alternating layer ansatz (ala).
24
+
25
+ Args:
26
+ n_qubits: number of qubits in the circuit
27
+ m_block_qubits: number of qubits in the local entangling block
28
+ depth: number of layers of the alternating layer ansatz
29
+ param_prefix: the base name of the variational parameters
30
+ support: qubit indices where the ala is applied
31
+ strategy: Strategy for the ansatz. One of the Strategy variants.
32
+ **strategy_args: see below
33
+
34
+ Keyword Arguments:
35
+ operations (list): list of operations to cycle through in the
36
+ digital single-qubit rotations of each layer. Valid for
37
+ Digital .
38
+ entangler (AbstractBlock):
39
+ - Digital: 2-qubit entangling operation. Supports CNOT, CZ,
40
+ CRX, CRY, CRZ, CPHASE. Controlled rotations will have variational
41
+ parameters on the rotation angles.
42
+ - SDAQC | BDAQC: Hamiltonian generator for the analog entangling
43
+ layer. Must be an m-qubit operator where m is the size of the
44
+ local entangling block. Defaults to a ZZ interaction.
45
+
46
+ Returns:
47
+ The Alternating Layer Ansatz (ALA) circuit.
48
+ """
49
+
50
+ if support is None:
51
+ support = tuple(range(n_qubits))
52
+
53
+ ala_func_dict = {
54
+ Strategy.DIGITAL: ala_digital,
55
+ Strategy.SDAQC: ala_sDAQC,
56
+ Strategy.BDAQC: ala_bDAQC,
57
+ Strategy.ANALOG: ala_analog,
58
+ }
59
+
60
+ try:
61
+ ala_func = ala_func_dict[strategy]
62
+ except KeyError:
63
+ raise KeyError(f"Strategy {strategy} not recognized.")
64
+
65
+ ala_block: AbstractBlock = ala_func(
66
+ n_qubits=n_qubits,
67
+ m_block_qubits=m_block_qubits,
68
+ depth=depth,
69
+ param_prefix=param_prefix,
70
+ support=support,
71
+ **strategy_args,
72
+ ) # type: ignore
73
+
74
+ return ala_block
75
+
76
+
77
+ #################
78
+ ## DIGITAL ALA ##
79
+ #################
80
+
81
+
82
+ def _rotations_digital(
83
+ n_qubits: int,
84
+ depth: int,
85
+ param_prefix: str = "theta",
86
+ support: tuple[int, ...] | None = None,
87
+ operations: list[Type[AbstractBlock]] = [RX, RY, RX],
88
+ ) -> list[AbstractBlock]:
89
+ """Creates the layers of single qubit rotations in an Alternating Layer Ansatz.
90
+
91
+ Args:
92
+ n_qubits: The number of qubits in the Alternating Layer Ansatz.
93
+ depth: The number of layers of rotations.
94
+ param_prefix: The prefix for the parameter names.
95
+ support: The qubits to apply the rotations to.
96
+ operations: The operations to apply the rotations with.
97
+
98
+ Returns:
99
+ A list of digital rotation layers for the Alternating Layer Ansatz.
100
+ """
101
+ if support is None:
102
+ support = tuple(range(n_qubits))
103
+ iterator = itertools.count()
104
+ rot_list: list[AbstractBlock] = []
105
+ for d in range(depth):
106
+ rots = [
107
+ kron(
108
+ gate(support[n], param_prefix + f"_{next(iterator)}") # type: ignore [arg-type]
109
+ for n in range(n_qubits)
110
+ )
111
+ for gate in operations
112
+ ]
113
+ rot_list.append(chain(*rots))
114
+ return rot_list
115
+
116
+
117
+ def _entangler(
118
+ control: int,
119
+ target: int,
120
+ param_str: str,
121
+ op: Type[DigitalEntanglers] = CNOT,
122
+ ) -> AbstractBlock:
123
+ """
124
+ Creates the entangler for a single qubit in an Alternating Layer Ansatz.
125
+
126
+ Args:
127
+ control: The control qubit.
128
+ target: The target qubit.
129
+ param_str: The parameter string.
130
+ op: The entangler to use.
131
+
132
+ Returns:
133
+ The 2-qubit digital entangler for the Alternating Layer Ansatz.
134
+ """
135
+ if op in [CNOT, CZ]:
136
+ return op(control, target) # type: ignore
137
+ elif op in [CRZ, CRY, CRX, CPHASE]:
138
+ return op(control, target, param_str) # type: ignore
139
+ else:
140
+ raise ValueError("Provided entangler not accepted for digital alternating layer ansatz")
141
+
142
+
143
+ def _entanglers_ala_block_digital(
144
+ n_qubits: int,
145
+ m_block_qubits: int,
146
+ depth: int,
147
+ param_prefix: str = "theta",
148
+ support: tuple[int, ...] | None = None,
149
+ entangler: Type[DigitalEntanglers] = CNOT,
150
+ ) -> list[AbstractBlock]:
151
+ """
152
+ Creates the entanglers for an Alternating Layer Ansatz.
153
+
154
+ Args:
155
+ n_qubits: The number of qubits in the Alternating Layer Ansatz.
156
+ m_block_qubits: The number of qubits in each block.
157
+ depth: The number of layers of entanglers.
158
+ param_prefix: The prefix for the parameter names.
159
+ support (tuple): qubit indices where the HEA is applied.
160
+ entangler: The entangler to use.
161
+
162
+ Returns:
163
+ The entanglers for the Alternating Layer Ansatz.
164
+ """
165
+ if support is None:
166
+ support = tuple(range(n_qubits))
167
+ iterator = itertools.count()
168
+ ent_list: list[AbstractBlock] = []
169
+
170
+ for d in range(depth):
171
+ start_i = 0 if not d % 2 else -m_block_qubits // 2
172
+ ents = [
173
+ kron(
174
+ _entangler(
175
+ control=support[i + j],
176
+ target=support[i + j + 1],
177
+ param_str=param_prefix + f"_ent_{next(iterator)}",
178
+ op=entangler,
179
+ )
180
+ for j in range(start_j, m_block_qubits, 2)
181
+ for i in range(start_i, n_qubits, m_block_qubits)
182
+ if i + j + 1 < n_qubits and j + 1 < m_block_qubits and i + j >= 0
183
+ )
184
+ for start_j in [i for i in range(2) if m_block_qubits > 2 or i == 0]
185
+ ]
186
+
187
+ ent_list.append(chain(*ents))
188
+ return ent_list
189
+
190
+
191
+ def ala_digital(
192
+ n_qubits: int,
193
+ m_block_qubits: int,
194
+ depth: int = 1,
195
+ param_prefix: str = "theta",
196
+ support: tuple[int, ...] | None = None,
197
+ operations: list[type[AbstractBlock]] = [RX, RY],
198
+ entangler: Type[DigitalEntanglers] = CNOT,
199
+ ) -> AbstractBlock:
200
+ """
201
+ Construct the digital alternating layer ansatz (ALA).
202
+
203
+ Args:
204
+ n_qubits (int): number of qubits in the circuit.
205
+ m_block_qubits (int): number of qubits in the local entangling block.
206
+ depth (int): number of layers of the ALA.
207
+ param_prefix (str): the base name of the variational parameters
208
+ support (tuple): qubit indices where the ALA is applied.
209
+ operations (list): list of operations to cycle through in the
210
+ digital single-qubit rotations of each layer.
211
+ entangler (AbstractBlock): 2-qubit entangling operation.
212
+ Supports CNOT, CZ, CRX, CRY, CRZ. Controlld rotations
213
+ will have variational parameters on the rotation angles.
214
+
215
+ Returns:
216
+ The digital Alternating Layer Ansatz (ALA) circuit.
217
+ """
218
+
219
+ try:
220
+ if entangler not in [CNOT, CZ, CRX, CRY, CRZ, CPHASE]:
221
+ raise ValueError(
222
+ "Please provide a valid two-qubit entangler operation for digital ALA."
223
+ )
224
+ except TypeError:
225
+ raise ValueError("Please provide a valid two-qubit entangler operation for digital ALA.")
226
+
227
+ rot_list = _rotations_digital(
228
+ n_qubits=n_qubits,
229
+ depth=depth,
230
+ support=support,
231
+ param_prefix=param_prefix,
232
+ operations=operations,
233
+ )
234
+
235
+ ent_list = _entanglers_ala_block_digital(
236
+ n_qubits,
237
+ m_block_qubits,
238
+ param_prefix=param_prefix + "_ent",
239
+ depth=depth,
240
+ support=support,
241
+ entangler=entangler,
242
+ )
243
+
244
+ layers = []
245
+ for d in range(depth):
246
+ layers.append(rot_list[d])
247
+ layers.append(ent_list[d])
248
+
249
+ return tag(chain(*layers), "ALA")
250
+
251
+
252
+ #################
253
+ ## sdaqc ALA ##
254
+ #################
255
+ def ala_sDAQC(*args: Any, **kwargs: Any) -> Any:
256
+ raise NotImplementedError
257
+
258
+
259
+ #################
260
+ ## bdaqc ALA ##
261
+ #################
262
+ def ala_bDAQC(*args: Any, **kwargs: Any) -> Any:
263
+ raise NotImplementedError
264
+
265
+
266
+ #################
267
+ ## analog ALA ##
268
+ #################
269
+ def ala_analog(*args: Any, **kwargs: Any) -> Any:
270
+ raise NotImplementedError