qadence 1.1.0__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.
- 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 +27 -1
- qadence/backends/braket/backend.py +1 -1
- qadence/backends/pulser/__init__.py +0 -1
- qadence/backends/pulser/backend.py +30 -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 +2 -3
- qadence/backends/pyqtorch/config.py +18 -12
- qadence/backends/pyqtorch/convert_ops.py +12 -4
- qadence/backends/pytorch_wrapper.py +2 -1
- qadence/backends/utils.py +1 -10
- qadence/blocks/abstract.py +5 -1
- qadence/blocks/analog.py +18 -9
- qadence/blocks/block_to_tensor.py +11 -0
- qadence/blocks/primitive.py +81 -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/draw/utils.py +1 -1
- qadence/extensions.py +1 -6
- qadence/finitediff.py +47 -0
- qadence/mitigations/readout.py +92 -25
- qadence/models/qnn.py +88 -23
- qadence/operations.py +55 -70
- qadence/parameters.py +10 -2
- qadence/register.py +91 -43
- qadence/transpile/__init__.py +1 -0
- qadence/transpile/apply_fn.py +40 -0
- qadence/transpile/block.py +15 -7
- qadence/types.py +19 -1
- qadence/utils.py +35 -0
- {qadence-1.1.0.dist-info → qadence-1.2.0.dist-info}/METADATA +2 -2
- {qadence-1.1.0.dist-info → qadence-1.2.0.dist-info}/RECORD +44 -38
- {qadence-1.1.0.dist-info → qadence-1.2.0.dist-info}/WHEEL +1 -1
- qadence/analog/interaction.py +0 -198
- qadence/analog/utils.py +0 -132
- {qadence-1.1.0.dist-info → qadence-1.2.0.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
|
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
|
|
@@ -192,7 +199,7 @@ class Backend(BackendInterface):
|
|
192
199
|
|
193
200
|
return circuit.native.build(**numpy_param_values)
|
194
201
|
|
195
|
-
def
|
202
|
+
def _run(
|
196
203
|
self,
|
197
204
|
circuit: ConvertedCircuit,
|
198
205
|
param_values: dict[str, Tensor] = {},
|
@@ -213,6 +220,10 @@ class Backend(BackendInterface):
|
|
213
220
|
|
214
221
|
for i, param_values_el in enumerate(vals):
|
215
222
|
sequence = self.assign_parameters(circuit, param_values_el)
|
223
|
+
pattern = circuit.original.register.device_specs.pattern
|
224
|
+
if pattern is not None:
|
225
|
+
add_addressing_pattern(sequence, pattern)
|
226
|
+
sequence.measure()
|
216
227
|
sim_result = simulate_sequence(sequence, self.config, state, n_shots=None)
|
217
228
|
wf = (
|
218
229
|
sim_result.get_final_state( # type:ignore [union-attr]
|
@@ -281,6 +292,10 @@ class Backend(BackendInterface):
|
|
281
292
|
samples = []
|
282
293
|
for param_values_el in vals:
|
283
294
|
sequence = self.assign_parameters(circuit, param_values_el)
|
295
|
+
pattern = circuit.original.register.device_specs.pattern
|
296
|
+
if pattern is not None:
|
297
|
+
add_addressing_pattern(sequence, pattern)
|
298
|
+
sequence.measure()
|
284
299
|
sample = simulate_sequence(sequence, self.config, state, n_shots=n_shots)
|
285
300
|
samples.append(sample)
|
286
301
|
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,
|
@@ -32,7 +31,7 @@ from qadence.transpile import (
|
|
32
31
|
transpile,
|
33
32
|
)
|
34
33
|
from qadence.types import BackendName, Endianness
|
35
|
-
from qadence.utils import int_to_basis
|
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
|
@@ -80,7 +79,7 @@ class Backend(BackendInterface):
|
|
80
79
|
(native,) = convert_observable(block, n_qubits=n_qubits, config=self.config)
|
81
80
|
return ConvertedObservable(native=native, abstract=block, original=observable)
|
82
81
|
|
83
|
-
def
|
82
|
+
def _run(
|
84
83
|
self,
|
85
84
|
circuit: ConvertedCircuit,
|
86
85
|
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
|
|
@@ -13,7 +13,7 @@ from torch.nn import Module
|
|
13
13
|
from qadence.backend import Backend as QuantumBackend
|
14
14
|
from qadence.backend import Converted, ConvertedCircuit, ConvertedObservable
|
15
15
|
from qadence.backends.adjoint import AdjointExpectation
|
16
|
-
from qadence.backends.utils import
|
16
|
+
from qadence.backends.utils import is_pyq_shape, param_dict, pyqify, validate_state
|
17
17
|
from qadence.blocks.abstract import AbstractBlock
|
18
18
|
from qadence.blocks.primitive import PrimitiveBlock
|
19
19
|
from qadence.blocks.utils import uuid_to_block, uuid_to_eigen
|
@@ -24,6 +24,7 @@ from qadence.mitigations import Mitigations
|
|
24
24
|
from qadence.ml_tools import promote_to_tensor
|
25
25
|
from qadence.noise import Noise
|
26
26
|
from qadence.types import DiffMode, Endianness
|
27
|
+
from qadence.utils import infer_batchsize
|
27
28
|
|
28
29
|
|
29
30
|
class PSRExpectation(Function):
|
qadence/backends/utils.py
CHANGED
@@ -19,7 +19,7 @@ from torch import (
|
|
19
19
|
)
|
20
20
|
from torch import flatten as torchflatten
|
21
21
|
|
22
|
-
from qadence.utils import Endianness, int_to_basis
|
22
|
+
from qadence.utils import Endianness, int_to_basis, is_qadence_shape
|
23
23
|
|
24
24
|
FINITE_DIFF_EPS = 1e-06
|
25
25
|
# Dict of NumPy dtype -> torch dtype (when the correspondence exists)
|
@@ -126,10 +126,6 @@ def is_pyq_shape(state: Tensor, n_qubits: int) -> bool:
|
|
126
126
|
return state.size()[:-1] == [2] * n_qubits # type: ignore[no-any-return]
|
127
127
|
|
128
128
|
|
129
|
-
def is_qadence_shape(state: Tensor, n_qubits: int) -> bool:
|
130
|
-
return state.shape[1] == 2**n_qubits # type: ignore[no-any-return]
|
131
|
-
|
132
|
-
|
133
129
|
def validate_state(state: Tensor, n_qubits: int) -> None:
|
134
130
|
"""Check if a custom initial state conforms to the qadence or the pyqtorch format."""
|
135
131
|
if state.dtype != complex128:
|
@@ -145,11 +141,6 @@ def validate_state(state: Tensor, n_qubits: int) -> None:
|
|
145
141
|
)
|
146
142
|
|
147
143
|
|
148
|
-
def infer_batchsize(param_values: dict[str, Tensor] = None) -> int:
|
149
|
-
"""Infer the batch_size through the length of the parameter tensors."""
|
150
|
-
return max([len(tensor) for tensor in param_values.values()]) if param_values else 1
|
151
|
-
|
152
|
-
|
153
144
|
# The following functions can be used to compute potentially higher order gradients using pyqtorch's
|
154
145
|
# native 'jacobian' methods.
|
155
146
|
|
qadence/blocks/abstract.py
CHANGED
@@ -5,7 +5,7 @@ from abc import ABC, abstractmethod, abstractproperty
|
|
5
5
|
from dataclasses import dataclass
|
6
6
|
from functools import cached_property
|
7
7
|
from pathlib import Path
|
8
|
-
from typing import ClassVar, Iterable, Tuple, Union, get_args
|
8
|
+
from typing import ClassVar, Iterable, Tuple, TypeVar, Union, get_args
|
9
9
|
|
10
10
|
import sympy
|
11
11
|
import torch
|
@@ -287,6 +287,7 @@ class AbstractBlock(ABC):
|
|
287
287
|
def __hash__(self) -> int:
|
288
288
|
return hash(self._to_json())
|
289
289
|
|
290
|
+
@abstractmethod
|
290
291
|
def dagger(self) -> AbstractBlock:
|
291
292
|
raise NotImplementedError(
|
292
293
|
f"Hermitian adjoint of the Block '{type(self)}' is not implemented yet!"
|
@@ -333,3 +334,6 @@ class AbstractBlock(ABC):
|
|
333
334
|
elif isinstance(self, PrimitiveBlock):
|
334
335
|
return self.name == "I"
|
335
336
|
return False
|
337
|
+
|
338
|
+
|
339
|
+
TAbstractBlock = TypeVar("TAbstractBlock", bound=AbstractBlock)
|