qadence 1.1.1__py3-none-any.whl → 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qadence/__init__.py +1 -0
- qadence/analog/__init__.py +4 -2
- qadence/analog/addressing.py +167 -0
- qadence/analog/constants.py +59 -0
- qadence/analog/device.py +82 -0
- qadence/analog/hamiltonian_terms.py +101 -0
- qadence/analog/parse_analog.py +120 -0
- qadence/backend.py +42 -12
- qadence/backends/__init__.py +1 -2
- qadence/backends/api.py +27 -9
- qadence/backends/braket/backend.py +3 -2
- qadence/backends/horqrux/__init__.py +5 -0
- qadence/backends/horqrux/backend.py +216 -0
- qadence/backends/horqrux/config.py +26 -0
- qadence/backends/horqrux/convert_ops.py +273 -0
- qadence/backends/jax_utils.py +45 -0
- qadence/backends/pulser/__init__.py +0 -1
- qadence/backends/pulser/backend.py +31 -15
- qadence/backends/pulser/config.py +19 -10
- qadence/backends/pulser/devices.py +57 -63
- qadence/backends/pulser/pulses.py +70 -12
- qadence/backends/pyqtorch/backend.py +4 -4
- qadence/backends/pyqtorch/config.py +18 -12
- qadence/backends/pyqtorch/convert_ops.py +15 -7
- qadence/backends/utils.py +5 -9
- qadence/blocks/abstract.py +5 -1
- qadence/blocks/analog.py +18 -9
- qadence/blocks/block_to_tensor.py +11 -0
- qadence/blocks/embedding.py +46 -24
- qadence/blocks/primitive.py +81 -9
- qadence/blocks/utils.py +20 -1
- qadence/circuit.py +3 -9
- qadence/constructors/__init__.py +4 -0
- qadence/constructors/feature_maps.py +84 -60
- qadence/constructors/hamiltonians.py +27 -98
- qadence/constructors/rydberg_feature_maps.py +113 -0
- qadence/divergences.py +12 -0
- qadence/engines/__init__.py +0 -0
- qadence/engines/differentiable_backend.py +152 -0
- qadence/engines/jax/__init__.py +8 -0
- qadence/engines/jax/differentiable_backend.py +73 -0
- qadence/engines/jax/differentiable_expectation.py +94 -0
- qadence/engines/torch/__init__.py +4 -0
- qadence/engines/torch/differentiable_backend.py +85 -0
- qadence/extensions.py +21 -9
- qadence/finitediff.py +47 -0
- qadence/mitigations/readout.py +92 -25
- qadence/ml_tools/models.py +10 -3
- qadence/models/qnn.py +88 -23
- qadence/models/quantum_model.py +13 -2
- qadence/operations.py +55 -70
- qadence/parameters.py +24 -13
- qadence/register.py +91 -43
- qadence/transpile/__init__.py +1 -0
- qadence/transpile/apply_fn.py +40 -0
- qadence/types.py +32 -2
- qadence/utils.py +35 -0
- {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/METADATA +22 -3
- {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/RECORD +62 -44
- {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/WHEEL +1 -1
- qadence/analog/interaction.py +0 -198
- qadence/analog/utils.py +0 -132
- /qadence/{backends/pytorch_wrapper.py → engines/torch/differentiable_expectation.py} +0 -0
- {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -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
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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(
|
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
|
-
|
81
|
+
qadence_register = qadence_register.rescale_coords(scaling=config.spacing)
|
74
82
|
else:
|
75
|
-
if
|
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(
|
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,
|
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
|
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.
|
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:
|
28
|
+
device_type: DeviceType = DeviceType.IDEALIZED
|
31
29
|
"""The type of quantum Device to use in the simulations.
|
32
30
|
|
33
|
-
|
34
|
-
|
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
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
#
|
27
|
-
RealisticDevice
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
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
|
-
(
|
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
|
-
|
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(
|
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(
|
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(
|
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
|
-
|
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
|
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
|
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
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
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`,
|
160
|
-
"
|
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__(
|