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.
Files changed (64) hide show
  1. qadence/__init__.py +1 -0
  2. qadence/analog/__init__.py +4 -2
  3. qadence/analog/addressing.py +167 -0
  4. qadence/analog/constants.py +59 -0
  5. qadence/analog/device.py +82 -0
  6. qadence/analog/hamiltonian_terms.py +101 -0
  7. qadence/analog/parse_analog.py +120 -0
  8. qadence/backend.py +42 -12
  9. qadence/backends/__init__.py +1 -2
  10. qadence/backends/api.py +27 -9
  11. qadence/backends/braket/backend.py +3 -2
  12. qadence/backends/horqrux/__init__.py +5 -0
  13. qadence/backends/horqrux/backend.py +216 -0
  14. qadence/backends/horqrux/config.py +26 -0
  15. qadence/backends/horqrux/convert_ops.py +273 -0
  16. qadence/backends/jax_utils.py +45 -0
  17. qadence/backends/pulser/__init__.py +0 -1
  18. qadence/backends/pulser/backend.py +31 -15
  19. qadence/backends/pulser/config.py +19 -10
  20. qadence/backends/pulser/devices.py +57 -63
  21. qadence/backends/pulser/pulses.py +70 -12
  22. qadence/backends/pyqtorch/backend.py +4 -4
  23. qadence/backends/pyqtorch/config.py +18 -12
  24. qadence/backends/pyqtorch/convert_ops.py +15 -7
  25. qadence/backends/utils.py +5 -9
  26. qadence/blocks/abstract.py +5 -1
  27. qadence/blocks/analog.py +18 -9
  28. qadence/blocks/block_to_tensor.py +11 -0
  29. qadence/blocks/embedding.py +46 -24
  30. qadence/blocks/primitive.py +81 -9
  31. qadence/blocks/utils.py +20 -1
  32. qadence/circuit.py +3 -9
  33. qadence/constructors/__init__.py +4 -0
  34. qadence/constructors/feature_maps.py +84 -60
  35. qadence/constructors/hamiltonians.py +27 -98
  36. qadence/constructors/rydberg_feature_maps.py +113 -0
  37. qadence/divergences.py +12 -0
  38. qadence/engines/__init__.py +0 -0
  39. qadence/engines/differentiable_backend.py +152 -0
  40. qadence/engines/jax/__init__.py +8 -0
  41. qadence/engines/jax/differentiable_backend.py +73 -0
  42. qadence/engines/jax/differentiable_expectation.py +94 -0
  43. qadence/engines/torch/__init__.py +4 -0
  44. qadence/engines/torch/differentiable_backend.py +85 -0
  45. qadence/extensions.py +21 -9
  46. qadence/finitediff.py +47 -0
  47. qadence/mitigations/readout.py +92 -25
  48. qadence/ml_tools/models.py +10 -3
  49. qadence/models/qnn.py +88 -23
  50. qadence/models/quantum_model.py +13 -2
  51. qadence/operations.py +55 -70
  52. qadence/parameters.py +24 -13
  53. qadence/register.py +91 -43
  54. qadence/transpile/__init__.py +1 -0
  55. qadence/transpile/apply_fn.py +40 -0
  56. qadence/types.py +32 -2
  57. qadence/utils.py +35 -0
  58. {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/METADATA +22 -3
  59. {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/RECORD +62 -44
  60. {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/WHEEL +1 -1
  61. qadence/analog/interaction.py +0 -198
  62. qadence/analog/utils.py +0 -132
  63. /qadence/{backends/pytorch_wrapper.py → engines/torch/differentiable_expectation.py} +0 -0
  64. {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -28,14 +28,14 @@ from qadence.noise.protocols import apply_noise
28
28
  from qadence.overlap import overlap_exact
29
29
  from qadence.register import Register
30
30
  from qadence.transpile import transpile
31
- from qadence.types import BackendName, Endianness
31
+ from qadence.types import BackendName, DeviceType, Endianness, Engine
32
32
 
33
33
  from .channels import GLOBAL_CHANNEL, LOCAL_CHANNEL
34
34
  from .cloud import get_client
35
35
  from .config import Configuration
36
36
  from .convert_ops import convert_observable
37
- from .devices import Device, IdealDevice, RealisticDevice
38
- from .pulses import add_pulses
37
+ from .devices import IdealDevice, RealisticDevice
38
+ from .pulses import add_addressing_pattern, add_pulses
39
39
 
40
40
  logger = get_logger(__file__)
41
41
 
@@ -54,25 +54,33 @@ def create_register(register: Register) -> PulserRegister:
54
54
 
55
55
 
56
56
  def make_sequence(circ: QuantumCircuit, config: Configuration) -> Sequence:
57
- if config.device_type == Device.IDEALIZED:
58
- device = IdealDevice
59
- elif config.device_type == Device.REALISTIC:
60
- device = RealisticDevice
57
+ qadence_register = circ.register
58
+ device_specs = qadence_register.device_specs
59
+
60
+ if device_specs.type == DeviceType.IDEALIZED:
61
+ device = IdealDevice(
62
+ device_specs.rydberg_level, device_specs.max_detuning, device_specs.max_amp
63
+ )
64
+ elif device_specs.type == DeviceType.REALISTIC:
65
+ device = RealisticDevice(
66
+ device_specs.rydberg_level, device_specs.max_detuning, device_specs.max_amp
67
+ )
61
68
  else:
62
- raise ValueError("Specified device is not supported.")
69
+ raise ValueError(
70
+ f"Specified device of type {device_specs.type} is not supported by the pulser backend."
71
+ )
63
72
 
64
73
  ########
65
74
  # FIXME: Remove the block below in V1.1.0
66
- register = circ.register
67
75
  if config.spacing is not None:
68
76
  logger.warning(
69
77
  "Passing register spacing in the backend configuration is deprecated. "
70
78
  "Please pass it in the register directly, as detailed in the register tutorial."
71
79
  )
72
80
  # Rescales the register coordinates, as was done with the previous "spacing" argument.
73
- register = register.rescale_coords(scaling=config.spacing)
81
+ qadence_register = qadence_register.rescale_coords(scaling=config.spacing)
74
82
  else:
75
- if register.min_distance < 4.0:
83
+ if qadence_register.min_distance < 4.0:
76
84
  # Throws warning for minimum distance below 4 because the typical values used
77
85
  # for the standard pulser device parameters is ~7-8, so this likely means the user
78
86
  # forgot to set the spacing at register creation.
@@ -83,15 +91,14 @@ def make_sequence(circ: QuantumCircuit, config: Configuration) -> Sequence:
83
91
  )
84
92
  ########
85
93
 
86
- pulser_register = create_register(register)
94
+ pulser_register = create_register(qadence_register)
87
95
 
88
96
  sequence = Sequence(pulser_register, device)
89
97
 
90
98
  sequence.declare_channel(GLOBAL_CHANNEL, "rydberg_global")
91
99
  sequence.declare_channel(LOCAL_CHANNEL, "rydberg_local", initial_target=0)
92
100
 
93
- add_pulses(sequence, circ.block, config, circ.register)
94
- sequence.measure()
101
+ add_pulses(sequence, circ.block, config, qadence_register)
95
102
 
96
103
  return sequence
97
104
 
@@ -152,6 +159,7 @@ class Backend(BackendInterface):
152
159
  with_noise: bool = False
153
160
  native_endianness: Endianness = Endianness.BIG
154
161
  config: Configuration = field(default_factory=Configuration)
162
+ engine: Engine = Engine.TORCH
155
163
 
156
164
  def circuit(self, circ: QuantumCircuit) -> Sequence:
157
165
  passes = self.config.transpilation_passes
@@ -192,7 +200,7 @@ class Backend(BackendInterface):
192
200
 
193
201
  return circuit.native.build(**numpy_param_values)
194
202
 
195
- def run(
203
+ def _run(
196
204
  self,
197
205
  circuit: ConvertedCircuit,
198
206
  param_values: dict[str, Tensor] = {},
@@ -213,6 +221,10 @@ class Backend(BackendInterface):
213
221
 
214
222
  for i, param_values_el in enumerate(vals):
215
223
  sequence = self.assign_parameters(circuit, param_values_el)
224
+ pattern = circuit.original.register.device_specs.pattern
225
+ if pattern is not None:
226
+ add_addressing_pattern(sequence, pattern)
227
+ sequence.measure()
216
228
  sim_result = simulate_sequence(sequence, self.config, state, n_shots=None)
217
229
  wf = (
218
230
  sim_result.get_final_state( # type:ignore [union-attr]
@@ -281,6 +293,10 @@ class Backend(BackendInterface):
281
293
  samples = []
282
294
  for param_values_el in vals:
283
295
  sequence = self.assign_parameters(circuit, param_values_el)
296
+ pattern = circuit.original.register.device_specs.pattern
297
+ if pattern is not None:
298
+ add_addressing_pattern(sequence, pattern)
299
+ sequence.measure()
284
300
  sample = simulate_sequence(sequence, self.config, state, n_shots=n_shots)
285
301
  samples.append(sample)
286
302
  if endianness != self.native_endianness:
@@ -8,9 +8,7 @@ from pasqal_cloud.device import EmulatorType
8
8
  from pulser_simulation.simconfig import SimConfig
9
9
 
10
10
  from qadence.backend import BackendConfiguration
11
- from qadence.blocks.analog import Interaction
12
-
13
- from .devices import Device
11
+ from qadence.types import DeviceType, Interaction
14
12
 
15
13
  DEFAULT_CLOUD_ENV = "prod"
16
14
 
@@ -27,12 +25,11 @@ class CloudConfiguration:
27
25
 
28
26
  @dataclass
29
27
  class Configuration(BackendConfiguration):
30
- device_type: Device = Device.IDEALIZED
28
+ device_type: DeviceType = DeviceType.IDEALIZED
31
29
  """The type of quantum Device to use in the simulations.
32
30
 
33
- Choose
34
- between IDEALIZED and REALISTIC. This influences pulse duration, max
35
- amplitude, minimum atom spacing and other properties of the system
31
+ FIXME: This is deprecated, the device_type is now controlled in the
32
+ Qadence Device, as detailed in the documentation.
36
33
  """
37
34
 
38
35
  sampling_rate: float = 1.0
@@ -67,19 +64,31 @@ class Configuration(BackendConfiguration):
67
64
  """
68
65
 
69
66
  amplitude_local: Optional[float] = None
70
- """Default pulse amplitude on local channel."""
67
+ """Default pulse amplitude on local channel.
68
+
69
+ FIXME: To be deprecated.
70
+ """
71
71
 
72
72
  amplitude_global: Optional[float] = None
73
- """Default pulse amplitude on global channel."""
73
+ """Default pulse amplitude on global channel.
74
+
75
+ FIXME: To be deprecated.
76
+ """
74
77
 
75
78
  detuning: Optional[float] = None
76
- """Default value for the detuning pulses."""
79
+ """Default value for the detuning pulses.
80
+
81
+ FIXME: To be deprecated.
82
+ """
77
83
 
78
84
  interaction: Interaction = Interaction.NN
79
85
  """Type of interaction introduced in the Hamiltonian.
80
86
 
81
87
  Currently, only
82
88
  NN interaction is support. XY interaction is possible but not implemented
89
+
90
+ FIXME: This is deprecated, the interaction is now controlled in the
91
+ Qadence Device, as detailed in the documentation.
83
92
  """
84
93
 
85
94
  # configuration for cloud simulations
@@ -3,75 +3,69 @@ from __future__ import annotations
3
3
  from numpy import pi
4
4
  from pulser.channels.channels import Rydberg
5
5
  from pulser.channels.eom import RydbergBeam, RydbergEOM
6
- from pulser.devices._device_datacls import Device as PulserDevice
7
6
  from pulser.devices._device_datacls import VirtualDevice
8
7
 
9
- from qadence.types import StrEnum
10
8
 
11
9
  # Idealized virtual device
12
- IdealDevice = VirtualDevice(
13
- name="IdealizedDevice",
14
- dimensions=2,
15
- rydberg_level=60,
16
- max_atom_num=100,
17
- max_radial_distance=100,
18
- min_atom_distance=0,
19
- channel_objects=(
20
- Rydberg.Global(max_abs_detuning=2 * pi * 4, max_amp=2 * pi * 3),
21
- Rydberg.Local(max_targets=1000, max_abs_detuning=2 * pi * 4, max_amp=2 * pi * 3),
22
- ),
23
- )
10
+ def IdealDevice(
11
+ rydberg_level: int = 60, max_abs_detuning: float = 2 * pi * 4, max_amp: float = 2 * pi * 3
12
+ ) -> VirtualDevice:
13
+ return VirtualDevice(
14
+ name="IdealizedDevice",
15
+ dimensions=2,
16
+ rydberg_level=rydberg_level,
17
+ max_atom_num=100,
18
+ max_radial_distance=100,
19
+ min_atom_distance=0,
20
+ channel_objects=(
21
+ Rydberg.Global(max_abs_detuning=max_abs_detuning, max_amp=max_amp),
22
+ Rydberg.Local(max_targets=1000, max_abs_detuning=max_abs_detuning, max_amp=max_amp),
23
+ ),
24
+ )
24
25
 
25
26
 
26
- # device with realistic specs with local channels and custom bandwith.
27
- RealisticDevice = PulserDevice(
28
- name="RealisticDevice",
29
- dimensions=2,
30
- rydberg_level=60,
31
- max_atom_num=100,
32
- max_radial_distance=60,
33
- min_atom_distance=5,
34
- channel_objects=(
35
- Rydberg.Global(
36
- max_abs_detuning=2 * pi * 4,
37
- max_amp=2 * pi * 1.5,
38
- clock_period=4,
39
- min_duration=16,
40
- max_duration=4000,
41
- mod_bandwidth=16,
42
- eom_config=RydbergEOM(
43
- limiting_beam=RydbergBeam.RED,
44
- max_limiting_amp=40 * 2 * pi,
45
- intermediate_detuning=700 * 2 * pi,
46
- mod_bandwidth=24,
47
- controlled_beams=(RydbergBeam.BLUE,),
27
+ # Device with realistic specs with local channels and custom bandwith.
28
+ def RealisticDevice(
29
+ rydberg_level: int = 60, max_abs_detuning: float = 2 * pi * 4, max_amp: float = 2 * pi * 3
30
+ ) -> VirtualDevice:
31
+ return VirtualDevice(
32
+ name="RealisticDevice",
33
+ dimensions=2,
34
+ rydberg_level=rydberg_level,
35
+ max_atom_num=100,
36
+ max_radial_distance=60,
37
+ min_atom_distance=5,
38
+ channel_objects=(
39
+ Rydberg.Global(
40
+ max_abs_detuning=max_abs_detuning,
41
+ max_amp=max_amp * 0.5,
42
+ clock_period=4,
43
+ min_duration=16,
44
+ max_duration=4000,
45
+ mod_bandwidth=16,
46
+ eom_config=RydbergEOM(
47
+ limiting_beam=RydbergBeam.RED,
48
+ max_limiting_amp=40 * 2 * pi,
49
+ intermediate_detuning=700 * 2 * pi,
50
+ mod_bandwidth=24,
51
+ controlled_beams=(RydbergBeam.BLUE,),
52
+ ),
48
53
  ),
49
- ),
50
- Rydberg.Local(
51
- max_targets=20,
52
- max_abs_detuning=2 * pi * 4,
53
- max_amp=2 * pi * 3,
54
- clock_period=4,
55
- min_duration=16,
56
- max_duration=2**26,
57
- mod_bandwidth=16,
58
- eom_config=RydbergEOM(
59
- limiting_beam=RydbergBeam.RED,
60
- max_limiting_amp=40 * 2 * pi,
61
- intermediate_detuning=700 * 2 * pi,
62
- mod_bandwidth=24,
63
- controlled_beams=(RydbergBeam.BLUE,),
54
+ Rydberg.Local(
55
+ max_targets=20,
56
+ max_abs_detuning=max_abs_detuning,
57
+ max_amp=max_amp,
58
+ clock_period=4,
59
+ min_duration=16,
60
+ max_duration=2**26,
61
+ mod_bandwidth=16,
62
+ eom_config=RydbergEOM(
63
+ limiting_beam=RydbergBeam.RED,
64
+ max_limiting_amp=40 * 2 * pi,
65
+ intermediate_detuning=700 * 2 * pi,
66
+ mod_bandwidth=24,
67
+ controlled_beams=(RydbergBeam.BLUE,),
68
+ ),
64
69
  ),
65
70
  ),
66
- ),
67
- )
68
-
69
-
70
- class Device(StrEnum):
71
- """Supported types of devices for Pulser backend."""
72
-
73
- IDEALIZED = IdealDevice
74
- """Idealized device, least realistic."""
75
-
76
- REALISTIC = RealisticDevice
77
- """Device with realistic specs."""
71
+ )
@@ -11,6 +11,7 @@ from pulser.sequence.sequence import Sequence
11
11
  from pulser.waveforms import CompositeWaveform, ConstantWaveform, RampWaveform
12
12
 
13
13
  from qadence import Register
14
+ from qadence.analog import AddressingPattern
14
15
  from qadence.blocks import AbstractBlock, CompositeBlock
15
16
  from qadence.blocks.analog import (
16
17
  AnalogBlock,
@@ -19,6 +20,7 @@ from qadence.blocks.analog import (
19
20
  Interaction,
20
21
  WaitBlock,
21
22
  )
23
+ from qadence.logger import get_logger
22
24
  from qadence.operations import RX, RY, RZ, AnalogEntanglement, OpName
23
25
  from qadence.parameters import evaluate
24
26
 
@@ -26,6 +28,8 @@ from .channels import GLOBAL_CHANNEL, LOCAL_CHANNEL
26
28
  from .config import Configuration
27
29
  from .waveforms import SquareWaveform
28
30
 
31
+ logger = get_logger(__file__)
32
+
29
33
  TVar = Union[Variable, VariableItem]
30
34
 
31
35
  supported_gates = [
@@ -42,6 +46,53 @@ supported_gates = [
42
46
  ]
43
47
 
44
48
 
49
+ def add_addressing_pattern(
50
+ sequence: Sequence,
51
+ pattern: AddressingPattern,
52
+ ) -> None:
53
+ total_duration = sequence.get_duration()
54
+ n_qubits = len(sequence.register.qubits)
55
+
56
+ support = tuple(range(n_qubits))
57
+
58
+ amp = pattern.amp
59
+ det = pattern.det
60
+ weights_amp = pattern.weights_amp
61
+ weights_det = pattern.weights_det
62
+ local_constr_amp = pattern.local_constr_amp
63
+ local_constr_det = pattern.local_constr_det
64
+ global_constr_amp = pattern.global_constr_amp
65
+ global_constr_det = pattern.global_constr_det
66
+
67
+ for i in support:
68
+ # declare separate local channel for each qubit
69
+ sequence.declare_channel(f"ch_q{i}", "rydberg_local", initial_target=0)
70
+
71
+ # add amplitude and detuning patterns
72
+ for i in support:
73
+ if weights_amp[i].is_number: # type: ignore [union-attr]
74
+ w_amp = evaluate(weights_amp[i], as_torch=True) * local_constr_amp[i]
75
+ else:
76
+ raise ValueError(
77
+ "Pulser backend currently doesn't support parametrized amplitude pattern weights."
78
+ )
79
+
80
+ if weights_det[i].is_number: # type: ignore [union-attr]
81
+ w_det = evaluate(weights_det[i], as_torch=True) * local_constr_det[i]
82
+ else:
83
+ raise ValueError(
84
+ "Pulser backend currently doesn't support parametrized detuning pattern weights."
85
+ )
86
+
87
+ omega = global_constr_amp * amp * w_amp
88
+ detuning = global_constr_det * det * w_det
89
+ pulse = Pulse.ConstantPulse(
90
+ duration=total_duration, amplitude=omega, detuning=detuning, phase=0
91
+ )
92
+ sequence.target(i, f"ch_q{i}")
93
+ sequence.add(pulse, f"ch_q{i}", protocol="no-delay")
94
+
95
+
45
96
  def add_pulses(
46
97
  sequence: Sequence,
47
98
  block: AbstractBlock,
@@ -58,7 +109,7 @@ def add_pulses(
58
109
  if not isinstance(qubit_support[0], int):
59
110
  qubit_support = tuple(range(n_qubits))
60
111
 
61
- if isinstance(block, AnalogBlock) and config.interaction != Interaction.NN:
112
+ if isinstance(block, AnalogBlock) and qc_register.device_specs.interaction != Interaction.NN:
62
113
  raise ValueError(f"Pulser does not support other interactions than '{Interaction.NN}'")
63
114
 
64
115
  local_channel = sequence.device.channels["rydberg_local"]
@@ -70,6 +121,11 @@ def add_pulses(
70
121
 
71
122
  # TODO: lets move those to `@singledipatch`ed functions
72
123
  if isinstance(block, WaitBlock):
124
+ if not block.add_pattern:
125
+ logger.warning(
126
+ "Found block with `add_pattern = False`. This is not yet supported in the Pulser "
127
+ "backend. If an addressing pattern is specified, it will be added to all blocks."
128
+ )
73
129
  # wait if its a global wait
74
130
  if block.qubit_support.is_global:
75
131
  (uuid, duration) = block.parameters.uuid_param("duration")
@@ -85,25 +141,30 @@ def add_pulses(
85
141
  raise ValueError("Trying to wait on qubits outside of support.")
86
142
 
87
143
  elif isinstance(block, ConstantAnalogRotation):
144
+ if not block.add_pattern:
145
+ logger.warning(
146
+ "Found block with `add_pattern = False`. This is not yet supported in the Pulser "
147
+ "backend. If an addressing pattern is specified, it will be added to all blocks."
148
+ )
88
149
  ps = block.parameters
89
- (a_uuid, alpha) = ps.uuid_param("alpha")
150
+ (t_uuid, duration) = ps.uuid_param("duration")
90
151
  (w_uuid, omega) = ps.uuid_param("omega")
91
152
  (p_uuid, phase) = ps.uuid_param("phase")
92
153
  (d_uuid, detuning) = ps.uuid_param("delta")
93
154
 
94
- a = evaluate(alpha) if alpha.is_number else sequence.declare_variable(a_uuid)
155
+ t = evaluate(duration) if duration.is_number else sequence.declare_variable(t_uuid)
95
156
  w = evaluate(omega) if omega.is_number else sequence.declare_variable(w_uuid)
96
157
  p = evaluate(phase) if phase.is_number else sequence.declare_variable(p_uuid)
97
158
  d = evaluate(detuning) if detuning.is_number else sequence.declare_variable(d_uuid)
98
159
 
99
160
  # calculate generator eigenvalues
100
- block.eigenvalues_generator = block.compute_eigenvalues_generator(qc_register, block)
161
+ block.eigenvalues_generator = block.compute_eigenvalues_generator(block, qc_register)
101
162
 
102
163
  if block.qubit_support.is_global:
103
- pulse = analog_rot_pulse(a, w, p, d, global_channel, config)
164
+ pulse = analog_rot_pulse(t, w, p, d, global_channel, config)
104
165
  sequence.add(pulse, GLOBAL_CHANNEL, protocol="wait-for-all")
105
166
  else:
106
- pulse = analog_rot_pulse(a, w, p, d, local_channel, config)
167
+ pulse = analog_rot_pulse(t, w, p, d, local_channel, config)
107
168
  sequence.target(qubit_support, LOCAL_CHANNEL)
108
169
  sequence.add(pulse, LOCAL_CHANNEL, protocol="wait-for-all")
109
170
 
@@ -136,7 +197,7 @@ def add_pulses(
136
197
 
137
198
 
138
199
  def analog_rot_pulse(
139
- alpha: TVar | float,
200
+ duration: TVar | float,
140
201
  omega: TVar | float,
141
202
  phase: TVar | float,
142
203
  detuning: TVar | float,
@@ -154,12 +215,9 @@ def analog_rot_pulse(
154
215
  max_amp = omega
155
216
  max_det = detuning
156
217
 
157
- # get pulse duration in ns
158
- duration = 1000 * abs(alpha) / np.sqrt(omega**2 + detuning**2)
159
-
160
218
  # create amplitude waveform
161
219
  amp_wf = SquareWaveform.from_duration(
162
- duration=duration, # type: ignore
220
+ duration=abs(duration), # type: ignore
163
221
  max_amp=max_amp, # type: ignore[arg-type]
164
222
  duration_steps=channel.clock_period, # type: ignore[attr-defined]
165
223
  min_duration=channel.min_duration,
@@ -167,7 +225,7 @@ def analog_rot_pulse(
167
225
 
168
226
  # create detuning waveform
169
227
  det_wf = SquareWaveform.from_duration(
170
- duration=duration, # type: ignore
228
+ duration=abs(duration), # type: ignore
171
229
  max_amp=max_det, # type: ignore[arg-type]
172
230
  duration_steps=channel.clock_period, # type: ignore[attr-defined]
173
231
  min_duration=channel.min_duration,
@@ -11,7 +11,6 @@ from torch import Tensor
11
11
  from qadence.backend import Backend as BackendInterface
12
12
  from qadence.backend import ConvertedCircuit, ConvertedObservable
13
13
  from qadence.backends.utils import (
14
- infer_batchsize,
15
14
  pyqify,
16
15
  to_list_of_dicts,
17
16
  unpyqify,
@@ -31,8 +30,8 @@ from qadence.transpile import (
31
30
  scale_primitive_blocks_only,
32
31
  transpile,
33
32
  )
34
- from qadence.types import BackendName, Endianness
35
- from qadence.utils import int_to_basis
33
+ from qadence.types import BackendName, Endianness, Engine
34
+ from qadence.utils import infer_batchsize, int_to_basis
36
35
 
37
36
  from .config import Configuration, default_passes
38
37
  from .convert_ops import convert_block, convert_observable
@@ -53,6 +52,7 @@ class Backend(BackendInterface):
53
52
  with_noise: bool = False
54
53
  native_endianness: Endianness = Endianness.BIG
55
54
  config: Configuration = field(default_factory=Configuration)
55
+ engine: Engine = Engine.TORCH
56
56
 
57
57
  def circuit(self, circuit: QuantumCircuit) -> ConvertedCircuit:
58
58
  passes = self.config.transpilation_passes
@@ -80,7 +80,7 @@ class Backend(BackendInterface):
80
80
  (native,) = convert_observable(block, n_qubits=n_qubits, config=self.config)
81
81
  return ConvertedObservable(native=native, abstract=block, original=observable)
82
82
 
83
- def run(
83
+ def _run(
84
84
  self,
85
85
  circuit: ConvertedCircuit,
86
86
  param_values: dict[str, Tensor] = {},
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from typing import Callable
5
5
 
6
- from qadence.analog import add_interaction
6
+ from qadence.analog import add_background_hamiltonian
7
7
  from qadence.backend import BackendConfiguration
8
8
  from qadence.logger import get_logger
9
9
  from qadence.transpile import (
@@ -12,19 +12,28 @@ from qadence.transpile import (
12
12
  flatten,
13
13
  scale_primitive_blocks_only,
14
14
  )
15
- from qadence.types import AlgoHEvo, Interaction
15
+ from qadence.types import AlgoHEvo
16
16
 
17
17
  logger = get_logger(__name__)
18
18
 
19
19
 
20
20
  def default_passes(config: Configuration) -> list[Callable]:
21
- return [
22
- lambda circ: add_interaction(circ, interaction=config.interaction),
23
- lambda circ: blockfn_to_circfn(chain_single_qubit_ops)(circ)
24
- if config.use_single_qubit_composition
25
- else blockfn_to_circfn(flatten)(circ),
26
- blockfn_to_circfn(scale_primitive_blocks_only),
27
- ]
21
+ passes: list = []
22
+
23
+ # Replaces AnalogBlocks with respective HamEvo in the circuit block tree:
24
+ passes.append(add_background_hamiltonian)
25
+
26
+ if config.use_single_qubit_composition:
27
+ # Composes chains of single-qubit gates into a single unitary before applying to the state:
28
+ passes.append(lambda circ: blockfn_to_circfn(chain_single_qubit_ops)(circ))
29
+ else:
30
+ # Flattens nested composed blocks:
31
+ passes.append(lambda circ: blockfn_to_circfn(flatten)(circ))
32
+
33
+ # Pushes block scales into the leaves of the block tree:
34
+ passes.append(blockfn_to_circfn(scale_primitive_blocks_only))
35
+
36
+ return passes
28
37
 
29
38
 
30
39
  @dataclass
@@ -44,9 +53,6 @@ class Configuration(BackendConfiguration):
44
53
  use_single_qubit_composition: bool = False
45
54
  """Composes chains of single qubit gates into a single matmul if possible."""
46
55
 
47
- interaction: Callable | Interaction | str = Interaction.NN
48
- """Digital-analog emulation interaction that is used for `AnalogBlock`s."""
49
-
50
56
  loop_expectation: bool = False
51
57
  """When computing batches of expectation values, only allocate one wavefunction.
52
58
 
@@ -29,7 +29,6 @@ from torch.nn import Module
29
29
 
30
30
  from qadence.backends.utils import (
31
31
  finitediff,
32
- infer_batchsize,
33
32
  pyqify,
34
33
  unpyqify,
35
34
  )
@@ -49,6 +48,7 @@ from qadence.blocks.block_to_tensor import (
49
48
  block_to_diagonal,
50
49
  block_to_tensor,
51
50
  )
51
+ from qadence.blocks.primitive import ProjectorBlock
52
52
  from qadence.operations import (
53
53
  OpName,
54
54
  U,
@@ -58,6 +58,7 @@ from qadence.operations import (
58
58
  three_qubit_gateset,
59
59
  two_qubit_gateset,
60
60
  )
61
+ from qadence.utils import infer_batchsize
61
62
 
62
63
  from .config import Configuration
63
64
 
@@ -127,7 +128,14 @@ def convert_block(
127
128
  # which would be wrong.
128
129
  return [pyq.QuantumCircuit(n_qubits, ops)]
129
130
  elif isinstance(block, tuple(non_unitary_gateset)):
130
- return [getattr(pyq, block.name)(qubit_support[0])]
131
+ if isinstance(block, ProjectorBlock):
132
+ projector = getattr(pyq, block.name)
133
+ if block.name == OpName.N:
134
+ return [projector(target=qubit_support)]
135
+ else:
136
+ return [projector(qubit_support=qubit_support, ket=block.ket, bra=block.bra)]
137
+ else:
138
+ return [getattr(pyq, block.name)(qubit_support[0])]
131
139
  elif isinstance(block, tuple(single_qubit_gateset)):
132
140
  pyq_cls = getattr(pyq, block.name)
133
141
  if isinstance(block, ParametricBlock):
@@ -156,8 +164,8 @@ def convert_block(
156
164
  else:
157
165
  raise NotImplementedError(
158
166
  f"Non supported operation of type {type(block)}. "
159
- "In case you are trying to run an `AnalogBlock`, try converting it "
160
- "with `add_interaction` first."
167
+ "In case you are trying to run an `AnalogBlock`, make sure you "
168
+ "specify the `device_specs` in your `Register` first."
161
169
  )
162
170
 
163
171
 
@@ -244,12 +252,12 @@ class PyQObservable(Module):
244
252
  convert_block(block, n_qubits, config),
245
253
  )
246
254
 
247
- def forward(self, state: Tensor, values: dict[str, Tensor]) -> Tensor:
248
- return pyq.overlap(state, self.operation(state, values))
249
-
250
255
  def run(self, state: Tensor, values: dict[str, Tensor]) -> Tensor:
251
256
  return self.operation(state, values)
252
257
 
258
+ def forward(self, state: Tensor, values: dict[str, Tensor]) -> Tensor:
259
+ return pyq.overlap(state, self.run(state, values))
260
+
253
261
 
254
262
  class PyQHamiltonianEvolution(Module):
255
263
  def __init__(