-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathpasqal_device.py
414 lines (333 loc) · 14.9 KB
/
pasqal_device.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# Copyright 2020 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import FrozenSet, Callable, List, Sequence, Any, Union, Dict
import numpy as np
import networkx as nx
import cirq
from cirq import _compat, GridQubit, LineQubit
from cirq.ops import NamedQubit
from cirq_pasqal import ThreeDQubit, TwoDQubit
@cirq.value.value_equality
class PasqalDevice(cirq.devices.Device):
"""A generic Pasqal device.
The most general of Pasqal devices, enforcing only restrictions expected to
be shared by all future devices. Serves as the parent class of all Pasqal
devices, but can also be used on its own for hosting a nearly unconstrained
device. When used as a circuit's device, the qubits have to be of the type
cirq.NamedQubit and assumed to be all connected, the idea behind it being
that after submission, all optimization and transpilation necessary for its
execution on the specified device are handled internally by Pasqal.
"""
def __init__(self, qubits: Sequence[cirq.Qid]) -> None:
"""Initializes a device with some qubits.
Args:
qubits (NamedQubit): Qubits on the device, exclusively unrelated to
a physical position.
Raises:
TypeError: If the wrong qubit type is provided.
ValueError: If the number of qubits is greater than the devices maximum.
"""
if len(qubits) > 0:
q_type = type(qubits[0])
for q in qubits:
if not isinstance(q, self.supported_qubit_type):
raise TypeError(
'Unsupported qubit type: {!r}. This device '
'supports qubit types: {}'.format(q, self.supported_qubit_type)
)
if not type(q) is q_type:
raise TypeError("All qubits must be of same type.")
if len(qubits) > self.maximum_qubit_number:
raise ValueError(
'Too many qubits. {} accepts at most {} '
'qubits.'.format(type(self), self.maximum_qubit_number)
)
self.gateset = cirq.Gateset(
cirq.ParallelGateFamily(cirq.H),
cirq.ParallelGateFamily(cirq.PhasedXPowGate),
cirq.ParallelGateFamily(cirq.XPowGate),
cirq.ParallelGateFamily(cirq.YPowGate),
cirq.ParallelGateFamily(cirq.ZPowGate),
cirq.AnyIntegerPowerGateFamily(cirq.CNotPowGate),
cirq.AnyIntegerPowerGateFamily(cirq.CCNotPowGate),
cirq.AnyIntegerPowerGateFamily(cirq.CZPowGate),
cirq.AnyIntegerPowerGateFamily(cirq.CCZPowGate),
cirq.IdentityGate,
cirq.MeasurementGate,
unroll_circuit_op=False,
accept_global_phase_op=False,
)
self.qubits = qubits
self._metadata = cirq.DeviceMetadata(
qubits, nx.from_edgelist([(a, b) for a in qubits for b in qubits if a != b])
)
# pylint: enable=missing-raises-doc
@property
def supported_qubit_type(self):
return (NamedQubit,)
@property
def maximum_qubit_number(self):
return 100
@property
def metadata(self):
return self._metadata
@_compat.deprecated(
fix='Use metadata.qubit_set() if applicable.',
deadline='v0.15',
)
def qubit_set(self) -> FrozenSet[cirq.Qid]:
return frozenset(self.qubits)
def qubit_list(self):
return [qubit for qubit in self.qubits]
@_compat.deprecated(
fix='Use PasqalConverter() to decompose operation instead.', deadline='v0.15'
)
def decompose_operation(self, operation: cirq.Operation) -> 'cirq.OP_TREE':
decomposition = [operation]
if not isinstance(operation, cirq.GateOperation):
raise TypeError(f"{operation!r} is not a gate operation.")
# Try to decompose the operation into elementary device operations
if not self.is_pasqal_device_op(operation):
decomposition = PasqalConverter().pasqal_convert(
operation, keep=self.is_pasqal_device_op
)
return decomposition
def is_pasqal_device_op(self, op: cirq.Operation) -> bool:
if not isinstance(op, cirq.Operation):
raise ValueError('Got unknown operation:', op)
return op in self.gateset
def validate_operation(self, operation: cirq.Operation):
"""Raises an error if the given operation is invalid on this device.
Args:
operation: The operation to validate.
Raises:
ValueError: If the operation is not valid.
NotImplementedError: If the operation is a measurement with an invert
mask.
"""
if not isinstance(operation, cirq.GateOperation):
raise ValueError("Unsupported operation")
if not self.is_pasqal_device_op(operation):
raise ValueError(f'{operation.gate!r} is not a supported gate')
for qub in operation.qubits:
if not isinstance(qub, self.supported_qubit_type):
raise ValueError(
'{} is not a valid qubit for gate {!r}. This '
'device accepts gates on qubits of type: '
'{}'.format(qub, operation.gate, self.supported_qubit_type)
)
if qub not in self.metadata.qubit_set:
raise ValueError(f'{qub} is not part of the device.')
if isinstance(operation.gate, cirq.MeasurementGate):
if operation.gate.invert_mask != ():
raise NotImplementedError(
"Measurements on Pasqal devices don't support invert_mask."
)
def validate_circuit(self, circuit: 'cirq.AbstractCircuit') -> None:
"""Raises an error if the given circuit is invalid on this device.
A circuit is invalid if any of its moments are invalid or if there
is a non-empty moment after a moment with a measurement.
Args:
circuit: The circuit to validate
Raises:
ValueError: If the given circuit can't be run on this device
"""
super().validate_circuit(circuit)
# Measurements must be in the last non-empty moment
has_measurement_occurred = False
for moment in circuit:
if has_measurement_occurred:
if len(moment.operations) > 0:
raise ValueError("Non-empty moment after measurement")
for operation in moment.operations:
if isinstance(operation.gate, cirq.MeasurementGate):
has_measurement_occurred = True
def can_add_operation_into_moment(self, operation: cirq.Operation, moment: cirq.Moment) -> bool:
"""Determines if it's possible to add an operation into a moment.
An operation can be added if the moment with the operation added is
valid.
Args:
operation: The operation being added.
moment: The moment being transformed.
Returns:
Whether or not the moment will validate after adding the operation.
Raises:
ValueError: If either of the given moment or operation is invalid
"""
if not super().can_add_operation_into_moment(operation, moment):
return False
try:
self.validate_moment(moment.with_operation(operation))
except ValueError:
return False
return True
def __repr__(self):
return f'pasqal.PasqalDevice(qubits={sorted(self.qubits)!r})'
def _value_equality_values_(self):
return self.qubits
def _json_dict_(self):
return cirq.protocols.obj_to_dict_helper(self, ['qubits'])
class PasqalVirtualDevice(PasqalDevice):
"""A Pasqal virtual device with qubits in 3d.
A virtual representation of a Pasqal device, enforcing the constraints
typically found in a physical device. The qubits can be positioned in 3d
space, although 2d layouts will be supported sooner and are thus
recommended. Only accepts qubits with physical placement.
"""
def __init__(
self, control_radius: float, qubits: Sequence[Union[ThreeDQubit, GridQubit, LineQubit]]
) -> None:
"""Initializes a device with some qubits.
Args:
control_radius: the maximum distance between qubits for a controlled
gate. Distance is measured in units of the coordinates passed
into the qubit constructor.
qubits: Qubits on the device, identified by their x, y, z position.
Must be of type ThreeDQubit, TwoDQubit, LineQubit or GridQubit.
Raises:
ValueError: if the wrong qubit type is provided or if invalid
parameter is provided for control_radius."""
super().__init__(qubits)
if not control_radius >= 0:
raise ValueError('Control_radius needs to be a non-negative float.')
if len(self.qubits) > 1:
if control_radius > 3.0 * self.minimal_distance():
raise ValueError(
'Control_radius cannot be larger than 3 times'
' the minimal distance between qubits.'
)
self.control_radius = control_radius
self.exclude_gateset = cirq.Gateset(
cirq.AnyIntegerPowerGateFamily(cirq.CNotPowGate),
cirq.AnyIntegerPowerGateFamily(cirq.CCNotPowGate),
cirq.AnyIntegerPowerGateFamily(cirq.CCZPowGate),
)
self.controlled_gateset = cirq.Gateset(
*self.exclude_gateset.gates,
cirq.AnyIntegerPowerGateFamily(cirq.CZPowGate),
)
@property
def supported_qubit_type(self):
return (
ThreeDQubit,
TwoDQubit,
GridQubit,
LineQubit,
)
def is_pasqal_device_op(self, op: cirq.Operation) -> bool:
return super().is_pasqal_device_op(op) and op not in self.exclude_gateset
def validate_operation(self, operation: cirq.Operation):
"""Raises an error if the given operation is invalid on this device.
Args:
operation: the operation to validate
Raises:
ValueError: If the operation is not valid
"""
super().validate_operation(operation)
# Verify that a controlled gate operation is valid
if operation in self.controlled_gateset:
for p in operation.qubits:
for q in operation.qubits:
if self.distance(p, q) > self.control_radius:
raise ValueError(f"Qubits {p!r}, {q!r} are too far away")
def validate_moment(self, moment: cirq.Moment):
"""Raises an error if the given moment is invalid on this device.
Args:
moment: The moment to validate.
Raises:
ValueError: If the given moment is invalid.
"""
super().validate_moment(moment)
if len(moment) > 1:
for operation in moment:
if not isinstance(operation.gate, cirq.MeasurementGate):
raise ValueError("Cannot do simultaneous gates. Use cirq.InsertStrategy.NEW.")
def minimal_distance(self) -> float:
"""Returns the minimal distance between two qubits in qubits.
Args:
qubits: qubit involved in the distance computation
Raises:
ValueError: If the device has only one qubit
Returns:
The minimal distance between qubits, in spacial coordinate units.
"""
if len(self.qubits) <= 1:
raise ValueError("Two qubits to compute a minimal distance.")
return min([self.distance(q1, q2) for q1 in self.qubits for q2 in self.qubits if q1 != q2])
def distance(self, p: Any, q: Any) -> float:
"""Returns the distance between two qubits.
Args:
p: qubit involved in the distance computation
q: qubit involved in the distance computation
Raises:
ValueError: If p or q not part of the device
Returns:
The distance between qubits p and q.
"""
all_qubits = self.qubit_list()
if p not in all_qubits or q not in all_qubits:
raise ValueError("Qubit not part of the device.")
if isinstance(p, GridQubit):
return np.sqrt((p.row - q.row) ** 2 + (p.col - q.col) ** 2)
if isinstance(p, LineQubit):
return abs(p.x - q.x)
return np.sqrt((p.x - q.x) ** 2 + (p.y - q.y) ** 2 + (p.z - q.z) ** 2)
def __repr__(self):
return ('pasqal.PasqalVirtualDevice(control_radius={!r}, qubits={!r})').format(
self.control_radius, sorted(self.qubits)
)
def _value_equality_values_(self) -> Any:
return (self.control_radius, self.qubits)
def _json_dict_(self) -> Dict[str, Any]:
return cirq.protocols.obj_to_dict_helper(self, ['control_radius', 'qubits'])
@_compat.deprecated(
deadline='v0.15',
fix='qubit coupling data can now be found in device.metadata if provided.',
)
def qid_pairs(self) -> FrozenSet['cirq.SymmetricalQidPair']:
"""Returns a list of qubit edges on the device.
Returns:
All qubit pairs that are less or equal to the control radius apart.
"""
with _compat.block_overlapping_deprecation('device\\.metadata'):
qs = self.qubits
return frozenset(
[
cirq.SymmetricalQidPair(q, q2)
for q in qs
for q2 in qs
if q < q2 and self.distance(q, q2) <= self.control_radius
]
)
class PasqalConverter(cirq.neutral_atoms.ConvertToNeutralAtomGates):
"""A gate converter for compatibility with Pasqal processors.
Modified version of ConvertToNeutralAtomGates, where a new 'convert' method
'pasqal_convert' takes the 'keep' function as an input.
"""
def pasqal_convert(
self, op: cirq.Operation, keep: Callable[[cirq.Operation], bool]
) -> List[cirq.Operation]:
def on_stuck_raise(bad):
return TypeError(
"Don't know how to work with {!r}. "
"It isn't a native PasqalDevice operation, "
"a 1 or 2 qubit gate with a known unitary, "
"or composite.".format(bad)
)
return cirq.protocols.decompose(
op,
keep=keep,
intercepting_decomposer=self._convert_one,
on_stuck_raise=None if self.ignore_failures else on_stuck_raise,
)