mibo-client 0.1.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.
- mb_client/__init__.py +45 -0
- mb_client/circuit/__init__.py +28 -0
- mb_client/circuit/circuit.py +482 -0
- mb_client/circuit/gates.py +147 -0
- mb_client/circuit/operations.py +63 -0
- mb_client/client.py +210 -0
- mb_client/common/__init__.py +17 -0
- mb_client/common/parameters.py +182 -0
- mb_client/compilation/__init__.py +24 -0
- mb_client/compilation/basis_translation.py +333 -0
- mb_client/compilation/calibration.py +110 -0
- mb_client/compilation/pulse_lowering.py +494 -0
- mb_client/compilation/schedule_map.py +282 -0
- mb_client/compilation/target.py +203 -0
- mb_client/conf/__init__.py +19 -0
- mb_client/conf/settings.py +24 -0
- mb_client/exceptions.py +23 -0
- mb_client/execution/__init__.py +27 -0
- mb_client/execution/backends.py +58 -0
- mb_client/execution/jobs.py +61 -0
- mb_client/execution/options.py +67 -0
- mb_client/execution/readout.py +64 -0
- mb_client/execution/remote.py +123 -0
- mb_client/execution/results.py +74 -0
- mb_client/execution/sweep.py +373 -0
- mb_client/protocol.py +297 -0
- mb_client/pulse/__init__.py +32 -0
- mb_client/pulse/block.py +101 -0
- mb_client/pulse/channels.py +59 -0
- mb_client/pulse/envelopes.py +459 -0
- mb_client/pulse/schedule.py +259 -0
- mb_client/pulse/sequence.py +406 -0
- mb_client/utils/__init__.py +19 -0
- mb_client/utils/formatting.py +54 -0
- mb_client/visualization/__init__.py +15 -0
- mb_client/visualization/circuit/__init__.py +36 -0
- mb_client/visualization/circuit/drawer.py +420 -0
- mb_client/visualization/circuit/matplotlib.py +232 -0
- mb_client/visualization/circuit/styles/default.json +87 -0
- mb_client/visualization/pulse.py +402 -0
- mibo_client-0.1.0.dist-info/METADATA +90 -0
- mibo_client-0.1.0.dist-info/RECORD +44 -0
- mibo_client-0.1.0.dist-info/WHEEL +4 -0
- mibo_client-0.1.0.dist-info/licenses/LICENSE +201 -0
mb_client/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright 2026 mibosuili
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Public API for the MB Cloud client SDK."""
|
|
16
|
+
|
|
17
|
+
from .client import MBCloudClient
|
|
18
|
+
from .exceptions import ClientError, ServerConnectionError
|
|
19
|
+
from .execution import MeasurementLevel
|
|
20
|
+
from .execution import (ExecutionResult, Job, JobCancelledError,
|
|
21
|
+
JobExecutionError, JobStatus, ProgramResult,
|
|
22
|
+
RemoteBackend, RemoteJob, RunOptions)
|
|
23
|
+
from .circuit import QuantumCircuit
|
|
24
|
+
from .protocol import decode_circuit, encode_circuit, decode_result, encode_result
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"ClientError",
|
|
28
|
+
"decode_circuit",
|
|
29
|
+
"decode_result",
|
|
30
|
+
"encode_result",
|
|
31
|
+
"encode_circuit",
|
|
32
|
+
"ExecutionResult",
|
|
33
|
+
"Job",
|
|
34
|
+
"JobCancelledError",
|
|
35
|
+
"JobExecutionError",
|
|
36
|
+
"JobStatus",
|
|
37
|
+
"MBCloudClient",
|
|
38
|
+
"MeasurementLevel",
|
|
39
|
+
"ProgramResult",
|
|
40
|
+
"QuantumCircuit",
|
|
41
|
+
"RemoteBackend",
|
|
42
|
+
"RemoteJob",
|
|
43
|
+
"RunOptions",
|
|
44
|
+
"ServerConnectionError",
|
|
45
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright 2026 mibosuili
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from .circuit import (CircuitInstruction, Clbit, Gate, Instruction,
|
|
16
|
+
QuantumCircuit, Qubit)
|
|
17
|
+
from .gates import (IGate, XGate, YGate, SYGate, SXGate, SXdgGate, RXGate,
|
|
18
|
+
RYGate, RXYGate, RZGate, HGate, CXGate, CZGate,
|
|
19
|
+
UnitaryGate, VirtualZ)
|
|
20
|
+
from .operations import (Barrier, Delay, Measure, Reset, Drive, Idle)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
'CircuitInstruction', 'Clbit', 'Gate', 'Instruction', 'QuantumCircuit',
|
|
24
|
+
'Qubit', 'IGate', 'XGate', 'YGate', 'SYGate', 'SXGate', 'SXdgGate',
|
|
25
|
+
'RXGate', 'RYGate', 'RXYGate', 'RZGate', 'HGate', 'CXGate', 'CZGate',
|
|
26
|
+
'UnitaryGate', 'VirtualZ', 'Barrier', 'Delay', 'Measure',
|
|
27
|
+
'Reset', 'Drive', 'Idle'
|
|
28
|
+
]
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
# Copyright 2026 mibosuili
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""MB Cloud's lightweight circuit intermediate representation.
|
|
16
|
+
|
|
17
|
+
The public shape is intentionally familiar to users of circuit SDKs, while the
|
|
18
|
+
implementation is focused on cloud transport and hardware compilation needs.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
import copy
|
|
23
|
+
import math
|
|
24
|
+
from numbers import Real
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from typing import Iterable, Mapping, Sequence
|
|
27
|
+
from mb_client.common.parameters import Parameter, ParameterExpression, bind_value, parameters_in
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, order=True)
|
|
31
|
+
class Qubit:
|
|
32
|
+
index: int
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, order=True)
|
|
36
|
+
class Clbit:
|
|
37
|
+
index: int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class CircuitInstruction:
|
|
42
|
+
operation: "Instruction"
|
|
43
|
+
qubits: tuple[Qubit, ...]
|
|
44
|
+
clbits: tuple[Clbit, ...] = ()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Instruction:
|
|
48
|
+
"""A circuit operation with fixed quantum/classical arity."""
|
|
49
|
+
|
|
50
|
+
def __init__(self,
|
|
51
|
+
name: str,
|
|
52
|
+
num_qubits: int,
|
|
53
|
+
num_clbits: int = 0,
|
|
54
|
+
params: Sequence[object] = ()):
|
|
55
|
+
if not name or num_qubits < 0 or num_clbits < 0:
|
|
56
|
+
raise ValueError("Instruction name and bit counts must be valid")
|
|
57
|
+
self.name = name
|
|
58
|
+
self.num_qubits = num_qubits
|
|
59
|
+
self.num_clbits = num_clbits
|
|
60
|
+
self.params = list(params)
|
|
61
|
+
self._definition: QuantumCircuit | None = None
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def definition(self) -> "QuantumCircuit | None":
|
|
65
|
+
return self._definition
|
|
66
|
+
|
|
67
|
+
@definition.setter
|
|
68
|
+
def definition(self, circuit: "QuantumCircuit | None") -> None:
|
|
69
|
+
if circuit is not None and circuit.num_qubits != self.num_qubits:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
"Instruction definition has the wrong number of qubits")
|
|
72
|
+
self._definition = circuit
|
|
73
|
+
|
|
74
|
+
def copy(self) -> "Instruction":
|
|
75
|
+
return copy.deepcopy(self)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Gate(Instruction):
|
|
79
|
+
"""An instruction without classical outputs."""
|
|
80
|
+
|
|
81
|
+
def __init__(self,
|
|
82
|
+
name: str,
|
|
83
|
+
num_qubits: int,
|
|
84
|
+
params: Sequence[object] = ()):
|
|
85
|
+
super().__init__(name, num_qubits, 0, params)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class QuantumCircuit:
|
|
89
|
+
"""Ordered logical operations over indexed quantum and classical bits."""
|
|
90
|
+
|
|
91
|
+
def __init__(self,
|
|
92
|
+
num_qubits: int,
|
|
93
|
+
num_clbits: int = 0,
|
|
94
|
+
name: str | None = None,
|
|
95
|
+
*,
|
|
96
|
+
bias: Mapping[int, object] | None = None):
|
|
97
|
+
if num_qubits < 0 or num_clbits < 0:
|
|
98
|
+
raise ValueError("Circuit bit counts cannot be negative")
|
|
99
|
+
self.name = name or "circuit"
|
|
100
|
+
self.metadata: dict[str, object] = {}
|
|
101
|
+
self.physical_qubits = tuple(range(num_qubits))
|
|
102
|
+
self.qubits = tuple(Qubit(index) for index in range(num_qubits))
|
|
103
|
+
self.clbits = tuple(Clbit(index) for index in range(num_clbits))
|
|
104
|
+
self.data: list[CircuitInstruction] = []
|
|
105
|
+
self._bias = self._validate_bias(bias or {})
|
|
106
|
+
|
|
107
|
+
def _validate_bias(self, bias):
|
|
108
|
+
result = {}
|
|
109
|
+
for qubit, value in bias.items():
|
|
110
|
+
if type(qubit) is not int or not 0 <= qubit < self.num_qubits:
|
|
111
|
+
raise ValueError(f"invalid logical bias qubit {qubit!r}")
|
|
112
|
+
if not isinstance(value, (Parameter, ParameterExpression)):
|
|
113
|
+
if isinstance(value, bool) or not isinstance(value, Real):
|
|
114
|
+
raise TypeError(
|
|
115
|
+
"bias must be a finite real number or parameter")
|
|
116
|
+
if not math.isfinite(value):
|
|
117
|
+
raise ValueError("bias must be a finite real number")
|
|
118
|
+
value = float(value)
|
|
119
|
+
result[qubit] = copy.deepcopy(value)
|
|
120
|
+
return result
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def bias(self) -> dict[int, object]:
|
|
124
|
+
"""A copy of whole-execution bias settings keyed by logical qubit.
|
|
125
|
+
|
|
126
|
+
Unspecified qubits use backend defaults. Bias has no timeline position.
|
|
127
|
+
"""
|
|
128
|
+
return copy.deepcopy(self._bias)
|
|
129
|
+
|
|
130
|
+
def with_bias(self, bias: Mapping[int, object]) -> "QuantumCircuit":
|
|
131
|
+
"""Return a copy with supplied settings replacing matching logical entries."""
|
|
132
|
+
overrides = self._validate_bias(bias)
|
|
133
|
+
target = copy.deepcopy(self)
|
|
134
|
+
target._bias.update(overrides)
|
|
135
|
+
return target
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def num_qubits(self) -> int:
|
|
139
|
+
return len(self.qubits)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def num_clbits(self) -> int:
|
|
143
|
+
return len(self.clbits)
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def parameters(self):
|
|
147
|
+
found = set(parameters_in(self._bias))
|
|
148
|
+
for item in self.data:
|
|
149
|
+
found.update(parameters_in(item.operation.params))
|
|
150
|
+
found.update(
|
|
151
|
+
parameters_in(getattr(item.operation, "calibration", {})))
|
|
152
|
+
found.update(
|
|
153
|
+
parameters_in(getattr(item.operation, "parameters", {})))
|
|
154
|
+
return frozenset(found)
|
|
155
|
+
|
|
156
|
+
def bind_parameters(self,
|
|
157
|
+
values,
|
|
158
|
+
*,
|
|
159
|
+
per_qubit_values=None,
|
|
160
|
+
inplace: bool = False) -> "QuantumCircuit":
|
|
161
|
+
per_qubit_values = per_qubit_values or {}
|
|
162
|
+
unknown = (set(values).union(per_qubit_values).difference(
|
|
163
|
+
self.parameters).difference(parameter.name
|
|
164
|
+
for parameter in self.parameters))
|
|
165
|
+
if unknown:
|
|
166
|
+
raise ValueError(
|
|
167
|
+
f"Unknown circuit parameters: {sorted(map(str, unknown))}")
|
|
168
|
+
target = self if inplace else copy.deepcopy(self)
|
|
169
|
+
bound_bias = {}
|
|
170
|
+
for qubit, value in target._bias.items():
|
|
171
|
+
local_values = dict(values)
|
|
172
|
+
for parameter in parameters_in(value):
|
|
173
|
+
if parameter.name in values and parameter not in local_values:
|
|
174
|
+
if sum(p.name == parameter.name
|
|
175
|
+
for p in self.parameters) != 1:
|
|
176
|
+
raise ValueError(
|
|
177
|
+
f"Ambiguous parameter name {parameter.name!r}; use Parameter keys"
|
|
178
|
+
)
|
|
179
|
+
local_values[parameter] = values[parameter.name]
|
|
180
|
+
qubit_values = per_qubit_values.get(
|
|
181
|
+
parameter, per_qubit_values.get(parameter.name))
|
|
182
|
+
if qubit_values is not None:
|
|
183
|
+
physical = target.physical_qubits[qubit]
|
|
184
|
+
if physical not in qubit_values:
|
|
185
|
+
raise ValueError(
|
|
186
|
+
f"missing value for parameter {parameter.name!r} on physical qubit {physical}"
|
|
187
|
+
)
|
|
188
|
+
local_values[parameter] = qubit_values[physical]
|
|
189
|
+
bound_bias[qubit] = bind_value(value, local_values)
|
|
190
|
+
target._bias = target._validate_bias(bound_bias)
|
|
191
|
+
for item in target.data:
|
|
192
|
+
operation = item.operation
|
|
193
|
+
item_parameters = set(parameters_in(operation.params))
|
|
194
|
+
for attribute in ("calibration", "parameters"):
|
|
195
|
+
item_parameters.update(
|
|
196
|
+
parameters_in(getattr(operation, attribute, {})))
|
|
197
|
+
local_values = dict(values)
|
|
198
|
+
# Circuit binding is explicit user input, unlike pulse operation binding.
|
|
199
|
+
for parameter in item_parameters:
|
|
200
|
+
if parameter.name in values and parameter not in local_values:
|
|
201
|
+
same_name = {
|
|
202
|
+
p
|
|
203
|
+
for p in self.parameters if p.name == parameter.name
|
|
204
|
+
}
|
|
205
|
+
if len(same_name) != 1:
|
|
206
|
+
raise ValueError(
|
|
207
|
+
f"Ambiguous parameter name {parameter.name!r}; use Parameter keys"
|
|
208
|
+
)
|
|
209
|
+
local_values[parameter] = values[parameter.name]
|
|
210
|
+
for parameter, qubit_values in per_qubit_values.items():
|
|
211
|
+
if parameter not in item_parameters:
|
|
212
|
+
continue
|
|
213
|
+
if len(item.qubits) != 1:
|
|
214
|
+
raise ValueError(
|
|
215
|
+
f"per-qubit parameter {parameter.name!r} is used by "
|
|
216
|
+
f"multi-qubit instruction {operation.name!r}")
|
|
217
|
+
physical = target.physical_qubits[item.qubits[0].index]
|
|
218
|
+
if physical not in qubit_values:
|
|
219
|
+
raise ValueError(
|
|
220
|
+
f"missing value for parameter {parameter.name!r} on "
|
|
221
|
+
f"physical qubit {physical}")
|
|
222
|
+
local_values[parameter] = qubit_values[physical]
|
|
223
|
+
operation.params = bind_value(operation.params, local_values)
|
|
224
|
+
for attribute in ("calibration", "parameters"):
|
|
225
|
+
if hasattr(operation, attribute):
|
|
226
|
+
setattr(
|
|
227
|
+
operation, attribute,
|
|
228
|
+
bind_value(getattr(operation, attribute),
|
|
229
|
+
local_values))
|
|
230
|
+
return target
|
|
231
|
+
|
|
232
|
+
def append(
|
|
233
|
+
self,
|
|
234
|
+
operation: Instruction,
|
|
235
|
+
qubits: Sequence[int | Qubit],
|
|
236
|
+
clbits: Sequence[int | Clbit] = ()
|
|
237
|
+
) -> CircuitInstruction:
|
|
238
|
+
if not isinstance(operation, Instruction):
|
|
239
|
+
raise TypeError("operation must be an MB Cloud Instruction")
|
|
240
|
+
qargs = tuple(self._resolve_qubit(bit) for bit in qubits)
|
|
241
|
+
cargs = tuple(self._resolve_clbit(bit) for bit in clbits)
|
|
242
|
+
if len(qargs) != operation.num_qubits or len(
|
|
243
|
+
cargs) != operation.num_clbits:
|
|
244
|
+
raise ValueError(
|
|
245
|
+
f"{operation.name} expects ({operation.num_qubits}, "
|
|
246
|
+
f"{operation.num_clbits}) bits, got ({len(qargs)}, {len(cargs)})"
|
|
247
|
+
)
|
|
248
|
+
if len(set(qargs)) != len(qargs):
|
|
249
|
+
raise ValueError("An instruction cannot use the same qubit twice")
|
|
250
|
+
item = CircuitInstruction(operation.copy(), qargs, cargs)
|
|
251
|
+
self.data.append(item)
|
|
252
|
+
return item
|
|
253
|
+
|
|
254
|
+
def compose(self,
|
|
255
|
+
other: "QuantumCircuit",
|
|
256
|
+
qubits: Sequence[int | Qubit] | None = None,
|
|
257
|
+
inplace: bool = False) -> "QuantumCircuit":
|
|
258
|
+
target = self if inplace else copy.deepcopy(self)
|
|
259
|
+
mapped = tuple(target.qubits if qubits is None else (
|
|
260
|
+
target._resolve_qubit(bit) for bit in qubits))
|
|
261
|
+
if len(mapped) != other.num_qubits:
|
|
262
|
+
raise ValueError("compose qubit mapping has the wrong size")
|
|
263
|
+
if len(set(mapped)) != len(mapped):
|
|
264
|
+
raise ValueError("compose qubit mapping contains duplicates")
|
|
265
|
+
merged_bias = target.bias
|
|
266
|
+
for qubit, value in other.bias.items():
|
|
267
|
+
mapped_qubit = mapped[qubit].index
|
|
268
|
+
if mapped_qubit in merged_bias and merged_bias[
|
|
269
|
+
mapped_qubit] != value:
|
|
270
|
+
raise ValueError(
|
|
271
|
+
f"conflicting static biases for logical qubit {mapped_qubit}"
|
|
272
|
+
)
|
|
273
|
+
merged_bias[mapped_qubit] = value
|
|
274
|
+
for item in other.data:
|
|
275
|
+
target.append(item.operation,
|
|
276
|
+
[mapped[bit.index] for bit in item.qubits],
|
|
277
|
+
[bit.index for bit in item.clbits])
|
|
278
|
+
target._bias = merged_bias
|
|
279
|
+
return target
|
|
280
|
+
|
|
281
|
+
def to_gate(self, name: str | None = None) -> Gate:
|
|
282
|
+
if self._bias:
|
|
283
|
+
raise ValueError("A circuit with static bias cannot become a Gate")
|
|
284
|
+
if self.num_clbits:
|
|
285
|
+
raise ValueError(
|
|
286
|
+
"A circuit containing classical bits cannot become a Gate")
|
|
287
|
+
gate = Gate(name or self.name, self.num_qubits)
|
|
288
|
+
gate.definition = copy.deepcopy(self)
|
|
289
|
+
return gate
|
|
290
|
+
|
|
291
|
+
def barrier(self, *qubits: int | Qubit) -> CircuitInstruction:
|
|
292
|
+
from mb_client.circuit.operations import Barrier
|
|
293
|
+
qargs = qubits or self.qubits
|
|
294
|
+
return self.append(Barrier(len(qargs)), qargs)
|
|
295
|
+
|
|
296
|
+
def measure(self, qubit: int | Qubit | Sequence[int | Qubit],
|
|
297
|
+
clbit: int | Clbit | Sequence[int | Clbit], **options):
|
|
298
|
+
"""Measure a scalar pair or equally sized lists of quantum/classical bits.
|
|
299
|
+
|
|
300
|
+
List calls return a tuple and add no implicit alignment barrier.
|
|
301
|
+
All operands are validated before any instructions are appended.
|
|
302
|
+
"""
|
|
303
|
+
from mb_client.circuit.operations import Measure
|
|
304
|
+
multiple = isinstance(qubit, (list, tuple))
|
|
305
|
+
if multiple != isinstance(clbit, (list, tuple)):
|
|
306
|
+
raise TypeError("measure requires two scalars or two lists/tuples")
|
|
307
|
+
qubits = qubit if multiple else (qubit, )
|
|
308
|
+
clbits = clbit if multiple else (clbit, )
|
|
309
|
+
if len(qubits) != len(clbits):
|
|
310
|
+
raise ValueError(
|
|
311
|
+
"measurement qubits and clbits must have equal lengths")
|
|
312
|
+
qargs = tuple(self._resolve_qubit(q) for q in qubits)
|
|
313
|
+
cargs = tuple(self._resolve_clbit(c) for c in clbits)
|
|
314
|
+
if len(set(qargs)) != len(qargs) or len(set(cargs)) != len(cargs):
|
|
315
|
+
raise ValueError(
|
|
316
|
+
"measurement operands must not contain duplicates")
|
|
317
|
+
operation = Measure(**options)
|
|
318
|
+
items = tuple(
|
|
319
|
+
self.append(operation, [q], [c]) for q, c in zip(qargs, cargs))
|
|
320
|
+
return items if multiple else items[0]
|
|
321
|
+
|
|
322
|
+
def measure_all(self, **options) -> tuple[CircuitInstruction, ...]:
|
|
323
|
+
"""Measure logical qubit i into clbit i, adding missing classical bits.
|
|
324
|
+
|
|
325
|
+
Existing bits and instructions are retained. No barrier is inserted.
|
|
326
|
+
"""
|
|
327
|
+
if self.num_clbits < self.num_qubits:
|
|
328
|
+
self.clbits += tuple(
|
|
329
|
+
Clbit(i) for i in range(self.num_clbits, self.num_qubits))
|
|
330
|
+
return self.measure(list(self.qubits),
|
|
331
|
+
list(self.clbits[:self.num_qubits]), **options)
|
|
332
|
+
|
|
333
|
+
def delay(self,
|
|
334
|
+
duration: float,
|
|
335
|
+
qubit: int | Qubit,
|
|
336
|
+
channel: str = "drive") -> CircuitInstruction:
|
|
337
|
+
from mb_client.circuit.operations import Delay
|
|
338
|
+
return self.append(Delay(duration, channel), [qubit])
|
|
339
|
+
|
|
340
|
+
def pulse_block(self,
|
|
341
|
+
sequence,
|
|
342
|
+
qubits,
|
|
343
|
+
*,
|
|
344
|
+
name: str | None = None) -> CircuitInstruction:
|
|
345
|
+
"""Insert a copy of an explicitly engineered PulseSequence(template=True).
|
|
346
|
+
|
|
347
|
+
Channel index i addresses qubits[i]. The compiler ordering policy controls
|
|
348
|
+
synchronization with surrounding instructions. Delays and barriers inside
|
|
349
|
+
the sequence control internal timing.
|
|
350
|
+
"""
|
|
351
|
+
from mb_client.pulse.block import PulseBlock
|
|
352
|
+
targets = (qubits, ) if isinstance(qubits,
|
|
353
|
+
(int, Qubit)) else tuple(qubits)
|
|
354
|
+
return self.append(PulseBlock(sequence, len(targets), name=name),
|
|
355
|
+
targets)
|
|
356
|
+
|
|
357
|
+
def x(self, qubit, **calibration):
|
|
358
|
+
from mb_client.circuit.gates import XGate
|
|
359
|
+
return self.append(XGate(**calibration), [qubit])
|
|
360
|
+
|
|
361
|
+
def y(self, qubit, **calibration):
|
|
362
|
+
from mb_client.circuit.gates import YGate
|
|
363
|
+
return self.append(YGate(**calibration), [qubit])
|
|
364
|
+
|
|
365
|
+
def id(self, duration: float, qubit):
|
|
366
|
+
from mb_client.circuit.gates import IGate
|
|
367
|
+
return self.append(IGate(duration), [qubit])
|
|
368
|
+
|
|
369
|
+
def sx(self, qubit, **calibration):
|
|
370
|
+
from mb_client.circuit.gates import SXGate
|
|
371
|
+
return self.append(SXGate(**calibration), [qubit])
|
|
372
|
+
|
|
373
|
+
def sy(self, qubit, **calibration):
|
|
374
|
+
from mb_client.circuit.gates import SYGate
|
|
375
|
+
return self.append(SYGate(**calibration), [qubit])
|
|
376
|
+
|
|
377
|
+
def unitary(self, matrix, qubits, *, name: str = "unitary"):
|
|
378
|
+
from mb_client.circuit.gates import UnitaryGate
|
|
379
|
+
if isinstance(qubits, (int, Qubit)):
|
|
380
|
+
qubits = [qubits]
|
|
381
|
+
return self.append(UnitaryGate(matrix, name=name), qubits)
|
|
382
|
+
|
|
383
|
+
def rz(self, phi, qubit):
|
|
384
|
+
from mb_client.circuit.gates import RZGate
|
|
385
|
+
return self.append(RZGate(phi), [qubit])
|
|
386
|
+
|
|
387
|
+
def rx(self, theta, qubit):
|
|
388
|
+
from mb_client.circuit.gates import RXGate
|
|
389
|
+
return self.append(RXGate(theta), [qubit])
|
|
390
|
+
|
|
391
|
+
def ry(self, theta, qubit):
|
|
392
|
+
from mb_client.circuit.gates import RYGate
|
|
393
|
+
return self.append(RYGate(theta), [qubit])
|
|
394
|
+
|
|
395
|
+
def rxy(self, theta, phi, qubit):
|
|
396
|
+
from mb_client.circuit.gates import RXYGate
|
|
397
|
+
return self.append(RXYGate(theta, phi), [qubit])
|
|
398
|
+
|
|
399
|
+
def h(self, qubit):
|
|
400
|
+
from mb_client.circuit.gates import HGate
|
|
401
|
+
return self.append(HGate(), [qubit])
|
|
402
|
+
|
|
403
|
+
def cx(self, control, target):
|
|
404
|
+
from mb_client.circuit.gates import CXGate
|
|
405
|
+
return self.append(CXGate(), [control, target])
|
|
406
|
+
|
|
407
|
+
def cz(self, control, target):
|
|
408
|
+
from mb_client.circuit.gates import CZGate
|
|
409
|
+
return self.append(CZGate(), [control, target])
|
|
410
|
+
|
|
411
|
+
def reset(self, *qubits, align: bool | None = None, **calibration):
|
|
412
|
+
"""Reset scalar, positional, or list operands; omit operands for all qubits.
|
|
413
|
+
|
|
414
|
+
An aligned reset brackets the individual reset instructions with
|
|
415
|
+
barriers. The leading barrier makes every selected qubit start its
|
|
416
|
+
reset at the same time; the trailing barrier prevents subsequent
|
|
417
|
+
instructions from starting until every reset has finished.
|
|
418
|
+
"""
|
|
419
|
+
from mb_client.circuit.operations import Reset
|
|
420
|
+
if len(qubits) == 1 and isinstance(qubits[0], (list, tuple)):
|
|
421
|
+
qubits = tuple(qubits[0])
|
|
422
|
+
targets = tuple(
|
|
423
|
+
self._resolve_qubit(q) for q in (qubits or tuple(self.qubits)))
|
|
424
|
+
if len(set(targets)) != len(targets):
|
|
425
|
+
raise ValueError("reset operands must not contain duplicates")
|
|
426
|
+
if align is None:
|
|
427
|
+
align = len(targets) > 1
|
|
428
|
+
if align:
|
|
429
|
+
self.barrier(*targets)
|
|
430
|
+
resets = tuple(
|
|
431
|
+
self.append(Reset(**calibration), [qubit]) for qubit in targets)
|
|
432
|
+
if align:
|
|
433
|
+
self.barrier(*targets)
|
|
434
|
+
return resets[0] if len(resets) == 1 else resets
|
|
435
|
+
|
|
436
|
+
def _resolve_qubit(self, bit: int | Qubit) -> Qubit:
|
|
437
|
+
resolved = self.qubits[bit] if isinstance(bit, int) else bit
|
|
438
|
+
if resolved not in self.qubits:
|
|
439
|
+
raise ValueError(f"Qubit {bit!r} is not in this circuit")
|
|
440
|
+
return resolved
|
|
441
|
+
|
|
442
|
+
def _resolve_clbit(self, bit: int | Clbit) -> Clbit:
|
|
443
|
+
resolved = self.clbits[bit] if isinstance(bit, int) else bit
|
|
444
|
+
if resolved not in self.clbits:
|
|
445
|
+
raise ValueError(f"Clbit {bit!r} is not in this circuit")
|
|
446
|
+
return resolved
|
|
447
|
+
|
|
448
|
+
def __iter__(self) -> Iterable[CircuitInstruction]:
|
|
449
|
+
return iter(self.data)
|
|
450
|
+
|
|
451
|
+
def __len__(self) -> int:
|
|
452
|
+
return len(self.data)
|
|
453
|
+
|
|
454
|
+
def draw(self, format: str = "text", style=None, *, show: bool = False):
|
|
455
|
+
"""Draw the circuit as text or a Matplotlib figure.
|
|
456
|
+
|
|
457
|
+
Set ``show=True`` to emit the drawing immediately, which is useful for
|
|
458
|
+
calls made inside a notebook loop. The default keeps rendering free of
|
|
459
|
+
display side effects, so assigned figures can be saved or composed.
|
|
460
|
+
"""
|
|
461
|
+
from mb_client.visualization.circuit import draw_circuit
|
|
462
|
+
drawing = draw_circuit(self, format=format, style=style)
|
|
463
|
+
if show:
|
|
464
|
+
self._display_drawing(drawing)
|
|
465
|
+
return drawing
|
|
466
|
+
|
|
467
|
+
@staticmethod
|
|
468
|
+
def _display_drawing(drawing) -> None:
|
|
469
|
+
"""Display a drawing in IPython, with a terminal-safe fallback."""
|
|
470
|
+
try:
|
|
471
|
+
from IPython.display import display
|
|
472
|
+
except ImportError:
|
|
473
|
+
if isinstance(drawing, str):
|
|
474
|
+
print(drawing)
|
|
475
|
+
else:
|
|
476
|
+
import matplotlib.pyplot as plt
|
|
477
|
+
plt.show()
|
|
478
|
+
else:
|
|
479
|
+
display(drawing)
|
|
480
|
+
|
|
481
|
+
def __str__(self) -> str:
|
|
482
|
+
return self.draw(format="text")
|