cirq-aqt 1.4.0.dev20240418165859__py3-none-any.whl → 1.4.0.dev20240425182738__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.
- cirq_aqt/_version.py +1 -1
- cirq_aqt/aqt_device.py +34 -15
- cirq_aqt/aqt_device_metadata.py +0 -2
- cirq_aqt/aqt_device_metadata_test.py +1 -1
- cirq_aqt/aqt_sampler.py +292 -59
- cirq_aqt/aqt_sampler_test.py +302 -67
- cirq_aqt/aqt_simulator_test.py +0 -18
- cirq_aqt/aqt_target_gateset.py +1 -4
- cirq_aqt/aqt_target_gateset_test.py +2 -2
- {cirq_aqt-1.4.0.dev20240418165859.dist-info → cirq_aqt-1.4.0.dev20240425182738.dist-info}/METADATA +2 -2
- cirq_aqt-1.4.0.dev20240425182738.dist-info/RECORD +21 -0
- cirq_aqt-1.4.0.dev20240418165859.dist-info/RECORD +0 -21
- {cirq_aqt-1.4.0.dev20240418165859.dist-info → cirq_aqt-1.4.0.dev20240425182738.dist-info}/LICENSE +0 -0
- {cirq_aqt-1.4.0.dev20240418165859.dist-info → cirq_aqt-1.4.0.dev20240425182738.dist-info}/WHEEL +0 -0
- {cirq_aqt-1.4.0.dev20240418165859.dist-info → cirq_aqt-1.4.0.dev20240425182738.dist-info}/top_level.txt +0 -0
cirq_aqt/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.4.0.
|
|
1
|
+
__version__ = "1.4.0.dev20240425182738"
|
cirq_aqt/aqt_device.py
CHANGED
|
@@ -25,6 +25,7 @@ The native gate set consists of the local gates: X, Y, and XX entangling gates
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
import json
|
|
28
|
+
from enum import Enum
|
|
28
29
|
from typing import Any, cast, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union
|
|
29
30
|
|
|
30
31
|
import networkx as nx
|
|
@@ -36,11 +37,27 @@ from cirq_aqt import aqt_device_metadata
|
|
|
36
37
|
gate_dict = {'X': cirq.X, 'Y': cirq.Y, 'Z': cirq.Z, 'MS': cirq.XX, 'R': cirq.PhasedXPowGate}
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
class OperationString(Enum):
|
|
41
|
+
"""String representations of operations supported by AQT resources."""
|
|
42
|
+
|
|
43
|
+
MS = "MS"
|
|
44
|
+
"""Cirq: XXPowGate, AQT: RXX gate."""
|
|
45
|
+
|
|
46
|
+
Z = "Z"
|
|
47
|
+
"""Cirq: ZPowGate, AQT: RZ gate."""
|
|
48
|
+
|
|
49
|
+
R = "R"
|
|
50
|
+
"""Cirq: PhasedXPowGate, AQT: R gate."""
|
|
51
|
+
|
|
52
|
+
MEASURE = "Meas"
|
|
53
|
+
"""Measurement gate."""
|
|
54
|
+
|
|
55
|
+
|
|
39
56
|
def get_op_string(op_obj: cirq.Operation) -> str:
|
|
40
57
|
"""Find the string representation for a given gate or operation.
|
|
41
58
|
|
|
42
59
|
Args:
|
|
43
|
-
op_obj: Gate or operation object. Gate must be one of: XXPowGate,
|
|
60
|
+
op_obj: Gate or operation object. Gate must be one of: XXPowGate,
|
|
44
61
|
ZPowGate, PhasedXPowGate, or MeasurementGate.
|
|
45
62
|
|
|
46
63
|
Returns:
|
|
@@ -50,20 +67,16 @@ def get_op_string(op_obj: cirq.Operation) -> str:
|
|
|
50
67
|
ValueError: If the gate is not one of the supported gates.
|
|
51
68
|
"""
|
|
52
69
|
if isinstance(op_obj.gate, cirq.XXPowGate):
|
|
53
|
-
op_str =
|
|
54
|
-
elif isinstance(op_obj.gate, cirq.XPowGate):
|
|
55
|
-
op_str = 'X'
|
|
56
|
-
elif isinstance(op_obj.gate, cirq.YPowGate):
|
|
57
|
-
op_str = 'Y'
|
|
70
|
+
op_str = OperationString.MS.value
|
|
58
71
|
elif isinstance(op_obj.gate, cirq.ZPowGate):
|
|
59
|
-
op_str =
|
|
72
|
+
op_str = OperationString.Z.value
|
|
60
73
|
elif isinstance(op_obj.gate, cirq.PhasedXPowGate):
|
|
61
|
-
op_str =
|
|
74
|
+
op_str = OperationString.R.value
|
|
62
75
|
elif isinstance(op_obj.gate, cirq.MeasurementGate):
|
|
63
|
-
op_str =
|
|
76
|
+
op_str = OperationString.MEASURE.value
|
|
64
77
|
else:
|
|
65
78
|
raise ValueError(f'Got unknown gate on operation: {op_obj}.')
|
|
66
|
-
return op_str
|
|
79
|
+
return str(op_str)
|
|
67
80
|
|
|
68
81
|
|
|
69
82
|
class AQTNoiseModel(cirq.NoiseModel):
|
|
@@ -97,6 +110,7 @@ class AQTNoiseModel(cirq.NoiseModel):
|
|
|
97
110
|
for qubit in op.qubits:
|
|
98
111
|
noise_list.append(noise_op.on(qubit))
|
|
99
112
|
noise_list += self.get_crosstalk_operation(op, system_qubits)
|
|
113
|
+
|
|
100
114
|
return list(moment) + noise_list
|
|
101
115
|
|
|
102
116
|
def get_crosstalk_operation(
|
|
@@ -122,16 +136,18 @@ class AQTNoiseModel(cirq.NoiseModel):
|
|
|
122
136
|
for neigh_idx in neighbors:
|
|
123
137
|
if neigh_idx >= 0 and neigh_idx < num_qubits:
|
|
124
138
|
xtlk_arr[neigh_idx] = self.noise_op_dict['crosstalk']
|
|
139
|
+
|
|
125
140
|
for idx in idx_list:
|
|
126
141
|
xtlk_arr[idx] = 0
|
|
127
142
|
xtlk_op_list = []
|
|
128
143
|
op_str = get_op_string(operation)
|
|
129
144
|
gate = cast(cirq.EigenGate, gate_dict[op_str])
|
|
145
|
+
|
|
130
146
|
if len(operation.qubits) == 1:
|
|
131
147
|
for idx in xtlk_arr.nonzero()[0]:
|
|
132
148
|
exponent = operation.gate.exponent # type:ignore
|
|
133
149
|
exponent = exponent * xtlk_arr[idx]
|
|
134
|
-
xtlk_op = gate.on(system_qubits[idx]) ** exponent
|
|
150
|
+
xtlk_op = operation.gate.on(system_qubits[idx]) ** exponent # type:ignore
|
|
135
151
|
xtlk_op_list.append(xtlk_op)
|
|
136
152
|
elif len(operation.qubits) == 2:
|
|
137
153
|
for op_qubit in operation.qubits:
|
|
@@ -216,10 +232,14 @@ class AQTSimulator:
|
|
|
216
232
|
noise_model = cirq.NO_NOISE
|
|
217
233
|
else:
|
|
218
234
|
noise_model = AQTNoiseModel()
|
|
235
|
+
|
|
219
236
|
if self.circuit == cirq.Circuit():
|
|
220
237
|
raise RuntimeError('Simulate called without a valid circuit.')
|
|
238
|
+
|
|
221
239
|
sim = cirq.DensityMatrixSimulator(noise=noise_model)
|
|
240
|
+
|
|
222
241
|
result = sim.run(self.circuit, repetitions=repetitions)
|
|
242
|
+
|
|
223
243
|
return result
|
|
224
244
|
|
|
225
245
|
|
|
@@ -342,10 +362,9 @@ def get_aqt_device(num_qubits: int) -> Tuple[AQTDevice, List[cirq.LineQubit]]:
|
|
|
342
362
|
def get_default_noise_dict() -> Dict[str, Any]:
|
|
343
363
|
"""Returns the current noise parameters"""
|
|
344
364
|
default_noise_dict = {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
'MS': cirq.depolarize(1e-2),
|
|
365
|
+
OperationString.R.value: cirq.depolarize(1e-3),
|
|
366
|
+
OperationString.Z.value: cirq.depolarize(0),
|
|
367
|
+
OperationString.MS.value: cirq.depolarize(1e-2),
|
|
349
368
|
'crosstalk': 0.03,
|
|
350
369
|
}
|
|
351
370
|
return default_noise_dict
|
cirq_aqt/aqt_device_metadata.py
CHANGED
|
@@ -53,8 +53,6 @@ class AQTDeviceMetadata(cirq.DeviceMetadata):
|
|
|
53
53
|
self._gate_durations = {
|
|
54
54
|
cirq.GateFamily(cirq.MeasurementGate): self._measurement_duration,
|
|
55
55
|
cirq.GateFamily(cirq.XXPowGate): self._twoq_gates_duration,
|
|
56
|
-
cirq.GateFamily(cirq.XPowGate): self._oneq_gates_duration,
|
|
57
|
-
cirq.GateFamily(cirq.YPowGate): self._oneq_gates_duration,
|
|
58
56
|
cirq.GateFamily(cirq.ZPowGate): self._oneq_gates_duration,
|
|
59
57
|
cirq.GateFamily(cirq.PhasedXPowGate): self._oneq_gates_duration,
|
|
60
58
|
}
|
|
@@ -45,7 +45,7 @@ def test_aqtdevice_metadata(metadata, qubits):
|
|
|
45
45
|
assert len(edges) == 10
|
|
46
46
|
assert all(q0 != q1 for q0, q1 in edges)
|
|
47
47
|
assert AQTTargetGateset() == metadata.gateset
|
|
48
|
-
assert len(metadata.gate_durations) ==
|
|
48
|
+
assert len(metadata.gate_durations) == 4
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def test_aqtdevice_duration_of(metadata, qubits):
|
cirq_aqt/aqt_sampler.py
CHANGED
|
@@ -25,13 +25,75 @@ API keys for classical simulators and quantum devices can be obtained at:
|
|
|
25
25
|
import json
|
|
26
26
|
import time
|
|
27
27
|
import uuid
|
|
28
|
-
from typing import cast, Dict, List, Sequence, Tuple, Union
|
|
28
|
+
from typing import Callable, cast, Dict, List, Sequence, Tuple, Union, Literal, TypedDict
|
|
29
|
+
from urllib.parse import urljoin
|
|
29
30
|
|
|
30
31
|
import numpy as np
|
|
31
|
-
from requests import
|
|
32
|
+
from requests import post, get
|
|
32
33
|
|
|
33
34
|
import cirq
|
|
34
|
-
from cirq_aqt.aqt_device import AQTSimulator, get_op_string
|
|
35
|
+
from cirq_aqt.aqt_device import AQTSimulator, get_op_string, OperationString
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
_DEFAULT_HOST = "https://arnica.aqt.eu/api/v1/"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SingleQubitGate(TypedDict):
|
|
42
|
+
"""Abstract single qubit rotation."""
|
|
43
|
+
|
|
44
|
+
qubit: int
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class GateRZ(SingleQubitGate):
|
|
48
|
+
"""A single-qubit rotation rotation around the Bloch sphere's z-axis."""
|
|
49
|
+
|
|
50
|
+
operation: Literal["RZ"]
|
|
51
|
+
phi: float
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class GateR(SingleQubitGate):
|
|
55
|
+
"""A single-qubit rotation around an arbitrary axis on the Bloch sphere's equatorial plane."""
|
|
56
|
+
|
|
57
|
+
operation: Literal["R"]
|
|
58
|
+
phi: float
|
|
59
|
+
theta: float
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class GateRXX(TypedDict):
|
|
63
|
+
"""A two-qubit entangling gate of Mølmer-Sørenson-type."""
|
|
64
|
+
|
|
65
|
+
operation: Literal["RXX"]
|
|
66
|
+
qubits: list[int]
|
|
67
|
+
theta: float
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Measure(TypedDict):
|
|
71
|
+
"""Measurement operation.
|
|
72
|
+
|
|
73
|
+
The MEASURE operation instructs the resource
|
|
74
|
+
to perform a projective measurement of all qubits.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
operation: Literal["MEASURE"]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
Gate = GateRZ | GateR | GateRXX
|
|
81
|
+
Operation = Gate | Measure
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Resource(TypedDict):
|
|
85
|
+
"""A quantum computing device."""
|
|
86
|
+
|
|
87
|
+
id: str
|
|
88
|
+
name: str
|
|
89
|
+
type: Literal["device", "simulator"]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Workspace(TypedDict):
|
|
93
|
+
"""A user workspace."""
|
|
94
|
+
|
|
95
|
+
id: str
|
|
96
|
+
resources: list[Resource]
|
|
35
97
|
|
|
36
98
|
|
|
37
99
|
class AQTSampler(cirq.Sampler):
|
|
@@ -40,16 +102,127 @@ class AQTSampler(cirq.Sampler):
|
|
|
40
102
|
runs a single circuit or an entire sweep remotely
|
|
41
103
|
"""
|
|
42
104
|
|
|
43
|
-
def __init__(
|
|
105
|
+
def __init__(
|
|
106
|
+
self, workspace: str, resource: str, access_token: str, remote_host: str = _DEFAULT_HOST
|
|
107
|
+
):
|
|
44
108
|
"""Inits AQTSampler.
|
|
45
109
|
|
|
46
110
|
Args:
|
|
47
|
-
|
|
48
|
-
|
|
111
|
+
workspace: the ID of the workspace you have access to.
|
|
112
|
+
resource: the ID of the resource to run the circuit on.
|
|
113
|
+
access_token: Access token for the AQT API.
|
|
114
|
+
remote_host: Address of the AQT API.
|
|
49
115
|
"""
|
|
116
|
+
self.workspace = workspace
|
|
117
|
+
self.resource = resource
|
|
50
118
|
self.remote_host = remote_host
|
|
51
119
|
self.access_token = access_token
|
|
52
120
|
|
|
121
|
+
@staticmethod
|
|
122
|
+
def fetch_resources(access_token: str, remote_host: str = _DEFAULT_HOST) -> list[Workspace]:
|
|
123
|
+
"""Lists the workspaces and resources that are accessible with access_token.
|
|
124
|
+
|
|
125
|
+
Returns a list containing the workspaces and resources that the passed
|
|
126
|
+
access_token gives access to. The workspace and resource IDs in this list can be
|
|
127
|
+
used to submit jobs using the run and run_sweep methods.
|
|
128
|
+
|
|
129
|
+
The printed table contains four columns:
|
|
130
|
+
- WORKSPACE ID: the ID of the workspace. Use this value to submit circuits.
|
|
131
|
+
- RESOURCE NAME: the human-readable name of the resource.
|
|
132
|
+
- RESOURCE ID: the ID of the resource. Use this value to submit circuits.
|
|
133
|
+
- D/S: whether the resource is a (D)evice or (S)imulator.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
access_token: Access token for the AQT API.
|
|
137
|
+
remote_host: Address of the AQT API. Defaults to "https://arnica.aqt.eu/api/v1/".
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
RuntimeError: If there was an unexpected response from the server.
|
|
141
|
+
"""
|
|
142
|
+
headers = {"Authorization": f"Bearer {access_token}", "SDK": "cirq"}
|
|
143
|
+
url = urljoin(remote_host if remote_host[-1] == "/" else remote_host + "/", "workspaces")
|
|
144
|
+
|
|
145
|
+
response = get(url, headers=headers)
|
|
146
|
+
if response.status_code != 200:
|
|
147
|
+
raise RuntimeError('Got unexpected return data from server: \n' + str(response.json()))
|
|
148
|
+
|
|
149
|
+
workspaces = [
|
|
150
|
+
Workspace(
|
|
151
|
+
id=w['id'],
|
|
152
|
+
resources=[
|
|
153
|
+
Resource(id=r['id'], name=r['name'], type=r['type']) for r in w['resources']
|
|
154
|
+
],
|
|
155
|
+
)
|
|
156
|
+
for w in response.json()
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
return workspaces
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def print_resources(
|
|
163
|
+
access_token: str, emit: Callable = print, remote_host: str = _DEFAULT_HOST
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Displays the workspaces and resources that are accessible with access_token.
|
|
166
|
+
|
|
167
|
+
Prints a table using the function passed as 'emit' containing the workspaces and
|
|
168
|
+
resources that the passed access_token gives access to. The IDs in this table
|
|
169
|
+
can be used to submit jobs using the run and run_sweep methods.
|
|
170
|
+
|
|
171
|
+
The printed table contains four columns:
|
|
172
|
+
- WORKSPACE ID: the ID of the workspace. Use this value to submit circuits.
|
|
173
|
+
- RESOURCE NAME: the human-readable name of the resource.
|
|
174
|
+
- RESOURCE ID: the ID of the resource. Use this value to submit circuits.
|
|
175
|
+
- D/S: whether the resource is a (D)evice or (S)imulator.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
access_token: Access token for the AQT API.
|
|
179
|
+
emit (optional): A Callable which will be called once with a single string argument,
|
|
180
|
+
containing the table. Defaults to print from the standard library.
|
|
181
|
+
remote_host (optional): Address of the AQT API. Defaults to
|
|
182
|
+
"https://arnica.aqt.eu/api/v1/".
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
RuntimeError: If there was an unexpected response from the server.
|
|
186
|
+
"""
|
|
187
|
+
table_lines = []
|
|
188
|
+
workspaces = AQTSampler.fetch_resources(access_token, remote_host)
|
|
189
|
+
|
|
190
|
+
if len(workspaces) == 0:
|
|
191
|
+
return emit("No workspaces are accessible with this access token.")
|
|
192
|
+
if any(len(w['resources']) == 0 for w in workspaces):
|
|
193
|
+
return emit("No workspaces accessible with this access token contain resources.")
|
|
194
|
+
|
|
195
|
+
col_widths = [
|
|
196
|
+
max([len(w['id']) for w in workspaces]),
|
|
197
|
+
max([len(d['name']) for w in workspaces for d in w['resources']]),
|
|
198
|
+
max([len(d['id']) for w in workspaces for d in w['resources']]),
|
|
199
|
+
3,
|
|
200
|
+
]
|
|
201
|
+
SEPARATOR = "+-" + "-+-".join(col_width * "-" for col_width in col_widths) + "-+"
|
|
202
|
+
|
|
203
|
+
table_lines.append(SEPARATOR)
|
|
204
|
+
table_lines.append(
|
|
205
|
+
f"| {'WORKSPACE ID'.ljust(col_widths[0])} |"
|
|
206
|
+
f" {'RESOURCE NAME'.ljust(col_widths[1])} |"
|
|
207
|
+
f" {'RESOURCE ID'.ljust(col_widths[2])} |"
|
|
208
|
+
f" {'D/S'.ljust(col_widths[3])} |"
|
|
209
|
+
)
|
|
210
|
+
table_lines.append(SEPARATOR)
|
|
211
|
+
|
|
212
|
+
for workspace in workspaces:
|
|
213
|
+
next_workspace = workspace['id']
|
|
214
|
+
for resource in workspace["resources"]:
|
|
215
|
+
table_lines.append(
|
|
216
|
+
f"| {next_workspace.ljust(col_widths[0])} |"
|
|
217
|
+
f" {resource['name'].ljust(col_widths[1])} |"
|
|
218
|
+
f" {resource['id'].ljust(col_widths[2])} |"
|
|
219
|
+
f" {resource['type'][0].upper().ljust(col_widths[3])} |"
|
|
220
|
+
)
|
|
221
|
+
next_workspace = ""
|
|
222
|
+
table_lines.append(SEPARATOR)
|
|
223
|
+
|
|
224
|
+
emit("\n".join(table_lines))
|
|
225
|
+
|
|
53
226
|
def _generate_json(
|
|
54
227
|
self, circuit: cirq.AbstractCircuit, param_resolver: cirq.ParamResolverOrSimilarType
|
|
55
228
|
) -> str:
|
|
@@ -62,7 +235,7 @@ class AQTSampler(cirq.Sampler):
|
|
|
62
235
|
which is a list of sequential quantum operations,
|
|
63
236
|
each operation defined by:
|
|
64
237
|
|
|
65
|
-
op_string: str that specifies the operation type: "
|
|
238
|
+
op_string: str that specifies the operation type: "Z","MS","R","Meas"
|
|
66
239
|
gate_exponent: float that specifies the gate_exponent of the operation
|
|
67
240
|
qubits: list of qubits where the operation acts on.
|
|
68
241
|
|
|
@@ -100,27 +273,72 @@ class AQTSampler(cirq.Sampler):
|
|
|
100
273
|
json_str = json.dumps(seq_list)
|
|
101
274
|
return json_str
|
|
102
275
|
|
|
276
|
+
def _parse_legacy_circuit_json(self, json_str: str) -> list[Operation]:
|
|
277
|
+
"""Converts a legacy JSON circuit representation.
|
|
278
|
+
|
|
279
|
+
Converts a JSON created for the legacy API into one that will work
|
|
280
|
+
with the Arnica v1 API.
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
ValueError:
|
|
284
|
+
* if there is not exactly one measurement operation at the end
|
|
285
|
+
of the circuit.
|
|
286
|
+
|
|
287
|
+
* if an operation is found in json_str that is not in
|
|
288
|
+
OperationString.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
json_str: A JSON-formatted string that could be used as the
|
|
292
|
+
data parameter in the body of a request to the old AQT API.
|
|
293
|
+
"""
|
|
294
|
+
circuit = []
|
|
295
|
+
number_of_measurements = 0
|
|
296
|
+
instruction: Operation
|
|
297
|
+
|
|
298
|
+
for legacy_op in json.loads(json_str):
|
|
299
|
+
if number_of_measurements > 0:
|
|
300
|
+
raise ValueError("Need exactly one `MEASURE` operation at the end of the circuit.")
|
|
301
|
+
|
|
302
|
+
if legacy_op[0] == OperationString.Z.value:
|
|
303
|
+
instruction = GateRZ(operation="RZ", qubit=legacy_op[2][0], phi=legacy_op[1])
|
|
304
|
+
|
|
305
|
+
elif legacy_op[0] == OperationString.R.value:
|
|
306
|
+
instruction = GateR(
|
|
307
|
+
operation="R", qubit=legacy_op[3][0], theta=legacy_op[1], phi=legacy_op[2]
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
elif legacy_op[0] == OperationString.MS.value:
|
|
311
|
+
instruction = GateRXX(operation="RXX", qubits=legacy_op[2], theta=legacy_op[1])
|
|
312
|
+
|
|
313
|
+
elif legacy_op[0] == OperationString.MEASURE.value:
|
|
314
|
+
instruction = Measure(operation="MEASURE")
|
|
315
|
+
number_of_measurements += 1
|
|
316
|
+
|
|
317
|
+
else:
|
|
318
|
+
raise ValueError(f'Got unknown gate on operation: {legacy_op}.')
|
|
319
|
+
|
|
320
|
+
circuit.append(instruction)
|
|
321
|
+
|
|
322
|
+
if circuit[-1]["operation"] != "MEASURE":
|
|
323
|
+
circuit.append({"operation": "MEASURE"})
|
|
324
|
+
|
|
325
|
+
return circuit
|
|
326
|
+
|
|
103
327
|
def _send_json(
|
|
104
|
-
self,
|
|
105
|
-
*,
|
|
106
|
-
json_str: str,
|
|
107
|
-
id_str: Union[str, uuid.UUID],
|
|
108
|
-
repetitions: int = 1,
|
|
109
|
-
num_qubits: int = 1,
|
|
328
|
+
self, *, json_str: str, id_str: str, repetitions: int = 1, num_qubits: int = 1
|
|
110
329
|
) -> np.ndarray:
|
|
111
|
-
"""Sends the json string to the remote AQT device
|
|
330
|
+
"""Sends the json string to the remote AQT device.
|
|
112
331
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
The experimental data is returned via the key 'data'
|
|
332
|
+
Submits a pre-prepared JSON string representing a circuit to the AQT
|
|
333
|
+
API, then polls for the result, which is parsed and returned when
|
|
334
|
+
available.
|
|
335
|
+
|
|
336
|
+
Please consider that due to the potential for long wait-times, there is
|
|
337
|
+
no timeout in the result polling.
|
|
120
338
|
|
|
121
339
|
Args:
|
|
122
340
|
json_str: Json representation of the circuit.
|
|
123
|
-
id_str:
|
|
341
|
+
id_str: A label to help identify a circuit.
|
|
124
342
|
repetitions: Number of repetitions.
|
|
125
343
|
num_qubits: Number of qubits present in the device.
|
|
126
344
|
|
|
@@ -130,49 +348,60 @@ class AQTSampler(cirq.Sampler):
|
|
|
130
348
|
Raises:
|
|
131
349
|
RuntimeError: If there was an unexpected response from the server.
|
|
132
350
|
"""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
351
|
+
headers = {"Authorization": f"Bearer {self.access_token}", "SDK": "cirq"}
|
|
352
|
+
quantum_circuit = self._parse_legacy_circuit_json(json_str)
|
|
353
|
+
submission_data = {
|
|
354
|
+
"job_type": "quantum_circuit",
|
|
355
|
+
"label": id_str,
|
|
356
|
+
"payload": {
|
|
357
|
+
"circuits": [
|
|
358
|
+
{
|
|
359
|
+
"repetitions": repetitions,
|
|
360
|
+
"quantum_circuit": quantum_circuit,
|
|
361
|
+
"number_of_qubits": num_qubits,
|
|
362
|
+
}
|
|
363
|
+
]
|
|
141
364
|
},
|
|
142
|
-
|
|
143
|
-
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
submission_url = urljoin(self.remote_host, f"submit/{self.workspace}/{self.resource}")
|
|
368
|
+
|
|
369
|
+
response = post(submission_url, json=submission_data, headers=headers)
|
|
144
370
|
response = response.json()
|
|
145
371
|
data = cast(Dict, response)
|
|
146
|
-
|
|
372
|
+
|
|
373
|
+
if 'response' not in data.keys() or 'status' not in data['response'].keys():
|
|
147
374
|
raise RuntimeError('Got unexpected return data from server: \n' + str(data))
|
|
148
|
-
if data['status'] == 'error':
|
|
375
|
+
if data['response']['status'] == 'error':
|
|
149
376
|
raise RuntimeError('AQT server reported error: \n' + str(data))
|
|
150
377
|
|
|
151
|
-
if '
|
|
378
|
+
if 'job' not in data.keys() or 'job_id' not in data['job'].keys():
|
|
152
379
|
raise RuntimeError('Got unexpected return data from AQT server: \n' + str(data))
|
|
153
|
-
|
|
380
|
+
job_id = data['job']['job_id']
|
|
154
381
|
|
|
382
|
+
result_url = urljoin(self.remote_host, f"result/{job_id}")
|
|
155
383
|
while True:
|
|
156
|
-
response =
|
|
157
|
-
self.remote_host,
|
|
158
|
-
data={'id': id_str, 'access_token': self.access_token},
|
|
159
|
-
headers=header,
|
|
160
|
-
)
|
|
384
|
+
response = get(result_url, headers=headers)
|
|
161
385
|
response = response.json()
|
|
162
386
|
data = cast(Dict, response)
|
|
163
|
-
|
|
387
|
+
|
|
388
|
+
if 'response' not in data.keys() or 'status' not in data['response'].keys():
|
|
164
389
|
raise RuntimeError('Got unexpected return data from AQT server: \n' + str(data))
|
|
165
|
-
if data['status'] == 'finished':
|
|
390
|
+
if data['response']['status'] == 'finished':
|
|
166
391
|
break
|
|
167
|
-
elif data['status'] == 'error':
|
|
392
|
+
elif data['response']['status'] == 'error':
|
|
168
393
|
raise RuntimeError('Got unexpected return data from AQT server: \n' + str(data))
|
|
169
394
|
time.sleep(1.0)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
395
|
+
|
|
396
|
+
if 'result' not in data['response'].keys():
|
|
397
|
+
raise RuntimeError('Got unexpected return data from AQT server: \n' + str(data))
|
|
398
|
+
|
|
399
|
+
measurement_int = data['response']['result']['0']
|
|
400
|
+
measurements = np.zeros((repetitions, num_qubits), dtype=int)
|
|
401
|
+
for i, repetition in enumerate(measurement_int):
|
|
174
402
|
for j in range(num_qubits):
|
|
175
|
-
measurements[i, j] =
|
|
403
|
+
measurements[i, j] = repetition[j]
|
|
404
|
+
|
|
176
405
|
return measurements
|
|
177
406
|
|
|
178
407
|
def run_sweep(
|
|
@@ -198,7 +427,7 @@ class AQTSampler(cirq.Sampler):
|
|
|
198
427
|
meas_name = 'm'
|
|
199
428
|
trial_results: List[cirq.Result] = []
|
|
200
429
|
for param_resolver in cirq.to_resolvers(params):
|
|
201
|
-
id_str = uuid.uuid1()
|
|
430
|
+
id_str = str(uuid.uuid1())
|
|
202
431
|
num_qubits = len(program.all_qubits())
|
|
203
432
|
json_str = self._generate_json(circuit=program, param_resolver=param_resolver)
|
|
204
433
|
results = self._send_json(
|
|
@@ -223,10 +452,19 @@ class AQTSamplerLocalSimulator(AQTSampler):
|
|
|
223
452
|
sampler.simulate_ideal=True
|
|
224
453
|
"""
|
|
225
454
|
|
|
226
|
-
def __init__(
|
|
455
|
+
def __init__(
|
|
456
|
+
self,
|
|
457
|
+
workspace: str = "",
|
|
458
|
+
resource: str = "",
|
|
459
|
+
access_token: str = "",
|
|
460
|
+
remote_host: str = "",
|
|
461
|
+
simulate_ideal: bool = False,
|
|
462
|
+
):
|
|
227
463
|
"""Args:
|
|
228
|
-
|
|
464
|
+
workspace: Workspace is not used by the local simulator.
|
|
465
|
+
resource: Resource is not used by the local simulator.
|
|
229
466
|
access_token: Access token is not used by the local simulator.
|
|
467
|
+
remote_host: Remote host is not used by the local simulator.
|
|
230
468
|
simulate_ideal: Boolean that determines whether a noisy or
|
|
231
469
|
an ideal simulation is performed.
|
|
232
470
|
"""
|
|
@@ -235,18 +473,13 @@ class AQTSamplerLocalSimulator(AQTSampler):
|
|
|
235
473
|
self.simulate_ideal = simulate_ideal
|
|
236
474
|
|
|
237
475
|
def _send_json(
|
|
238
|
-
self,
|
|
239
|
-
*,
|
|
240
|
-
json_str: str,
|
|
241
|
-
id_str: Union[str, uuid.UUID],
|
|
242
|
-
repetitions: int = 1,
|
|
243
|
-
num_qubits: int = 1,
|
|
476
|
+
self, *, json_str: str, id_str: str, repetitions: int = 1, num_qubits: int = 1
|
|
244
477
|
) -> np.ndarray:
|
|
245
478
|
"""Replaces the remote host with a local simulator
|
|
246
479
|
|
|
247
480
|
Args:
|
|
248
481
|
json_str: Json representation of the circuit.
|
|
249
|
-
id_str:
|
|
482
|
+
id_str: A label to help identify a datapoint.
|
|
250
483
|
repetitions: Number of repetitions.
|
|
251
484
|
num_qubits: Number of qubits present in the device.
|
|
252
485
|
|
cirq_aqt/aqt_sampler_test.py
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
from unittest import mock
|
|
16
|
+
import json
|
|
16
17
|
import numpy as np
|
|
17
18
|
import pytest
|
|
18
19
|
import sympy
|
|
@@ -22,15 +23,11 @@ from cirq_aqt import AQTSampler, AQTSamplerLocalSimulator
|
|
|
22
23
|
from cirq_aqt.aqt_device import get_aqt_device, get_op_string
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
class
|
|
26
|
+
class GetResultReturn:
|
|
26
27
|
"""A put mock class for testing the REST interface"""
|
|
27
28
|
|
|
28
29
|
def __init__(self):
|
|
29
|
-
self.test_dict = {
|
|
30
|
-
'status': 'queued',
|
|
31
|
-
'id': '2131da',
|
|
32
|
-
'samples': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
33
|
-
}
|
|
30
|
+
self.test_dict = {'job': {'job_id': '2131da'}, 'response': {'status': 'queued'}}
|
|
34
31
|
self.counter = 0
|
|
35
32
|
|
|
36
33
|
def json(self):
|
|
@@ -38,75 +35,104 @@ class EngineReturn:
|
|
|
38
35
|
return self.test_dict
|
|
39
36
|
|
|
40
37
|
def update(self, *args, **kwargs):
|
|
41
|
-
if self.counter >= 2:
|
|
42
|
-
self.test_dict['status'] = 'finished'
|
|
43
38
|
return self
|
|
44
39
|
|
|
45
40
|
|
|
46
|
-
class
|
|
41
|
+
class GetResultError(GetResultReturn):
|
|
47
42
|
"""A put mock class for testing error responses"""
|
|
48
43
|
|
|
49
44
|
def __init__(self):
|
|
50
|
-
self.test_dict = {'
|
|
45
|
+
self.test_dict = {'response': {}}
|
|
46
|
+
self.test_dict['response']['status'] = 'error'
|
|
47
|
+
self.test_dict['response']['message'] = "Error message"
|
|
51
48
|
self.counter = 0
|
|
52
49
|
|
|
53
50
|
|
|
54
|
-
class
|
|
55
|
-
"""A put mock class for testing error responses
|
|
56
|
-
This will not return an id at the first call"""
|
|
57
|
-
|
|
58
|
-
def __init__(self):
|
|
59
|
-
self.test_dict = {'status': 'queued'}
|
|
60
|
-
self.counter = 0
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class EngineNoStatus(EngineReturn):
|
|
51
|
+
class GetResultNoStatus(GetResultReturn):
|
|
64
52
|
"""A put mock class for testing error responses
|
|
65
53
|
This will not return a status in the second call"""
|
|
66
54
|
|
|
67
55
|
def update(self, *args, **kwargs):
|
|
68
|
-
del self.test_dict['status']
|
|
56
|
+
del self.test_dict['response']['status']
|
|
69
57
|
return self
|
|
70
58
|
|
|
71
59
|
|
|
72
|
-
class
|
|
60
|
+
class GetResultErrorSecond(GetResultReturn):
|
|
73
61
|
"""A put mock class for testing error responses
|
|
74
|
-
This will
|
|
62
|
+
This will return an error on the second put call"""
|
|
75
63
|
|
|
76
64
|
def update(self, *args, **kwargs):
|
|
77
65
|
if self.counter >= 1:
|
|
78
|
-
|
|
66
|
+
self.test_dict['response']['status'] = 'error'
|
|
79
67
|
return self
|
|
80
68
|
|
|
81
69
|
|
|
82
|
-
class
|
|
70
|
+
class SubmitGoodResponse:
|
|
71
|
+
def json(self):
|
|
72
|
+
return {"job": {"job_id": "test_job"}, "response": {"status": "queued"}}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SubmitResultNoID:
|
|
83
76
|
"""A put mock class for testing error responses
|
|
84
|
-
This will return an
|
|
77
|
+
This will not return an id at the first call"""
|
|
78
|
+
|
|
79
|
+
def json(self):
|
|
80
|
+
return {"job": {}, "response": {"status": "queued"}}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class SubmitResultNoStatus:
|
|
84
|
+
"""A put mock class for testing error responses
|
|
85
|
+
This will not return an id at the first call"""
|
|
86
|
+
|
|
87
|
+
def json(self):
|
|
88
|
+
return {"job": {"job_id": "test_job"}, "response": {}}
|
|
85
89
|
|
|
86
|
-
def update(self, *args, **kwargs):
|
|
87
|
-
if self.counter >= 1:
|
|
88
|
-
self.test_dict['status'] = 'error'
|
|
89
|
-
return self
|
|
90
90
|
|
|
91
|
+
class SubmitResultWithError:
|
|
92
|
+
"""A put mock class for testing error responses
|
|
93
|
+
This will not return an id at the first call"""
|
|
94
|
+
|
|
95
|
+
def json(self):
|
|
96
|
+
return {"job": {"job_id": "test_job"}, "response": {"status": "error"}}
|
|
91
97
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
]:
|
|
100
|
-
with mock.patch(
|
|
101
|
-
'cirq_aqt.aqt_sampler.put', return_value=e_return, side_effect=e_return.update
|
|
102
|
-
) as _mock_method:
|
|
98
|
+
|
|
99
|
+
def test_aqt_sampler_submit_job_error_handling():
|
|
100
|
+
for e_return in [SubmitResultNoID(), SubmitResultNoStatus(), SubmitResultWithError()]:
|
|
101
|
+
with (
|
|
102
|
+
mock.patch('cirq_aqt.aqt_sampler.post', return_value=e_return),
|
|
103
|
+
mock.patch('cirq_aqt.aqt_sampler.get', return_value=GetResultReturn()),
|
|
104
|
+
):
|
|
103
105
|
theta = sympy.Symbol('theta')
|
|
104
106
|
num_points = 1
|
|
105
107
|
max_angle = np.pi
|
|
106
108
|
repetitions = 10
|
|
107
|
-
sampler = AQTSampler(
|
|
109
|
+
sampler = AQTSampler(access_token='testkey', workspace="default", resource="test")
|
|
108
110
|
_, qubits = get_aqt_device(1)
|
|
109
|
-
circuit = cirq.Circuit(
|
|
111
|
+
circuit = cirq.Circuit(
|
|
112
|
+
cirq.PhasedXPowGate(exponent=theta, phase_exponent=0.0).on(qubits[0])
|
|
113
|
+
)
|
|
114
|
+
sweep = cirq.Linspace(key='theta', start=0.1, stop=max_angle / np.pi, length=num_points)
|
|
115
|
+
with pytest.raises(RuntimeError):
|
|
116
|
+
_results = sampler.run_sweep(circuit, params=sweep, repetitions=repetitions)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_aqt_sampler_get_result_error_handling():
|
|
120
|
+
for e_return in [GetResultError(), GetResultErrorSecond(), GetResultNoStatus()]:
|
|
121
|
+
with (
|
|
122
|
+
mock.patch('cirq_aqt.aqt_sampler.post', return_value=SubmitGoodResponse()),
|
|
123
|
+
mock.patch(
|
|
124
|
+
'cirq_aqt.aqt_sampler.get', return_value=e_return, side_effect=e_return.update
|
|
125
|
+
),
|
|
126
|
+
):
|
|
127
|
+
theta = sympy.Symbol('theta')
|
|
128
|
+
num_points = 1
|
|
129
|
+
max_angle = np.pi
|
|
130
|
+
repetitions = 10
|
|
131
|
+
sampler = AQTSampler(access_token='testkey', workspace="default", resource="test")
|
|
132
|
+
_, qubits = get_aqt_device(1)
|
|
133
|
+
circuit = cirq.Circuit(
|
|
134
|
+
cirq.PhasedXPowGate(exponent=theta, phase_exponent=0.0).on(qubits[0])
|
|
135
|
+
)
|
|
110
136
|
sweep = cirq.Linspace(key='theta', start=0.1, stop=max_angle / np.pi, length=num_points)
|
|
111
137
|
with pytest.raises(RuntimeError):
|
|
112
138
|
_results = sampler.run_sweep(circuit, params=sweep, repetitions=repetitions)
|
|
@@ -127,28 +153,48 @@ def test_aqt_sampler_empty_circuit():
|
|
|
127
153
|
|
|
128
154
|
|
|
129
155
|
def test_aqt_sampler():
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
156
|
+
class ResultReturn:
|
|
157
|
+
def __init__(self):
|
|
158
|
+
self.request_counter = 0
|
|
159
|
+
self.status = "queued"
|
|
160
|
+
|
|
161
|
+
def json(self):
|
|
162
|
+
return {"response": {"status": self.status, "result": {"0": [[1, 1], [0, 0]]}}}
|
|
163
|
+
|
|
164
|
+
def on_request(self, *args, **kwargs):
|
|
165
|
+
self.request_counter += 1
|
|
166
|
+
if self.request_counter >= 3:
|
|
167
|
+
self.status = "finished"
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
result_return = ResultReturn()
|
|
171
|
+
|
|
172
|
+
with (
|
|
173
|
+
mock.patch('cirq_aqt.aqt_sampler.post', return_value=SubmitGoodResponse()) as submit_method,
|
|
174
|
+
mock.patch(
|
|
175
|
+
'cirq_aqt.aqt_sampler.get',
|
|
176
|
+
return_value=result_return,
|
|
177
|
+
side_effect=result_return.on_request,
|
|
178
|
+
) as result_method,
|
|
179
|
+
):
|
|
136
180
|
theta = sympy.Symbol('theta')
|
|
137
181
|
num_points = 1
|
|
138
182
|
max_angle = np.pi
|
|
139
183
|
repetitions = 10
|
|
140
|
-
sampler = AQTSampler(
|
|
184
|
+
sampler = AQTSampler(access_token='testkey', workspace="default", resource="test")
|
|
141
185
|
_, qubits = get_aqt_device(1)
|
|
142
|
-
circuit = cirq.Circuit(
|
|
186
|
+
circuit = cirq.Circuit(
|
|
187
|
+
cirq.PhasedXPowGate(exponent=theta, phase_exponent=0.0).on(qubits[0])
|
|
188
|
+
)
|
|
143
189
|
sweep = cirq.Linspace(key='theta', start=0.1, stop=max_angle / np.pi, length=num_points)
|
|
144
190
|
results = sampler.run_sweep(circuit, params=sweep, repetitions=repetitions)
|
|
145
191
|
excited_state_probs = np.zeros(num_points)
|
|
192
|
+
|
|
146
193
|
for i in range(num_points):
|
|
147
194
|
excited_state_probs[i] = np.mean(results[i].measurements['m'])
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
assert mock_method.call_count == 3
|
|
195
|
+
|
|
196
|
+
assert submit_method.call_count == 1
|
|
197
|
+
assert result_method.call_count == 3
|
|
152
198
|
|
|
153
199
|
|
|
154
200
|
def test_aqt_sampler_sim():
|
|
@@ -161,13 +207,13 @@ def test_aqt_sampler_sim():
|
|
|
161
207
|
sampler = AQTSamplerLocalSimulator()
|
|
162
208
|
sampler.simulate_ideal = True
|
|
163
209
|
circuit = cirq.Circuit(
|
|
164
|
-
cirq.
|
|
165
|
-
cirq.
|
|
166
|
-
cirq.
|
|
167
|
-
cirq.
|
|
168
|
-
cirq.
|
|
169
|
-
cirq.
|
|
170
|
-
cirq.
|
|
210
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=theta).on(qubits[3]),
|
|
211
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=1.0).on(qubits[0]),
|
|
212
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=1.0).on(qubits[0]),
|
|
213
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=1.0).on(qubits[1]),
|
|
214
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=1.0).on(qubits[1]),
|
|
215
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=1.0).on(qubits[2]),
|
|
216
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=1.0).on(qubits[2]),
|
|
171
217
|
)
|
|
172
218
|
circuit.append(cirq.PhasedXPowGate(phase_exponent=0.5, exponent=-0.5).on(qubits[0]))
|
|
173
219
|
circuit.append(cirq.PhasedXPowGate(phase_exponent=0.5, exponent=0.5).on(qubits[0]))
|
|
@@ -188,11 +234,13 @@ def test_aqt_sampler_sim_xtalk():
|
|
|
188
234
|
sampler = AQTSamplerLocalSimulator()
|
|
189
235
|
sampler.simulate_ideal = False
|
|
190
236
|
circuit = cirq.Circuit(
|
|
191
|
-
cirq.
|
|
192
|
-
cirq.
|
|
193
|
-
cirq.
|
|
194
|
-
cirq.
|
|
195
|
-
cirq.
|
|
237
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=1.0).on(qubits[0]),
|
|
238
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=1.0).on(qubits[1]),
|
|
239
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=1.0).on(qubits[1]),
|
|
240
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=1.0).on(qubits[3]),
|
|
241
|
+
cirq.PhasedXPowGate(phase_exponent=0.0, exponent=1.0).on(qubits[2]),
|
|
242
|
+
cirq.XX(qubits[0], qubits[1]) ** 0.5,
|
|
243
|
+
cirq.Z.on_each(*qubits),
|
|
196
244
|
)
|
|
197
245
|
sweep = cirq.Linspace(key='theta', start=0.1, stop=max_angle / np.pi, length=num_points)
|
|
198
246
|
_results = sampler.run_sweep(circuit, params=sweep, repetitions=repetitions)
|
|
@@ -220,3 +268,190 @@ def test_aqt_device_wrong_op_str():
|
|
|
220
268
|
for op in circuit.all_operations():
|
|
221
269
|
with pytest.raises(ValueError):
|
|
222
270
|
_result = get_op_string(op)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_aqt_sampler_parses_legacy_json_correctly() -> None:
|
|
274
|
+
legacy_json = json.dumps(
|
|
275
|
+
[
|
|
276
|
+
["R", 1.0, 0.0, [0]],
|
|
277
|
+
["MS", 0.5, [0, 1]],
|
|
278
|
+
["Z", -0.5, [0]],
|
|
279
|
+
["R", 0.5, 1.0, [0]],
|
|
280
|
+
["R", 0.5, 1.0, [1]],
|
|
281
|
+
]
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
sampler = AQTSampler("default", "test", "testkey")
|
|
285
|
+
quantum_circuit = sampler._parse_legacy_circuit_json(legacy_json)
|
|
286
|
+
|
|
287
|
+
assert quantum_circuit == [
|
|
288
|
+
{"operation": "R", "phi": 0.0, "theta": 1.0, "qubit": 0},
|
|
289
|
+
{"operation": "RXX", "qubits": [0, 1], "theta": 0.5},
|
|
290
|
+
{"operation": "RZ", "qubit": 0, "phi": -0.5},
|
|
291
|
+
{"operation": "R", "qubit": 0, "theta": 0.5, "phi": 1.0},
|
|
292
|
+
{"operation": "R", "qubit": 1, "theta": 0.5, "phi": 1.0},
|
|
293
|
+
{"operation": "MEASURE"},
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_aqt_sampler_submits_jobs_correctly() -> None:
|
|
298
|
+
legacy_json = json.dumps(
|
|
299
|
+
[
|
|
300
|
+
["R", 1.0, 0.0, [0]],
|
|
301
|
+
["MS", 0.5, [0, 1]],
|
|
302
|
+
["Z", -0.5, [0]],
|
|
303
|
+
["R", 0.5, 1.0, [0]],
|
|
304
|
+
["R", 0.5, 1.0, [1]],
|
|
305
|
+
]
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
result = [[1, 1], [0, 0]]
|
|
309
|
+
|
|
310
|
+
class ResultReturn:
|
|
311
|
+
def json(self):
|
|
312
|
+
return {"response": {"status": "finished", "result": {"0": result}}}
|
|
313
|
+
|
|
314
|
+
sampler = AQTSampler("default", "test", "testkey", "http://localhost:7777/api/v1/")
|
|
315
|
+
|
|
316
|
+
with (
|
|
317
|
+
mock.patch('cirq_aqt.aqt_sampler.post', return_value=SubmitGoodResponse()) as submit_method,
|
|
318
|
+
mock.patch('cirq_aqt.aqt_sampler.get', return_value=ResultReturn()) as result_method,
|
|
319
|
+
):
|
|
320
|
+
measurements = sampler._send_json(
|
|
321
|
+
json_str=legacy_json, id_str="test", repetitions=2, num_qubits=2
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
assert submit_method.call_count == 1
|
|
325
|
+
assert submit_method.call_args[0][0] == "http://localhost:7777/api/v1/submit/default/test"
|
|
326
|
+
|
|
327
|
+
assert result_method.call_count == 1
|
|
328
|
+
assert result_method.call_args[0][0] == "http://localhost:7777/api/v1/result/test_job"
|
|
329
|
+
|
|
330
|
+
for i, rep in enumerate(measurements):
|
|
331
|
+
for j, sample in enumerate(rep):
|
|
332
|
+
assert sample == result[i][j]
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def test_measurement_not_at_end_is_not_allowed() -> None:
|
|
336
|
+
legacy_json = json.dumps([["R", 1.0, 0.0, [0]], ["Meas"], ["MS", 0.5, [0, 1]]])
|
|
337
|
+
|
|
338
|
+
sampler = AQTSampler("default", "dummy_resource", "test")
|
|
339
|
+
with pytest.raises(ValueError):
|
|
340
|
+
sampler._send_json(json_str=legacy_json, id_str="test")
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def test_multiple_measurements_are_not_allowed() -> None:
|
|
344
|
+
legacy_json = json.dumps([["R", 1.0, 0.0, [0]], ["Meas"], ["Meas"]])
|
|
345
|
+
|
|
346
|
+
sampler = AQTSampler("default", "dummy_resource", "test")
|
|
347
|
+
with pytest.raises(ValueError):
|
|
348
|
+
sampler._send_json(json_str=legacy_json, id_str="test")
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def test_unknown_gate_in_json() -> None:
|
|
352
|
+
legacy_json = json.dumps([["A", 1.0, 0.0, [0]], ["Meas"]])
|
|
353
|
+
|
|
354
|
+
sampler = AQTSampler("default", "dummy_resource", "test")
|
|
355
|
+
with pytest.raises(
|
|
356
|
+
ValueError, match=r"Got unknown gate on operation: \['A', 1\.0, 0\.0, \[0\]\]\."
|
|
357
|
+
):
|
|
358
|
+
sampler._send_json(json_str=legacy_json, id_str="test")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def test_aqt_sampler_raises_exception_on_bad_result_response() -> None:
|
|
362
|
+
legacy_json = json.dumps([["R", 1.0, 0.0, [0]]])
|
|
363
|
+
|
|
364
|
+
class ResultReturn:
|
|
365
|
+
def json(self):
|
|
366
|
+
return {"response": {"status": "finished"}}
|
|
367
|
+
|
|
368
|
+
sampler = AQTSampler("default", "test", "testkey", "http://localhost:7777/api/v1/")
|
|
369
|
+
|
|
370
|
+
with (
|
|
371
|
+
mock.patch('cirq_aqt.aqt_sampler.post', return_value=SubmitGoodResponse()),
|
|
372
|
+
mock.patch('cirq_aqt.aqt_sampler.get', return_value=ResultReturn()),
|
|
373
|
+
pytest.raises(RuntimeError),
|
|
374
|
+
):
|
|
375
|
+
sampler._send_json(json_str=legacy_json, id_str="test", repetitions=2, num_qubits=2)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def test_aqt_sampler_print_resources_shows_hint_if_no_workspaces() -> None:
|
|
379
|
+
output = []
|
|
380
|
+
|
|
381
|
+
def intercept(values):
|
|
382
|
+
output.append(str(values))
|
|
383
|
+
|
|
384
|
+
with mock.patch('cirq_aqt.aqt_sampler.AQTSampler.fetch_resources', return_value=[]):
|
|
385
|
+
AQTSampler.print_resources(access_token="test", emit=intercept)
|
|
386
|
+
|
|
387
|
+
assert output[0] == "No workspaces are accessible with this access token."
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def test_aqt_sampler_print_resources_shows_hint_if_no_resources() -> None:
|
|
391
|
+
output = []
|
|
392
|
+
|
|
393
|
+
def intercept(values):
|
|
394
|
+
output.append(str(values))
|
|
395
|
+
|
|
396
|
+
empty_workspace_list = [{"id": "test_ws", "resources": []}]
|
|
397
|
+
|
|
398
|
+
with mock.patch(
|
|
399
|
+
'cirq_aqt.aqt_sampler.AQTSampler.fetch_resources', return_value=empty_workspace_list
|
|
400
|
+
):
|
|
401
|
+
AQTSampler.print_resources("test", emit=intercept)
|
|
402
|
+
|
|
403
|
+
assert output[0] == "No workspaces accessible with this access token contain resources."
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def test_aqt_sampler_print_resources_includes_received_resources_in_table() -> None:
|
|
407
|
+
output = []
|
|
408
|
+
|
|
409
|
+
def intercept(values):
|
|
410
|
+
output.append(str(values))
|
|
411
|
+
|
|
412
|
+
workspace_list = [
|
|
413
|
+
{"id": "test_ws", "resources": [{"id": "resource", "name": "Resource", "type": "device"}]}
|
|
414
|
+
]
|
|
415
|
+
|
|
416
|
+
with mock.patch('cirq_aqt.aqt_sampler.AQTSampler.fetch_resources', return_value=workspace_list):
|
|
417
|
+
AQTSampler.print_resources("test", emit=intercept)
|
|
418
|
+
|
|
419
|
+
assert any("test_ws" in o and "resource" in o and "Resource" in o and "D" in o for o in output)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def test_aqt_sampler_fetch_resources_raises_exception_if_non_200_status_code() -> None:
|
|
423
|
+
class ResourceResponse:
|
|
424
|
+
def __init__(self):
|
|
425
|
+
self.status_code = 403
|
|
426
|
+
|
|
427
|
+
def json(self):
|
|
428
|
+
return "error"
|
|
429
|
+
|
|
430
|
+
sampler = AQTSampler("default", "test", "testkey", "http://localhost:7777/api/v1/")
|
|
431
|
+
|
|
432
|
+
with (
|
|
433
|
+
mock.patch('cirq_aqt.aqt_sampler.get', return_value=ResourceResponse()),
|
|
434
|
+
pytest.raises(RuntimeError),
|
|
435
|
+
):
|
|
436
|
+
sampler.fetch_resources("token")
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def test_aqt_sampler_fetch_resources_returns_retrieved_resources() -> None:
|
|
440
|
+
class ResourceResponse:
|
|
441
|
+
def __init__(self):
|
|
442
|
+
self.status_code = 200
|
|
443
|
+
|
|
444
|
+
def json(self):
|
|
445
|
+
return [
|
|
446
|
+
{"id": "wid", "resources": [{"id": "rid", "name": "Resource", "type": "device"}]}
|
|
447
|
+
]
|
|
448
|
+
|
|
449
|
+
sampler = AQTSampler("default", "test", "testkey", "http://localhost:7777/api/v1/")
|
|
450
|
+
|
|
451
|
+
with mock.patch('cirq_aqt.aqt_sampler.get', return_value=ResourceResponse()):
|
|
452
|
+
workspaces = sampler.fetch_resources("token")
|
|
453
|
+
|
|
454
|
+
assert workspaces[0]["id"] == "wid"
|
|
455
|
+
assert workspaces[0]["resources"][0]["id"] == "rid"
|
|
456
|
+
assert workspaces[0]["resources"][0]["name"] == "Resource"
|
|
457
|
+
assert workspaces[0]["resources"][0]["type"] == "device"
|
cirq_aqt/aqt_simulator_test.py
CHANGED
|
@@ -44,21 +44,3 @@ def test_ms_crosstalk_n_noise():
|
|
|
44
44
|
(cirq.XX**0.015).on(cirq.LineQubit(2), cirq.LineQubit(0)),
|
|
45
45
|
(cirq.XX**0.015).on(cirq.LineQubit(2), cirq.LineQubit(3)),
|
|
46
46
|
]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_x_crosstalk_n_noise():
|
|
50
|
-
num_qubits = 4
|
|
51
|
-
noise_mod = AQTNoiseModel()
|
|
52
|
-
_, qubits = get_aqt_device(num_qubits)
|
|
53
|
-
circuit = cirq.Circuit()
|
|
54
|
-
circuit.append(cirq.Y(qubits[1]) ** 0.5)
|
|
55
|
-
circuit.append(cirq.Z(qubits[1]) ** 0.5)
|
|
56
|
-
circuit.append(cirq.X(qubits[1]) ** 0.5)
|
|
57
|
-
for moment in circuit.moments:
|
|
58
|
-
noisy_moment = noise_mod.noisy_moment(moment, qubits)
|
|
59
|
-
assert noisy_moment == [
|
|
60
|
-
(cirq.X**0.5).on(cirq.LineQubit(1)),
|
|
61
|
-
cirq.depolarize(p=0.001).on(cirq.LineQubit(1)),
|
|
62
|
-
(cirq.X**0.015).on(cirq.LineQubit(0)),
|
|
63
|
-
(cirq.X**0.015).on(cirq.LineQubit(2)),
|
|
64
|
-
]
|
cirq_aqt/aqt_target_gateset.py
CHANGED
|
@@ -30,8 +30,7 @@ class AQTTargetGateset(cirq.TwoQubitCompilationTargetGateset):
|
|
|
30
30
|
gates to the following universal target gateset:
|
|
31
31
|
|
|
32
32
|
- `cirq.XXPowGate`: The two qubit entangling gate.
|
|
33
|
-
- `cirq.
|
|
34
|
-
`cirq.PhasedXPowGate`: Single qubit rotations.
|
|
33
|
+
- `cirq.ZPowGate`, `cirq.PhasedXPowGate`: Single qubit rotations.
|
|
35
34
|
- `cirq.MeasurementGate`: Measurements.
|
|
36
35
|
"""
|
|
37
36
|
|
|
@@ -39,8 +38,6 @@ class AQTTargetGateset(cirq.TwoQubitCompilationTargetGateset):
|
|
|
39
38
|
super().__init__(
|
|
40
39
|
cirq.XXPowGate,
|
|
41
40
|
cirq.MeasurementGate,
|
|
42
|
-
cirq.XPowGate,
|
|
43
|
-
cirq.YPowGate,
|
|
44
41
|
cirq.ZPowGate,
|
|
45
42
|
cirq.PhasedXPowGate,
|
|
46
43
|
unroll_circuit_op=False,
|
|
@@ -31,8 +31,8 @@ Q, Q2, Q3, Q4 = cirq.LineQubit.range(4)
|
|
|
31
31
|
(cirq.HPowGate(exponent=0.5)(Q), False),
|
|
32
32
|
(cirq.XX(Q, Q2), True),
|
|
33
33
|
(cirq.measure(Q), True),
|
|
34
|
-
(cirq.XPowGate(exponent=0.5)(Q),
|
|
35
|
-
(cirq.YPowGate(exponent=0.25)(Q),
|
|
34
|
+
(cirq.XPowGate(exponent=0.5)(Q), False),
|
|
35
|
+
(cirq.YPowGate(exponent=0.25)(Q), False),
|
|
36
36
|
(cirq.ZPowGate(exponent=0.125)(Q), True),
|
|
37
37
|
(cirq.PhasedXPowGate(exponent=0.25, phase_exponent=0.125)(Q), True),
|
|
38
38
|
(cirq.CZPowGate(exponent=0.5)(Q, Q2), False),
|
{cirq_aqt-1.4.0.dev20240418165859.dist-info → cirq_aqt-1.4.0.dev20240425182738.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cirq-aqt
|
|
3
|
-
Version: 1.4.0.
|
|
3
|
+
Version: 1.4.0.dev20240425182738
|
|
4
4
|
Summary: A Cirq package to simulate and connect to Alpine Quantum Technologies quantum computers
|
|
5
5
|
Home-page: http://github.com/quantumlib/cirq
|
|
6
6
|
Author: The Cirq Developers
|
|
@@ -9,7 +9,7 @@ License: Apache 2
|
|
|
9
9
|
Requires-Python: >=3.9.0
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: requests ~=2.18
|
|
12
|
-
Requires-Dist: cirq-core ==1.4.0.
|
|
12
|
+
Requires-Dist: cirq-core ==1.4.0.dev20240425182738
|
|
13
13
|
|
|
14
14
|
**This is a development version of Cirq-AQT and may be unstable.**
|
|
15
15
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
cirq_aqt/__init__.py,sha256=2uufR12R3crUqeeDqMrA3rvsPqZuSNYysVLVvdpQzFk,803
|
|
2
|
+
cirq_aqt/_version.py,sha256=20dupYl-7k_nUGCrGssSGwvcs9o4s2CVVZs23-aKd3I,40
|
|
3
|
+
cirq_aqt/_version_test.py,sha256=b1Lhv0twYyu-3pKzJygENb3Uy57KwZXQoctswTiRJSg,141
|
|
4
|
+
cirq_aqt/aqt_device.py,sha256=48OblqJDD0R7xuwRHxHMcP3yxeTbGc5gSfWHZM9LE0Y,13654
|
|
5
|
+
cirq_aqt/aqt_device_metadata.py,sha256=vdYGf3gdZNJEIttlYNutdXGh9qz-93vNzLrail6ujYc,4779
|
|
6
|
+
cirq_aqt/aqt_device_metadata_test.py,sha256=naTXEU4VCCdR__gBlzbWjR0-DzViZ1xgVrH_GAyjskY,2111
|
|
7
|
+
cirq_aqt/aqt_device_test.py,sha256=eYHT1DIt3EI_4e-rSROiUw5_E4A9Bjt8tIchvUrKnxs,5210
|
|
8
|
+
cirq_aqt/aqt_sampler.py,sha256=GKkbF1iEuseDui9fDR6stJSrwQcssX4DTTeUhzFdezg,18379
|
|
9
|
+
cirq_aqt/aqt_sampler_test.py,sha256=EspGPtmOs6WJUEOZEjCGcbUANawPyOWAFL3ozsFiu7Y,16402
|
|
10
|
+
cirq_aqt/aqt_simulator_test.py,sha256=LSRyNSnme3A1l5eyDC79MzJLKZNoKa3LdsN_NoKMPTE,1673
|
|
11
|
+
cirq_aqt/aqt_target_gateset.py,sha256=1hvaHH5zSz292qgMxE7MQJxngytpUAH2nEj5Ob9e8CI,2779
|
|
12
|
+
cirq_aqt/aqt_target_gateset_test.py,sha256=wILzfJ3uWTzs-9R93I_qReZoWER-TaBhVr1lG3zl1WU,3336
|
|
13
|
+
cirq_aqt/conftest.py,sha256=2-K0ZniZ28adwVNB-f5IqvYjavKEBwC4jes_WQ17Xzg,667
|
|
14
|
+
cirq_aqt/json_resolver_cache.py,sha256=oZUTckLvATBDSrn-Y6kEAjHjiGXtKc-Bz_i23WsxFj8,788
|
|
15
|
+
cirq_aqt/json_test_data/__init__.py,sha256=K0c9IduIrV7xvSXoc0yJVz5T-ITed3VKlssTN8XYq9w,892
|
|
16
|
+
cirq_aqt/json_test_data/spec.py,sha256=RMp340PqpXN2iFQClZzTcXrKDVMKa_OLcXQ8qn2mPsw,1051
|
|
17
|
+
cirq_aqt-1.4.0.dev20240425182738.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
|
18
|
+
cirq_aqt-1.4.0.dev20240425182738.dist-info/METADATA,sha256=MLhAqwtRvfdgbM9TpxBC8G2_KhJggFNqBOdYldDp0Jw,1789
|
|
19
|
+
cirq_aqt-1.4.0.dev20240425182738.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
20
|
+
cirq_aqt-1.4.0.dev20240425182738.dist-info/top_level.txt,sha256=culYyFTEtuC3Z7wT3wZ6kGVspH3hYFZUEKooByfe9h0,9
|
|
21
|
+
cirq_aqt-1.4.0.dev20240425182738.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
cirq_aqt/__init__.py,sha256=2uufR12R3crUqeeDqMrA3rvsPqZuSNYysVLVvdpQzFk,803
|
|
2
|
-
cirq_aqt/_version.py,sha256=8Aglz7W7HlwC1YfKagLu65j2bqvxm11Lxc6Y-BsInew,40
|
|
3
|
-
cirq_aqt/_version_test.py,sha256=b1Lhv0twYyu-3pKzJygENb3Uy57KwZXQoctswTiRJSg,141
|
|
4
|
-
cirq_aqt/aqt_device.py,sha256=RIjNlZzB4cZkNKHytKRe-HHT2y85Fwtbr51fiD5AguY,13327
|
|
5
|
-
cirq_aqt/aqt_device_metadata.py,sha256=l8TtJtbxe1g99lygxVg5Njrn_mvnhxlvi0eZaptuuQI,4921
|
|
6
|
-
cirq_aqt/aqt_device_metadata_test.py,sha256=CJ7XAvvogUSJPfduq5GDZp9t-nZaIZGaRqRexeUmHlg,2111
|
|
7
|
-
cirq_aqt/aqt_device_test.py,sha256=eYHT1DIt3EI_4e-rSROiUw5_E4A9Bjt8tIchvUrKnxs,5210
|
|
8
|
-
cirq_aqt/aqt_sampler.py,sha256=9Iuam2RXcWW0uHha0eZETotJdiIrnC1gx9oNwUEPzfw,9738
|
|
9
|
-
cirq_aqt/aqt_sampler_test.py,sha256=0pYnqZikTySM1Yjr1IWoryGlIa_H54Dhq1e19poaaOg,7446
|
|
10
|
-
cirq_aqt/aqt_simulator_test.py,sha256=ZRW04CtQduqYfoEsxEKNa04CdTAr987xMhck-KGUIm0,2292
|
|
11
|
-
cirq_aqt/aqt_target_gateset.py,sha256=2HjfnCgJ2MaKITjvXqpej7Y2DVQEm7n7RAklanUKDWo,2873
|
|
12
|
-
cirq_aqt/aqt_target_gateset_test.py,sha256=dnwyv74JmnU3G0OX9-RS-KuEyxTmx9JQ9SbHS1pBmRE,3334
|
|
13
|
-
cirq_aqt/conftest.py,sha256=2-K0ZniZ28adwVNB-f5IqvYjavKEBwC4jes_WQ17Xzg,667
|
|
14
|
-
cirq_aqt/json_resolver_cache.py,sha256=oZUTckLvATBDSrn-Y6kEAjHjiGXtKc-Bz_i23WsxFj8,788
|
|
15
|
-
cirq_aqt/json_test_data/__init__.py,sha256=K0c9IduIrV7xvSXoc0yJVz5T-ITed3VKlssTN8XYq9w,892
|
|
16
|
-
cirq_aqt/json_test_data/spec.py,sha256=RMp340PqpXN2iFQClZzTcXrKDVMKa_OLcXQ8qn2mPsw,1051
|
|
17
|
-
cirq_aqt-1.4.0.dev20240418165859.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
|
18
|
-
cirq_aqt-1.4.0.dev20240418165859.dist-info/METADATA,sha256=gEnRXcZmRBjF1MzPM6Z_QnZSgkJ9KzWM9H2jDr2qYbQ,1789
|
|
19
|
-
cirq_aqt-1.4.0.dev20240418165859.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
20
|
-
cirq_aqt-1.4.0.dev20240418165859.dist-info/top_level.txt,sha256=culYyFTEtuC3Z7wT3wZ6kGVspH3hYFZUEKooByfe9h0,9
|
|
21
|
-
cirq_aqt-1.4.0.dev20240418165859.dist-info/RECORD,,
|
{cirq_aqt-1.4.0.dev20240418165859.dist-info → cirq_aqt-1.4.0.dev20240425182738.dist-info}/LICENSE
RENAMED
|
File without changes
|
{cirq_aqt-1.4.0.dev20240418165859.dist-info → cirq_aqt-1.4.0.dev20240425182738.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|