Skip to content

Commit 2bf45ad

Browse files
committed
Merge remote-tracking branch 'base/main' into add_reset
2 parents e0f23ac + 80c072f commit 2bf45ad

File tree

12 files changed

+306
-14
lines changed

12 files changed

+306
-14
lines changed

bqskit/compiler/compile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1014,7 +1014,7 @@ def build_multi_qudit_retarget_workflow(
10141014
),
10151015
RestoreModelConnevtivityPass(),
10161016
],
1017-
AutoRebase2QuditGatePass(3, 5),
1017+
AutoRebase2QuditGatePass(3, 5, synthesis_epsilon),
10181018
),
10191019
ScanningGateRemovalPass(
10201020
success_threshold=synthesis_epsilon,

bqskit/ext/qiskit/translate.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
if TYPE_CHECKING:
66
from qiskit import QuantumCircuit
77

8+
from qiskit import qasm2
89
from bqskit.ir.circuit import Circuit
910
from bqskit.ir.lang.qasm2 import OPENQASM2Language
1011

1112

1213
def qiskit_to_bqskit(qc: QuantumCircuit) -> Circuit:
1314
"""Convert Qiskit's QuantumCircuit `qc` to a BQSKit Circuit."""
14-
circuit = OPENQASM2Language().decode(qc.qasm())
15+
circuit = OPENQASM2Language().decode(qasm2.dumps(qc))
1516
# circuit.renumber_qudits(list(reversed(range(circuit.num_qudits))))
1617
return circuit
1718
# TODO: support gates not captured by qasm

bqskit/ir/gates/composed/controlled.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,33 @@ def __init__(
286286
ctrl_U = np.kron(self.ctrl, U) + self.ihalf
287287
self._utry = UnitaryMatrix(ctrl_U, self.radixes)
288288

289+
@property
290+
def qasm_name(self) -> str:
291+
"""
292+
Override default `Gate.qasm_name` method.
293+
294+
If the core gate is a standard gate, this function will output
295+
qasm in the form 'c+<gate_qasm>'. Otherwise an error will be raised.
296+
297+
Raises:
298+
ValueError: If the core gate is non-standard in OpenQASM 2.0.
299+
"""
300+
_core_gate = self.gate.qasm_name
301+
if self.num_controls <= 2:
302+
_controls = 'c' * self.num_controls
303+
else:
304+
_controls = f'c{self.num_controls}'
305+
qasm_name = _controls + _core_gate
306+
supported_gates = ('cu1', 'cu2', 'cu3', 'cswap', 'c3x', 'c4x')
307+
if qasm_name not in supported_gates:
308+
raise ValueError(
309+
f'Controlled gate {_core_gate} with {self.num_controls} '
310+
'controls is not a standard OpenQASM 2.0 identifier. '
311+
'To encode this gate, try decomposing it into gates with'
312+
'standard identifiers.',
313+
)
314+
return qasm_name
315+
289316
def get_unitary(self, params: RealVector = []) -> UnitaryMatrix:
290317
"""Return the unitary for this gate, see :class:`Unitary` for more."""
291318
if hasattr(self, '_utry'):

bqskit/passes/retarget/auto.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from bqskit.ir.opt.cost.functions import HilbertSchmidtResidualsGenerator
1010
from bqskit.ir.opt.cost.generator import CostFunctionGenerator
1111
from bqskit.passes.retarget.two import Rebase2QuditGatePass
12+
from bqskit.qis.unitary import UnitaryMatrix
1213
from bqskit.runtime import get_runtime
1314
from bqskit.utils.typing import is_integer
1415
from bqskit.utils.typing import is_real_number
@@ -112,6 +113,13 @@ async def run(self, circuit: Circuit, data: PassData) -> None:
112113

113114
target = self.get_target(circuit, data)
114115

116+
if isinstance(target, UnitaryMatrix):
117+
identity = UnitaryMatrix.identity(target.dim, target.radixes)
118+
if target.get_distance_from(identity) < self.success_threshold:
119+
_logger.debug('Target is identity, returning empty circuit.')
120+
circuit.clear()
121+
return
122+
115123
for g in old_gates:
116124
# Track retries to check for no progress
117125
num_retries = 0

bqskit/passes/retarget/two.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from bqskit.ir.opt.cost.functions import HilbertSchmidtResidualsGenerator
1414
from bqskit.ir.opt.cost.generator import CostFunctionGenerator
1515
from bqskit.ir.point import CircuitPoint as Point
16+
from bqskit.qis.unitary import UnitaryMatrix
1617
from bqskit.runtime import get_runtime
1718
from bqskit.utils.typing import is_integer
1819
from bqskit.utils.typing import is_real_number
@@ -164,6 +165,13 @@ async def run(self, circuit: Circuit, data: PassData) -> None:
164165

165166
target = self.get_target(circuit, data)
166167

168+
if isinstance(target, UnitaryMatrix):
169+
identity = UnitaryMatrix.identity(target.dim, target.radixes)
170+
if target.get_distance_from(identity) < self.success_threshold:
171+
_logger.debug('Target is identity, returning empty circuit.')
172+
circuit.clear()
173+
return
174+
167175
for g in self.gates:
168176
# Track retries to check for no progress
169177
num_retries = 0
@@ -188,6 +196,7 @@ async def run(self, circuit: Circuit, data: PassData) -> None:
188196
circuit.point(g),
189197
self.gates,
190198
)
199+
191200
circuits_with_new_gate = []
192201
for circ in self.circs:
193202
circuit_copy = circuit.copy()

bqskit/passes/search/generators/seed.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(
2727
seed: Circuit | Sequence[Circuit],
2828
forward_generator: LayerGenerator = SimpleLayerGenerator(),
2929
num_removed: int = 1,
30+
hash_on_1q_gate: bool = False,
3031
) -> None:
3132
"""
3233
Construct a SeedLayerGenerator.
@@ -43,6 +44,13 @@ def __init__(
4344
backwards traversal of the synthesis tree will be done.
4445
(Default: 1)
4546
47+
hash_on_1q_gate (bool): If True, 1 qubit gates that appear
48+
in circuit templates will be used in circuit hash
49+
calculations. Otherwise only multi-qubit gates will be
50+
used. Setting this value to False can help stability
51+
when using more complex LayerGenerators for the
52+
forward_generator. (Default: False)
53+
4654
Raises:
4755
ValueError: If 'num_removed' is negative.
4856
"""
@@ -57,6 +65,11 @@ def __init__(
5765
'Expected integer for num_removed, '
5866
f'got {type(num_removed)}.',
5967
)
68+
if not isinstance(hash_on_1q_gate, bool):
69+
raise TypeError(
70+
'Expected bool for hash_on_1q_gate, '
71+
f'got {type(hash_on_1q_gate)}.',
72+
)
6073

6174
if num_removed < 0:
6275
raise ValueError(
@@ -83,6 +96,7 @@ def __init__(
8396
self.seeds = list(cast(Sequence[Circuit], seed))
8497
self.forward_generator = forward_generator
8598
self.num_removed = num_removed
99+
self.hash_on_1q_gate = hash_on_1q_gate
86100

87101
def gen_initial_layer(
88102
self,
@@ -122,7 +136,7 @@ def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
122136

123137
# If circuit is empty Circuit, successors are the seeds
124138
if circuit.is_empty:
125-
circ_hash = self.hash_structure(circuit)
139+
circ_hash = self.hash_structure(circuit, self.hash_on_1q_gate)
126140
# Do not return seeds if empty circuit already visited
127141
if circ_hash in data['seed_seen_before']:
128142
return []
@@ -134,7 +148,8 @@ def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
134148
if circuit.radixes == seed.radixes
135149
]
136150
for seed in usable_seeds:
137-
data['seed_seen_before'].add(self.hash_structure(seed))
151+
h = self.hash_structure(seed, self.hash_on_1q_gate)
152+
data['seed_seen_before'].add(h)
138153

139154
if len(usable_seeds) == 0:
140155
_logger.warning(
@@ -157,17 +172,28 @@ def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
157172
# Filter out successors that have already been visited
158173
filtered_successors = []
159174
for s in successors:
160-
h = self.hash_structure(s)
175+
h = self.hash_structure(s, self.hash_on_1q_gate)
161176
if h not in data['seed_seen_before']:
162177
data['seed_seen_before'].add(h)
163178
filtered_successors.append(s)
164179

165180
return filtered_successors
166181

167182
@staticmethod
168-
def hash_structure(circuit: Circuit) -> int:
183+
def hash_structure(circuit: Circuit, hash_on_1q_gate: bool = True) -> int:
184+
"""
185+
Compute a hash of the Circuit's structure.
186+
187+
Args:
188+
circuit (Circuit): Circuit to be hashed.
189+
190+
hash_on_1q_gate (bool): Whether or not to include 1 qubit
191+
gates in the hash computation. (Default: True)
192+
"""
169193
hashes = []
170194
for cycle, op in circuit.operations_with_cycles():
195+
if not hash_on_1q_gate and op.num_qudits <= 1:
196+
continue
171197
hashes.append(hash((cycle, str(op))))
172198
if len(hashes) > 100:
173199
hashes = [sum(hashes)]

bqskit/runtime/__init__.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,46 @@ def cancel(self, future: RuntimeFuture) -> None:
162162
"""
163163
...
164164

165+
def get_cache(self) -> dict[str, Any]:
166+
"""
167+
Retrieve worker's local cache.
168+
169+
In situations where a large or non-easily serializable object is
170+
needed during the execution of a custom pass, `get_cache` can be used
171+
to load and store data. Objects placed in cache are transient. Objects
172+
can be placed in cache to reduce reloading, but should not be expected
173+
to remain in cache forever.
174+
175+
For example, say some CustomPassA needs a SomeBigScaryObject. Within
176+
its run() method, the object is constructed, then placed into cache.
177+
A later pass, CustomPassB, also needs to use SomeBigScaryObject. It
178+
checks that the object has been loaded by CustomPassA, then uses it.
179+
180+
```
181+
# Load helper method
182+
def load_or_retrieve_big_scary_object(file):
183+
worker_cache = get_runtime().get_cache()
184+
if 'big_scary_object' not in worker_cache:
185+
# Expensive io operation...
186+
big_scary_object = load_big_scary_object(file)
187+
worker_cache['big_scary_object'] = big_scary_object
188+
big_scary_object = worker_cache['big_scary_object']
189+
return big_scary_object
190+
191+
# In CustomPassA's .run() definition...
192+
# CustomPassA performs the load.
193+
big_scary_object = load_or_retrieve_big_scary_object(file)
194+
big_scary_object.use(...)
195+
196+
# In CustomPassB's .run() definition...
197+
# CustomPassB saves time by retrieving big_scary_object from
198+
# cache after CustomPassA has loaded it.
199+
big_scary_object = load_or_retrieve_big_scary_object(file)
200+
unpicklable_object.use(...)
201+
```
202+
"""
203+
...
204+
165205
async def next(self, future: RuntimeFuture) -> list[tuple[int, Any]]:
166206
"""
167207
Wait for and return the next batch of results from a map task.

bqskit/runtime/worker.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@ def __init__(self, id: int, conn: Connection) -> None:
205205
self._mailbox_counter = 0
206206
"""This count ensures every mailbox has a unique id."""
207207

208+
self._cache: dict[str, Any] = {}
209+
"""Local worker cache."""
210+
208211
# Send out every emitted log message upstream
209212
old_factory = logging.getLogRecordFactory()
210213

@@ -563,6 +566,20 @@ def cancel(self, future: RuntimeFuture) -> None:
563566
msgs = [(RuntimeMessage.CANCEL, addr) for addr in addrs]
564567
self._outgoing.extend(msgs)
565568

569+
def get_cache(self) -> dict[str, Any]:
570+
"""
571+
Retrieve worker's local cache.
572+
573+
Returns:
574+
(dict[str, Any]): The worker's local cache. This cache can be
575+
used to store large or unserializable objects within a
576+
worker process' memory. Passes on the same worker that use
577+
the same object can load the object from this cache. If
578+
there are multiple workers, those workers will load their
579+
own copies of the object into their own cache.
580+
"""
581+
return self._cache
582+
566583
async def next(self, future: RuntimeFuture) -> list[tuple[int, Any]]:
567584
"""
568585
Wait for and return the next batch of results from a map task.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from __future__ import annotations
2+
3+
from bqskit.compiler.compile import compile
4+
from bqskit.compiler.compiler import Compiler
5+
from bqskit.ir.lang.qasm2 import OPENQASM2Language
6+
7+
8+
def test_cry_identity_corner_case(compiler: Compiler) -> None:
9+
qasm = """
10+
OPENQASM 2.0;
11+
include "qelib1.inc";
12+
qreg q[5];
13+
creg meas[5];
14+
cry(0) q[0],q[2];
15+
cry(0) q[1],q[2];
16+
"""
17+
circuit = OPENQASM2Language().decode(qasm)
18+
_ = compile(circuit, optimization_level=1, seed=10)
19+
# Should finish

tests/ext/test_qiskit.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,21 @@ def bqskit_circuit(self) -> Circuit:
4444
@pytest.fixture
4545
def qiskit_circuit(self) -> QuantumCircuit:
4646
circuit = QuantumCircuit(3)
47-
circuit.cnot(0, 1)
47+
circuit.cx(0, 1)
4848
circuit.u(1, 2, 3, 0)
4949
circuit.u(1, 2, 3, 1)
5050
circuit.u(1, 2, 3, 2)
51-
circuit.cnot(0, 1)
52-
circuit.cnot(0, 2)
53-
circuit.cnot(0, 2)
51+
circuit.cx(0, 1)
52+
circuit.cx(0, 2)
53+
circuit.cx(0, 2)
5454
circuit.u(1, 2.4, 3, 0)
5555
circuit.u(1, 2.2, 3, 1)
5656
circuit.u(1, 2.1, 3, 2)
5757
circuit.u(1, 2.1, 3, 2)
58-
circuit.cnot(0, 2)
59-
circuit.cnot(0, 2)
60-
circuit.cnot(0, 1)
61-
circuit.cnot(0, 2)
58+
circuit.cx(0, 2)
59+
circuit.cx(0, 2)
60+
circuit.cx(0, 1)
61+
circuit.cx(0, 2)
6262
circuit.u(1, 2.4, 3, 0)
6363
circuit.u(1, 2.2, 3, 1)
6464
circuit.u(1, 2.1, 3, 2)

0 commit comments

Comments
 (0)