qadence 1.1.1__py3-none-any.whl → 1.2.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.
Files changed (44) hide show
  1. qadence/analog/__init__.py +4 -2
  2. qadence/analog/addressing.py +167 -0
  3. qadence/analog/constants.py +59 -0
  4. qadence/analog/device.py +82 -0
  5. qadence/analog/hamiltonian_terms.py +101 -0
  6. qadence/analog/parse_analog.py +120 -0
  7. qadence/backend.py +27 -1
  8. qadence/backends/braket/backend.py +1 -1
  9. qadence/backends/pulser/__init__.py +0 -1
  10. qadence/backends/pulser/backend.py +30 -15
  11. qadence/backends/pulser/config.py +19 -10
  12. qadence/backends/pulser/devices.py +57 -63
  13. qadence/backends/pulser/pulses.py +70 -12
  14. qadence/backends/pyqtorch/backend.py +2 -3
  15. qadence/backends/pyqtorch/config.py +18 -12
  16. qadence/backends/pyqtorch/convert_ops.py +12 -4
  17. qadence/backends/pytorch_wrapper.py +2 -1
  18. qadence/backends/utils.py +1 -10
  19. qadence/blocks/abstract.py +5 -1
  20. qadence/blocks/analog.py +18 -9
  21. qadence/blocks/block_to_tensor.py +11 -0
  22. qadence/blocks/primitive.py +81 -9
  23. qadence/constructors/__init__.py +4 -0
  24. qadence/constructors/feature_maps.py +84 -60
  25. qadence/constructors/hamiltonians.py +27 -98
  26. qadence/constructors/rydberg_feature_maps.py +113 -0
  27. qadence/divergences.py +12 -0
  28. qadence/extensions.py +1 -6
  29. qadence/finitediff.py +47 -0
  30. qadence/mitigations/readout.py +92 -25
  31. qadence/models/qnn.py +88 -23
  32. qadence/operations.py +55 -70
  33. qadence/parameters.py +10 -2
  34. qadence/register.py +91 -43
  35. qadence/transpile/__init__.py +1 -0
  36. qadence/transpile/apply_fn.py +40 -0
  37. qadence/types.py +19 -1
  38. qadence/utils.py +35 -0
  39. {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/METADATA +2 -2
  40. {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/RECORD +42 -36
  41. {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/WHEEL +1 -1
  42. qadence/analog/interaction.py +0 -198
  43. qadence/analog/utils.py +0 -132
  44. {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from .interaction import add_interaction
3
+ from .addressing import AddressingPattern
4
+ from .device import IdealDevice, RealisticDevice, RydbergDevice
5
+ from .parse_analog import add_background_hamiltonian
4
6
 
5
- __all__ = ["add_interaction"]
7
+ __all__ = ["RydbergDevice", "IdealDevice", "RealisticDevice", "AddressingPattern"]
@@ -0,0 +1,167 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, fields
4
+ from typing import Union
5
+ from warnings import warn
6
+
7
+ from sympy import Expr, Heaviside, exp
8
+ from torch import Tensor, pi
9
+
10
+ from qadence.parameters import Parameter, evaluate
11
+
12
+ # FIXME: Clarify the roles of these values in the context
13
+ # device specification and how they relate with the
14
+ # maximum values for delta and omega.
15
+ GLOBAL_MAX_AMPLITUDE = 300
16
+ GLOBAL_MAX_DETUNING = 2 * pi * 2000
17
+ LOCAL_MAX_AMPLITUDE = 3
18
+ LOCAL_MAX_DETUNING = 2 * pi * 20
19
+
20
+ TWeight = Union[str, float, Tensor, Parameter]
21
+
22
+
23
+ def sigmoid(x: Tensor, a: float, b: float) -> Expr:
24
+ return 1.0 / (1.0 + exp(-a * (x + b)))
25
+
26
+
27
+ @dataclass
28
+ class AddressingPattern:
29
+ """Semi-local addressing pattern."""
30
+
31
+ n_qubits: int
32
+ """Number of qubits in register."""
33
+
34
+ weights_amp: dict[int, TWeight]
35
+ """List of weights for fixed amplitude pattern that cannot be changed during the execution."""
36
+
37
+ weights_det: dict[int, TWeight]
38
+ """List of weights for fixed detuning pattern that cannot be changed during the execution."""
39
+
40
+ amp: TWeight = LOCAL_MAX_AMPLITUDE
41
+ """Maximum amplitude of the amplitude pattern felt by a single qubit."""
42
+
43
+ det: TWeight = LOCAL_MAX_DETUNING
44
+ """Maximum detuning of the detuning pattern felt by a single qubit."""
45
+
46
+ def _validate_weights(
47
+ self,
48
+ weights: dict[int, TWeight],
49
+ ) -> None:
50
+ for v in weights.values():
51
+ if not isinstance(v, (str, Parameter)):
52
+ if not (v >= 0.0 and v <= 1.0):
53
+ raise ValueError("Addressing pattern weights must sum fall in range [0.0, 1.0]")
54
+
55
+ def _constrain_weights(
56
+ self,
57
+ weights: dict[int, TWeight],
58
+ ) -> dict:
59
+ # augment weight dict if needed
60
+ weights = {
61
+ i: Parameter(0.0)
62
+ if i not in weights
63
+ else (Parameter(weights[i]) if not isinstance(weights[i], Parameter) else weights[i])
64
+ for i in range(self.n_qubits)
65
+ }
66
+
67
+ # restrict weights to [0, 1] range - equal to 0 everywhere else
68
+ weights = {
69
+ k: v if v.is_number else abs(v * (sigmoid(v, 20, 1) - sigmoid(v, 20.0, -1))) # type: ignore [union-attr]
70
+ for k, v in weights.items()
71
+ }
72
+
73
+ return weights
74
+
75
+ def _constrain_max_vals(self) -> None:
76
+ # enforce constraints:
77
+ # 0 <= amp <= GLOBAL_MAX_AMPLITUDE
78
+ # 0 <= abs(det) <= GLOBAL_MAX_DETUNING
79
+ self.amp = abs(
80
+ self.amp
81
+ * (
82
+ Heaviside(self.amp + GLOBAL_MAX_AMPLITUDE) # type: ignore [operator]
83
+ - Heaviside(self.amp - GLOBAL_MAX_AMPLITUDE) # type: ignore [operator]
84
+ )
85
+ )
86
+ self.det = -abs(
87
+ self.det
88
+ * (
89
+ Heaviside(self.det + GLOBAL_MAX_DETUNING)
90
+ - Heaviside(self.det - GLOBAL_MAX_DETUNING)
91
+ )
92
+ )
93
+
94
+ def _create_local_constraint(self, val: Expr, weights: dict, max_val: float) -> dict:
95
+ # enforce local constraints:
96
+ # amp * w_amp_i < LOCAL_MAX_AMPLITUDE or
97
+ # abs(det) * w_det_i < LOCAL_MAX_DETUNING
98
+ local_constr = {k: val * v for k, v in weights.items()}
99
+ local_constr = {k: Heaviside(v) - Heaviside(v - max_val) for k, v in local_constr.items()}
100
+
101
+ return local_constr
102
+
103
+ def _create_global_constraint(self, val: Expr, weights: dict, max_val: float) -> Expr:
104
+ # enforce global constraints:
105
+ # amp * sum(w_amp_0, w_amp_1, ...) < GLOBAL_MAX_AMPLITUDE or
106
+ # abs(det) * sum(w_det_0, w_det_1, ...) < GLOBAL_MAX_DETUNING
107
+ weighted_vals_global = val * sum([v for v in weights.values()])
108
+ weighted_vals_global = Heaviside(weighted_vals_global) - Heaviside(
109
+ weighted_vals_global - max_val
110
+ )
111
+
112
+ return weighted_vals_global
113
+
114
+ def __post_init__(self) -> None:
115
+ # validate amplitude/detuning weights
116
+ self._validate_weights(self.weights_amp)
117
+ self._validate_weights(self.weights_det)
118
+
119
+ # validate maximum global amplitude/detuning values
120
+ if not isinstance(self.amp, (str, Parameter)):
121
+ if self.amp > GLOBAL_MAX_AMPLITUDE:
122
+ warn("Maximum absolute value of amplitude is exceeded")
123
+ elif isinstance(self.amp, str):
124
+ self.amp = Parameter(self.amp, trainable=True)
125
+ if not isinstance(self.det, (str, Parameter)):
126
+ if abs(self.det) > GLOBAL_MAX_DETUNING:
127
+ warn("Maximum absolute value of detuning is exceeded")
128
+ elif isinstance(self.det, str):
129
+ self.det = Parameter(self.det, trainable=True)
130
+
131
+ # constrain amplitude/detuning parameterized weights to [0.0, 1.0] interval
132
+ self.weights_amp = self._constrain_weights(self.weights_amp)
133
+ self.weights_det = self._constrain_weights(self.weights_det)
134
+
135
+ # constrain max global amplitude and detuning to strict interval
136
+ self._constrain_max_vals()
137
+
138
+ # create additional local and global constraints for amplitude/detuning masks
139
+ self.local_constr_amp = self._create_local_constraint(
140
+ self.amp, self.weights_amp, LOCAL_MAX_AMPLITUDE
141
+ )
142
+ self.local_constr_det = self._create_local_constraint(
143
+ -self.det, self.weights_det, LOCAL_MAX_DETUNING
144
+ )
145
+ self.global_constr_amp = self._create_global_constraint(
146
+ self.amp, self.weights_amp, GLOBAL_MAX_AMPLITUDE
147
+ )
148
+ self.global_constr_det = self._create_global_constraint(
149
+ -self.det, self.weights_det, GLOBAL_MAX_DETUNING
150
+ )
151
+
152
+ # validate number of qubits in mask
153
+ if max(list(self.weights_amp.keys())) >= self.n_qubits:
154
+ raise ValueError("Amplitude weight specified for non-existing qubit")
155
+ if max(list(self.weights_det.keys())) >= self.n_qubits:
156
+ raise ValueError("Detuning weight specified for non-existing qubit")
157
+
158
+ def evaluate(self, weights: dict, values: dict) -> dict:
159
+ # evaluate weight expressions with actual values
160
+ return {k: evaluate(v, values, as_torch=True).flatten() for k, v in weights.items()} # type: ignore [union-attr]
161
+
162
+ def _to_dict(self) -> dict:
163
+ return {field.name: getattr(self, field.name) for field in fields(self)}
164
+
165
+ @classmethod
166
+ def _from_dict(cls, d: dict) -> AddressingPattern | None:
167
+ return cls(**d) if len(d) > 0 else None
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ # Ising coupling coefficient depending on the Rydberg level
4
+ # Include a normalization to the Planck constant hbar
5
+ # In units of [rad . µm^6 / µs]
6
+
7
+ C6_DICT = {
8
+ 50: 96120.72,
9
+ 51: 122241.6,
10
+ 52: 154693.02,
11
+ 53: 194740.36,
12
+ 54: 243973.91,
13
+ 55: 304495.01,
14
+ 56: 378305.98,
15
+ 57: 468027.05,
16
+ 58: 576714.85,
17
+ 59: 707911.38,
18
+ 60: 865723.02,
19
+ 61: 1054903.11,
20
+ 62: 1281042.11,
21
+ 63: 1550531.15,
22
+ 64: 1870621.31,
23
+ 65: 2249728.57,
24
+ 66: 2697498.69,
25
+ 67: 3224987.51,
26
+ 68: 3844734.37,
27
+ 69: 4571053.32,
28
+ 70: 5420158.53,
29
+ 71: 6410399.4,
30
+ 72: 7562637.31,
31
+ 73: 8900342.14,
32
+ 74: 10449989.62,
33
+ 75: 12241414.53,
34
+ 76: 14308028.03,
35
+ 77: 16687329.94,
36
+ 78: 19421333.62,
37
+ 79: 22557029.94,
38
+ 80: 26146720.74,
39
+ 81: 30248886.65,
40
+ 82: 34928448.69,
41
+ 83: 40257623.67,
42
+ 84: 46316557.88,
43
+ 85: 53194043.52,
44
+ 86: 60988354.64,
45
+ 87: 69808179.15,
46
+ 88: 79773468.88,
47
+ 89: 91016513.07,
48
+ 90: 103677784.57,
49
+ 91: 117933293.96,
50
+ 92: 133943541.9,
51
+ 93: 151907135.94,
52
+ 94: 172036137.34,
53
+ 95: 194562889.89,
54
+ 96: 219741590.56,
55
+ 97: 247850178.91,
56
+ 98: 279192193.77,
57
+ 99: 314098829.39,
58
+ 100: 352931119.11,
59
+ }
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, fields
4
+
5
+ from torch import pi
6
+
7
+ from qadence.analog import AddressingPattern
8
+ from qadence.types import DeviceType, Interaction
9
+
10
+
11
+ @dataclass(frozen=True, eq=True)
12
+ class RydbergDevice:
13
+ """
14
+ Dataclass for interacting Rydberg atoms under an Hamiltonian:
15
+
16
+ H = ∑_i [Ω/2 * (cos(φ) * Xᵢ - sin(φ) * Yᵢ) - δ * N_i] + H_int,
17
+
18
+ where:
19
+
20
+ H_int = ∑_(j<i) (C_6 / R**6) * (N_i @ N_j) for the NN interaction;
21
+
22
+ H_int = ∑_(j<i) (C_3 / R**3) * ((X_i @ X_j) + (Y_i @ Y_j)) for the XY interaction;
23
+ """
24
+
25
+ interaction: Interaction = Interaction.NN
26
+ """Defines the interaction Hamiltonian."""
27
+
28
+ rydberg_level: int = 60
29
+ """Rydberg level affecting the value of C_6."""
30
+
31
+ coeff_xy: float = 3700.00
32
+ """Value of C_3."""
33
+
34
+ max_detuning: float = 2 * pi * 4
35
+ """Maximum value of the detuning δ."""
36
+
37
+ max_amp: float = 2 * pi * 3
38
+ """Maximum value of the amplitude Ω."""
39
+
40
+ pattern: AddressingPattern | None = None
41
+ """Semi-local addressing pattern configuration."""
42
+
43
+ type: DeviceType = DeviceType.IDEALIZED
44
+ """DeviceType.IDEALIZED or REALISTIC to convert to the Pulser backend."""
45
+
46
+ def __post_init__(self) -> None:
47
+ # FIXME: Currently not supporting custom interaction functions.
48
+ if self.interaction not in [Interaction.NN, Interaction.XY]:
49
+ raise KeyError(
50
+ "RydbergDevice currently only supports Interaction.NN or Interaction.XY."
51
+ )
52
+
53
+ def _to_dict(self) -> dict:
54
+ device_dict = {}
55
+ for field in fields(self):
56
+ if field.name != "pattern":
57
+ device_dict[field.name] = getattr(self, field.name)
58
+ else:
59
+ device_dict[field.name] = (
60
+ self.pattern._to_dict() if self.pattern is not None else {}
61
+ )
62
+ return device_dict
63
+
64
+ @classmethod
65
+ def _from_dict(cls, d: dict) -> RydbergDevice:
66
+ pattern = AddressingPattern._from_dict(d["pattern"])
67
+ d["pattern"] = pattern
68
+ return cls(**d)
69
+
70
+
71
+ def IdealDevice(pattern: AddressingPattern | None = None) -> RydbergDevice:
72
+ return RydbergDevice(
73
+ pattern=pattern,
74
+ type=DeviceType.IDEALIZED,
75
+ )
76
+
77
+
78
+ def RealisticDevice(pattern: AddressingPattern | None = None) -> RydbergDevice:
79
+ return RydbergDevice(
80
+ pattern=pattern,
81
+ type=DeviceType.REALISTIC,
82
+ )
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ from sympy import cos, sin
4
+ from torch import float64, tensor
5
+
6
+ from qadence.analog.constants import C6_DICT
7
+ from qadence.blocks import add
8
+ from qadence.blocks.abstract import AbstractBlock
9
+ from qadence.blocks.analog import ConstantAnalogRotation
10
+ from qadence.constructors import hamiltonian_factory
11
+ from qadence.operations import I, N, X, Y, Z
12
+ from qadence.register import Register
13
+ from qadence.types import Interaction
14
+
15
+
16
+ def rydberg_interaction_hamiltonian(
17
+ register: Register,
18
+ ) -> AbstractBlock:
19
+ """
20
+ Computes the Rydberg Ising or XY interaction Hamiltonian for a register of qubits.
21
+
22
+ H_int = ∑_(j<i) (C_6 / R**6) * kron(N_i, N_j)
23
+
24
+ H_int = ∑_(j<i) (C_3 / R**3) * (kron(X_i, X_j) + kron(Y_i, Y_j))
25
+
26
+ Args:
27
+ register: the register of qubits.
28
+ """
29
+
30
+ distances = tensor(list(register.distances.values()), dtype=float64)
31
+ device_specs = register.device_specs
32
+
33
+ if device_specs.interaction == Interaction.NN:
34
+ c6 = C6_DICT[device_specs.rydberg_level]
35
+ strength_list = c6 / (distances**6)
36
+ elif device_specs.interaction == Interaction.XY:
37
+ c3 = device_specs.coeff_xy
38
+ strength_list = c3 / (distances**3)
39
+
40
+ return hamiltonian_factory(
41
+ register,
42
+ interaction=device_specs.interaction,
43
+ interaction_strength=strength_list,
44
+ use_all_node_pairs=True,
45
+ )
46
+
47
+
48
+ def rydberg_drive_hamiltonian(block: ConstantAnalogRotation, register: Register) -> AbstractBlock:
49
+ """
50
+ Computes the Rydberg drive Hamiltonian for some local or global rotation.
51
+
52
+ H_d = ∑_i (Ω/2 cos(φ) * X_i - sin(φ) * Y_i) - δ * N_i
53
+
54
+ Args:
55
+ block: the ConstantAnalogRotation block containing the parameters.
56
+ register: the register of qubits.
57
+ """
58
+
59
+ if block.qubit_support.is_global:
60
+ qubit_support = tuple(register.nodes)
61
+ else:
62
+ qubit_support = block.qubit_support
63
+
64
+ omega = block.parameters.omega
65
+ delta = block.parameters.delta
66
+ phase = block.parameters.phase
67
+
68
+ x_terms = (omega / 2) * add(cos(phase) * X(i) for i in qubit_support)
69
+ y_terms = (omega / 2) * add(sin(phase) * Y(i) for i in qubit_support)
70
+ n_terms = delta * add(N(i) for i in qubit_support)
71
+ h_drive: AbstractBlock = x_terms - y_terms - n_terms
72
+ return h_drive
73
+
74
+
75
+ def rydberg_pattern_hamiltonian(register: Register) -> AbstractBlock | None:
76
+ support = tuple(range(register.n_qubits))
77
+ pattern = register.device_specs.pattern
78
+ if pattern is not None:
79
+ amp = pattern.amp
80
+ det = pattern.det
81
+ weights_amp = pattern.weights_amp
82
+ weights_det = pattern.weights_det
83
+ local_constr_amp = pattern.local_constr_amp
84
+ local_constr_det = pattern.local_constr_det
85
+ global_constr_amp = pattern.global_constr_amp
86
+ global_constr_det = pattern.global_constr_det
87
+
88
+ p_amp_terms: AbstractBlock = (
89
+ (1 / 2) # type: ignore [operator]
90
+ * amp
91
+ * global_constr_amp
92
+ * add(X(i) * weights_amp[i] * local_constr_amp[i] for i in support) # type: ignore [operator]
93
+ )
94
+ p_det_terms: AbstractBlock = (
95
+ -det # type: ignore [operator]
96
+ * global_constr_det
97
+ * add(0.5 * (I(i) - Z(i)) * weights_det[i] * local_constr_det[i] for i in support) # type: ignore [operator]
98
+ )
99
+ return p_amp_terms + p_det_terms
100
+ else:
101
+ return None
@@ -0,0 +1,120 @@
1
+ from __future__ import annotations
2
+
3
+ from qadence.analog.hamiltonian_terms import (
4
+ rydberg_drive_hamiltonian,
5
+ rydberg_interaction_hamiltonian,
6
+ rydberg_pattern_hamiltonian,
7
+ )
8
+ from qadence.blocks import chain
9
+ from qadence.blocks.abstract import AbstractBlock
10
+ from qadence.blocks.analog import (
11
+ AnalogKron,
12
+ ConstantAnalogRotation,
13
+ WaitBlock,
14
+ )
15
+ from qadence.circuit import QuantumCircuit
16
+ from qadence.operations import HamEvo
17
+ from qadence.register import Register
18
+ from qadence.transpile import apply_fn_to_blocks
19
+
20
+
21
+ def add_background_hamiltonian(
22
+ circuit: QuantumCircuit | AbstractBlock,
23
+ register: Register | None = None,
24
+ ) -> QuantumCircuit | AbstractBlock:
25
+ """
26
+ Parses a `QuantumCircuit` to transform `AnalogBlocks` to `HamEvo`.
27
+
28
+ Depends on the circuit `Register` and included `RydbergDevice` specifications.
29
+
30
+ Currently checks if input is either circuit or block and adjusts
31
+ the ouput accordingly. Running this function on single blocks is
32
+ currently used for eigenvalue computation for GPSR.
33
+
34
+ Arguments:
35
+ circuit: the circuit to parse, or single block to transform.
36
+ register: needed for calling the function on a single block.
37
+ """
38
+ # FIXME: revisit eigenvalues of analog blocks and clean this code.
39
+
40
+ is_circuit_input = isinstance(circuit, QuantumCircuit)
41
+
42
+ if not is_circuit_input and register is None:
43
+ raise ValueError("Block input requires an input to the `register` argument.")
44
+
45
+ input_block: AbstractBlock = circuit.block if is_circuit_input else circuit # type: ignore
46
+ input_register: Register = circuit.register if is_circuit_input else register # type: ignore
47
+
48
+ if input_register.device_specs is not None:
49
+ # Create interaction hamiltonian:
50
+ h_int = rydberg_interaction_hamiltonian(input_register)
51
+
52
+ # Create addressing pattern:
53
+ h_addr = rydberg_pattern_hamiltonian(input_register)
54
+
55
+ output_block = apply_fn_to_blocks(
56
+ input_block,
57
+ _analog_to_hevo,
58
+ input_register,
59
+ (h_int, h_addr),
60
+ )
61
+ else:
62
+ output_block = input_block
63
+
64
+ if is_circuit_input:
65
+ return QuantumCircuit(input_register, output_block)
66
+ else:
67
+ return output_block
68
+
69
+
70
+ def _build_ham_evo(
71
+ block: WaitBlock | ConstantAnalogRotation,
72
+ h_int: AbstractBlock,
73
+ h_drive: AbstractBlock | None,
74
+ h_addr: AbstractBlock | None,
75
+ ) -> HamEvo:
76
+ duration = block.parameters.duration
77
+ h_block = h_int
78
+ if h_drive is not None:
79
+ h_block += h_drive
80
+ if block.add_pattern and h_addr is not None:
81
+ h_block += h_addr
82
+ return HamEvo(h_block, duration / 1000)
83
+
84
+
85
+ def _analog_to_hevo(
86
+ block: AbstractBlock,
87
+ register: Register,
88
+ h_terms: tuple[AbstractBlock, AbstractBlock | None],
89
+ ) -> AbstractBlock:
90
+ """
91
+ Converter from AnalogBlock to the respective HamEvo.
92
+
93
+ Any other block not covered by the specific conditions below is left unchanged.
94
+ """
95
+
96
+ h_int, h_addr = h_terms
97
+
98
+ if isinstance(block, WaitBlock):
99
+ return _build_ham_evo(block, h_int, None, h_addr)
100
+
101
+ if isinstance(block, ConstantAnalogRotation):
102
+ h_drive = rydberg_drive_hamiltonian(block, register)
103
+ return _build_ham_evo(block, h_int, h_drive, h_addr)
104
+
105
+ if isinstance(block, AnalogKron):
106
+ # Needed to ensure kronned Analog blocks are implemented
107
+ # in sequence, consistent with the current Pulser implementation.
108
+ # FIXME: Revisit this assumption and the need for AnalogKron to have
109
+ # the same duration, and clean this code accordingly.
110
+ # https://github.com/pasqal-io/qadence/issues/226
111
+ ops = []
112
+ for block in block.blocks:
113
+ if isinstance(block, ConstantAnalogRotation):
114
+ h_drive = rydberg_drive_hamiltonian(block, register)
115
+ ops.append(_build_ham_evo(block, h_int, h_drive, h_addr))
116
+ if len(ops) == 0:
117
+ ops.append(_build_ham_evo(block, h_int, None, h_addr)) # type: ignore [arg-type]
118
+ return chain(*ops)
119
+
120
+ return block
qadence/backend.py CHANGED
@@ -26,6 +26,7 @@ from qadence.mitigations import Mitigations
26
26
  from qadence.noise import Noise
27
27
  from qadence.parameters import stringify
28
28
  from qadence.types import BackendName, DiffMode, Endianness
29
+ from qadence.utils import validate_values_and_state
29
30
 
30
31
  logger = get_logger(__file__)
31
32
 
@@ -255,7 +256,7 @@ class Backend(ABC):
255
256
  raise NotImplementedError
256
257
 
257
258
  @abstractmethod
258
- def run(
259
+ def _run(
259
260
  self,
260
261
  circuit: ConvertedCircuit,
261
262
  param_values: dict[str, Tensor] = {},
@@ -277,6 +278,31 @@ class Backend(ABC):
277
278
  """
278
279
  raise NotImplementedError
279
280
 
281
+ def run(
282
+ self,
283
+ circuit: ConvertedCircuit,
284
+ param_values: dict[str, Tensor] = {},
285
+ state: Tensor | None = None,
286
+ endianness: Endianness = Endianness.BIG,
287
+ *args: Any,
288
+ **kwargs: Any,
289
+ ) -> Tensor:
290
+ """Run a circuit and return the resulting wave function.
291
+
292
+ Arguments:
293
+ circuit: A converted circuit as returned by `backend.circuit`.
294
+ param_values: _**Already embedded**_ parameters of the circuit. See
295
+ [`embedding`][qadence.blocks.embedding.embedding] for more info.
296
+ state: Initial state.
297
+ endianness: Endianness of the resulting wavefunction.
298
+
299
+ Returns:
300
+ A list of Counter objects where each key represents a bitstring
301
+ and its value the number of times it has been sampled from the given wave function.
302
+ """
303
+ validate_values_and_state(state, circuit.abstract.n_qubits, param_values)
304
+ return self._run(circuit, param_values, state, endianness, *args, **kwargs)
305
+
280
306
  @abstractmethod
281
307
  def run_dm(
282
308
  self,
@@ -87,7 +87,7 @@ class Backend(BackendInterface):
87
87
  ).squeeze(0)
88
88
  return ConvertedObservable(native=native, abstract=obs, original=obs)
89
89
 
90
- def run(
90
+ def _run(
91
91
  self,
92
92
  circuit: ConvertedCircuit,
93
93
  param_values: dict[str, Tensor] = {},
@@ -1,5 +1,4 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from .backend import Backend, Configuration
4
- from .devices import Device
5
4
  from .pulses import supported_gates