Skip to content

Commit 92ab11e

Browse files
authored
Use greedy_topological_sort in qubit counting (#1641)
Fixes #1636
1 parent 4a39b42 commit 92ab11e

File tree

4 files changed

+155
-3
lines changed

4 files changed

+155
-3
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Copyright 2025 Google LLC
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+
from typing import Dict
15+
16+
import attrs
17+
18+
from qualtran import Bloq, BloqBuilder, Signature, Soquet, SoquetT
19+
from qualtran.bloqs.basic_gates import CNOT, TGate
20+
21+
22+
@attrs.frozen
23+
class TestManyAllocOnce(Bloq):
24+
"""Allocate an ancilla once, and re-use it explicitly
25+
26+
See qualtran.resource.counting._qubit_counts_test:test_many_alloc
27+
"""
28+
29+
n: int
30+
31+
@property
32+
def signature(self) -> Signature:
33+
return Signature.build(x=self.n)
34+
35+
def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'Soquet') -> dict[str, 'SoquetT']:
36+
x = bb.split(x)
37+
anc = bb.allocate()
38+
for i in range(self.n):
39+
x[i], anc = bb.add(CNOT(), ctrl=x[i], target=anc)
40+
anc = bb.add(TGate(), q=anc)
41+
x[i], anc = bb.add(CNOT(), ctrl=x[i], target=anc)
42+
bb.free(anc)
43+
return {'x': bb.join(x)}
44+
45+
46+
@attrs.frozen
47+
class TestManyAllocMany(Bloq):
48+
"""Allocate a new ancilla for each pseudo-subcall.
49+
50+
See qualtran.resource.counting._qubit_counts_test:test_many_alloc
51+
"""
52+
53+
n: int
54+
55+
@property
56+
def signature(self) -> Signature:
57+
return Signature.build(x=self.n)
58+
59+
def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'Soquet') -> dict[str, 'SoquetT']:
60+
x = bb.split(x)
61+
for i in range(self.n):
62+
anc = bb.allocate()
63+
x[i], anc = bb.add(CNOT(), ctrl=x[i], target=anc)
64+
anc = bb.add(TGate(), q=anc)
65+
x[i], anc = bb.add(CNOT(), ctrl=x[i], target=anc)
66+
bb.free(anc)
67+
return {'x': bb.join(x)}
68+
69+
70+
@attrs.frozen
71+
class _Inner(Bloq):
72+
"""Inner part, used by `TestManyAllocAbstracted`"""
73+
74+
@property
75+
def signature(self) -> Signature:
76+
return Signature.build(x=1)
77+
78+
def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'Soquet') -> Dict[str, 'SoquetT']:
79+
anc = bb.allocate()
80+
x, anc = bb.add(CNOT(), ctrl=x, target=anc)
81+
anc = bb.add(TGate(), q=anc)
82+
x, anc = bb.add(CNOT(), ctrl=x, target=anc)
83+
bb.free(anc)
84+
return {'x': x}
85+
86+
87+
@attrs.frozen
88+
class TestManyAllocAbstracted(Bloq):
89+
"""Factor allocation into subbloq
90+
91+
See qualtran.resource.counting._qubit_counts_test:test_many_alloc
92+
"""
93+
94+
n: int
95+
96+
@property
97+
def signature(self) -> Signature:
98+
return Signature.build(x=self.n)
99+
100+
def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'Soquet') -> dict[str, 'SoquetT']:
101+
x = bb.split(x)
102+
for i in range(self.n):
103+
x[i] = bb.add(_Inner(), x=x[i])
104+
return {'x': bb.join(x)}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright 2025 Google LLC
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+
import qualtran.testing as qlt_testing
15+
from qualtran.bloqs.for_testing.qubit_count_many_alloc import (
16+
TestManyAllocAbstracted,
17+
TestManyAllocMany,
18+
TestManyAllocOnce,
19+
)
20+
21+
22+
def test_many_alloc_validity():
23+
qlt_testing.assert_valid_bloq_decomposition(TestManyAllocMany(10))
24+
qlt_testing.assert_valid_bloq_decomposition(TestManyAllocOnce(10))
25+
qlt_testing.assert_valid_bloq_decomposition(TestManyAllocAbstracted(10))
26+
qlt_testing.assert_valid_cbloq(TestManyAllocAbstracted(10).as_composite_bloq().flatten())

qualtran/resource_counting/_qubit_counts.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,16 @@
1818
import networkx as nx
1919
from attrs import frozen
2020

21-
from qualtran import Bloq, Connection, DanglingT, DecomposeNotImplementedError, DecomposeTypeError
22-
from qualtran._infra.composite_bloq import _binst_to_cxns, CompositeBloq
21+
from qualtran import (
22+
Bloq,
23+
CompositeBloq,
24+
Connection,
25+
DanglingT,
26+
DecomposeNotImplementedError,
27+
DecomposeTypeError,
28+
)
29+
from qualtran._infra.binst_graph_iterators import greedy_topological_sort
30+
from qualtran._infra.composite_bloq import _binst_to_cxns
2331
from qualtran.symbolics import smax, SymbolicInt
2432

2533
from ._call_graph import get_bloq_callee_counts
@@ -46,7 +54,7 @@ def _cbloq_max_width(
4654
in_play: Set[Connection] = set()
4755

4856
for cc in nx.weakly_connected_components(binst_graph):
49-
for binst in nx.topological_sort(binst_graph.subgraph(cc)):
57+
for binst in greedy_topological_sort(binst_graph.subgraph(cc)):
5058
pred_cxns, succ_cxns = _binst_to_cxns(binst, binst_graph=binst_graph)
5159

5260
# Remove inbound connections from those that are 'in play'.

qualtran/resource_counting/_qubit_counts_test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ def test_on_cbloq():
8181
assert n_qubits == 3 * n
8282

8383

84+
def test_many_alloc():
85+
from qualtran.bloqs.for_testing.qubit_count_many_alloc import (
86+
TestManyAllocAbstracted,
87+
TestManyAllocMany,
88+
TestManyAllocOnce,
89+
)
90+
91+
n = 10
92+
for bloq in [TestManyAllocMany(n), TestManyAllocOnce(n), TestManyAllocAbstracted(n)]:
93+
# These should all give n+1 despite their unique constructions
94+
# https://github.com/quantumlib/Qualtran/issues/1636
95+
assert get_cost_value(bloq, QubitCount()) == 11
96+
97+
8498
@pytest.mark.notebook
8599
def test_notebook():
86100
qlt_testing.execute_notebook("qubit_counts")

0 commit comments

Comments
 (0)