From cfda75d5df32a6579784775a785a97a683861bf5 Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Sun, 30 Jun 2024 15:21:43 -0700 Subject: [PATCH 1/8] Greedy topological sort of the binst graph to minimize qubit allocations / deallocations --- qualtran/_infra/binst_graph_iterators.py | 98 +++++++++++++++++++ qualtran/_infra/binst_graph_iterators_test.py | 63 ++++++++++++ qualtran/_infra/composite_bloq.py | 3 +- qualtran/cirq_interop/_bloq_to_cirq.py | 32 +++--- qualtran/cirq_interop/_bloq_to_cirq_test.py | 22 +++++ 5 files changed, 199 insertions(+), 19 deletions(-) create mode 100644 qualtran/_infra/binst_graph_iterators.py create mode 100644 qualtran/_infra/binst_graph_iterators_test.py diff --git a/qualtran/_infra/binst_graph_iterators.py b/qualtran/_infra/binst_graph_iterators.py new file mode 100644 index 000000000..83eeab9c8 --- /dev/null +++ b/qualtran/_infra/binst_graph_iterators.py @@ -0,0 +1,98 @@ +# Copyright 2024 Google LLC +# +# 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. + +import heapq +from collections import Counter +from typing import Dict, Iterator, List, TYPE_CHECKING + +import attrs +import networkx as nx + +if TYPE_CHECKING: + from qualtran import BloqInstance + +_INFINITY: int = int(1e18) + + +def _priority(node: 'BloqInstance') -> int: + from qualtran._infra.gate_with_registers import total_bits + from qualtran._infra.quantum_graph import DanglingT + from qualtran.bloqs.bookkeeping import Allocate, Free + + if isinstance(node, DanglingT): + return 0 + + if node.bloq_is(Allocate): + return _INFINITY + + if node.bloq_is(Free): + return -_INFINITY + + signature = node.bloq.signature + return total_bits(signature.rights()) - total_bits(signature.lefts()) + + +@attrs.frozen(order=True) +class PrioritizedItem: + item: 'BloqInstance' = attrs.field(eq=_priority, order=_priority) + priority: int + + +def greedy_topological_sort(binst_graph: nx.DiGraph) -> Iterator['BloqInstance']: + """Stable greedy topological sorting for the bloq instance graph. + + Topological sorting for the Bloq Instances graph which maintains a priority queue + instead of a queue. Priority for each bloq is a tuple of the form + (_priority(bloq), insertion_index); where each term corresponds to + + ### Priority of a bloq + - 0: For Left / Right Dangling bloqs. + - +Infinity / -Infinity: For `Allocate` / `Free` bloqs. + - total_bits(right registers) - total_bits(left registers): For all other bloqs. + + ### Insertion Index + `insertion_index` is a unique ID used to breaks ties between bloqs that have the + same priority and follows from the order of nodes inserted in the networkx Graph. + + The stability condition guarantees that two networkx graphs constructed with + identical ordering of Graph.nodes and Graph.edges will have the same topological + sorting. + + Args: + binst_graph: A networkx DiGraph with `BloqInstances` as nodes. Usually obtained + from `cbloq._binst_graph` where `cbloq` is a `CompositeBloq`. + + Yields: + Nodes from the input graph returned in a greedy topological sorted order with the + goal to minimize qubit allocations and deallocations by pushing allocations to the + right and de-allocations to the left. + """ + heap: List[PrioritizedItem] = [] + idx: int = 0 + in_degree: Dict[BloqInstance, int] = Counter() + + for x in binst_graph.nodes(): + in_degree[x] = binst_graph.in_degree(x) + if not in_degree[x]: + heapq.heappush(heap, PrioritizedItem(x, idx)) + idx = idx + 1 + + while heap: + x = heapq.heappop(heap).item + yield x + for y in binst_graph.neighbors(x): + in_degree[y] -= 1 + if not in_degree[y]: + heapq.heappush(heap, PrioritizedItem(y, idx)) + idx = idx + 1 diff --git a/qualtran/_infra/binst_graph_iterators_test.py b/qualtran/_infra/binst_graph_iterators_test.py new file mode 100644 index 000000000..58dbb13c8 --- /dev/null +++ b/qualtran/_infra/binst_graph_iterators_test.py @@ -0,0 +1,63 @@ +# Copyright 2024 Google LLC +# +# 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 attrs import frozen + +from qualtran import ( + Bloq, + BloqBuilder, + BloqInstance, + LeftDangle, + QAny, + QBit, + RightDangle, + Signature, + SoquetT, +) +from qualtran._infra.binst_graph_iterators import greedy_topological_sort +from qualtran.bloqs.basic_gates import CNOT +from qualtran.bloqs.bookkeeping import Allocate, Free + + +@frozen +class MultiAlloc(Bloq): + rounds: int = 2 + + @property + def signature(self) -> Signature: + return Signature.build(q=1) + + def build_composite_bloq(self, bb: BloqBuilder, q: SoquetT) -> dict[str, SoquetT]: + for _ in range(self.rounds): + a = bb.allocate(1) + a, q = bb.add(CNOT(), ctrl=a, target=q) + bb.free(a) + + return {"q": q} + + +def test_greedy_topological_sort(): + bloq = MultiAlloc() + binst_graph = bloq.decompose_bloq()._binst_graph + greedy_toposort = [*greedy_topological_sort(binst_graph)] + assert greedy_toposort == [ + LeftDangle, + BloqInstance(bloq=Allocate(dtype=QAny(bitsize=1)), i=0), + BloqInstance(bloq=CNOT(), i=1), + BloqInstance(bloq=Free(dtype=QBit()), i=2), + BloqInstance(bloq=Allocate(dtype=QAny(bitsize=1)), i=3), + BloqInstance(bloq=CNOT(), i=4), + BloqInstance(bloq=Free(dtype=QBit()), i=5), + RightDangle, + ] diff --git a/qualtran/_infra/composite_bloq.py b/qualtran/_infra/composite_bloq.py index e483774ee..245e5fac4 100644 --- a/qualtran/_infra/composite_bloq.py +++ b/qualtran/_infra/composite_bloq.py @@ -40,6 +40,7 @@ import sympy from numpy.typing import NDArray +from .binst_graph_iterators import greedy_topological_sort from .bloq import Bloq, DecomposeNotImplementedError, DecomposeTypeError from .data_types import check_dtypes_consistent, QAny, QBit, QDType from .quantum_graph import BloqInstance, Connection, DanglingT, LeftDangle, RightDangle, Soquet @@ -248,7 +249,7 @@ def iter_bloqnections( a predecessor and again as a successor. """ g = self._binst_graph - for binst in nx.topological_sort(g): + for binst in greedy_topological_sort(g): if isinstance(binst, DanglingT): continue pred_cxns, succ_cxns = _binst_to_cxns(binst, binst_graph=g) diff --git a/qualtran/cirq_interop/_bloq_to_cirq.py b/qualtran/cirq_interop/_bloq_to_cirq.py index 0face5128..9ab2fd19e 100644 --- a/qualtran/cirq_interop/_bloq_to_cirq.py +++ b/qualtran/cirq_interop/_bloq_to_cirq.py @@ -34,6 +34,7 @@ Signature, Soquet, ) +from qualtran._infra.binst_graph_iterators import greedy_topological_sort from qualtran._infra.composite_bloq import _binst_to_cxns from qualtran._infra.gate_with_registers import ( _get_all_and_output_quregs_from_input, @@ -266,23 +267,18 @@ def _cbloq_to_cirq_circuit( for reg in signature.lefts() for idx in reg.all_idxs() } - moments: List[cirq.Moment] = [] - for binsts in nx.topological_generations(binst_graph): - moment: List[cirq.Operation] = [] - - for binst in binsts: - if binst is LeftDangle: - continue - pred_cxns, succ_cxns = _binst_to_cxns(binst, binst_graph=binst_graph) - if binst is RightDangle: - _track_soq_name_changes(pred_cxns, qvar_to_qreg) - continue - - op = _bloq_to_cirq_op(binst.bloq, pred_cxns, succ_cxns, qvar_to_qreg, qubit_manager) - if op is not None: - moment.append(op) - if moment: - moments.append(cirq.Moment(moment)) + ops: List[cirq.Operation] = [] + for binst in greedy_topological_sort(binst_graph): + if binst is LeftDangle: + continue + pred_cxns, succ_cxns = _binst_to_cxns(binst, binst_graph=binst_graph) + if binst is RightDangle: + _track_soq_name_changes(pred_cxns, qvar_to_qreg) + continue + + op = _bloq_to_cirq_op(binst.bloq, pred_cxns, succ_cxns, qvar_to_qreg, qubit_manager) + if op is not None: + ops.append(op) # Find output Cirq quregs using `qvar_to_qreg` mapping for registers in `signature.rights()`. def _f_quregs(reg: Register) -> CirqQuregT: @@ -294,7 +290,7 @@ def _f_quregs(reg: Register) -> CirqQuregT: out_quregs = {reg.name: _f_quregs(reg) for reg in signature.rights()} - return cirq.FrozenCircuit(moments), out_quregs + return cirq.FrozenCircuit(ops), out_quregs def _wire_symbol_to_cirq_diagram_info( diff --git a/qualtran/cirq_interop/_bloq_to_cirq_test.py b/qualtran/cirq_interop/_bloq_to_cirq_test.py index 9d9ab0f2e..2e9772b65 100644 --- a/qualtran/cirq_interop/_bloq_to_cirq_test.py +++ b/qualtran/cirq_interop/_bloq_to_cirq_test.py @@ -23,6 +23,7 @@ from qualtran.bloqs.basic_gates import Toffoli, XGate from qualtran.bloqs.factoring import ModExp from qualtran.bloqs.mcmt.and_bloq import And, MultiAnd +from qualtran.bloqs.state_preparation import PrepareUniformSuperposition from qualtran.cirq_interop._bloq_to_cirq import BloqAsCirqGate, CirqQuregT from qualtran.cirq_interop.t_complexity_protocol import t_complexity from qualtran.testing import execute_notebook @@ -148,6 +149,27 @@ def test_multi_and_allocates(): assert sorted(out_quregs.keys()) == ['ctrl', 'junk', 'target'] +def test_flat_cbloq_to_cirq_circuit_minimizes_qubit_allocation(): + bloq = PrepareUniformSuperposition(n=3, cvs=(1,)) + qm = cirq.GreedyQubitManager(prefix='anc', maximize_reuse=True) + cbloq = bloq.as_composite_bloq() + assert len(cbloq.to_cirq_circuit(qubit_manager=qm).all_qubits()) == 3 + cbloq = bloq.decompose_bloq() + assert len(cbloq.to_cirq_circuit(qubit_manager=qm).all_qubits()) == 5 + cbloq = bloq.decompose_bloq().flatten_once() + assert len(cbloq.to_cirq_circuit(qubit_manager=qm).all_qubits()) == 7 + qm = cirq.GreedyQubitManager(prefix='anc', maximize_reuse=True) + # Note: This should also be 7 but to work correctly, it relies on + # `greedy_topological_sort` iterating on allocation nodes in insertion order. + # `cbloq.flatten()` preserves this now because cbloq.iter_bloqnections is also + # updated to use `greedy_topological_sort` instead of `nx.topological_sort`. + # In general, we should have a more stable way to preserve this property, + # potentially by maintaing a sorted order in `binst.i`; + # xref: https://github.com/quantumlib/Qualtran/issues/1098 + cbloq = bloq.decompose_bloq().flatten() + assert len(cbloq.to_cirq_circuit(qubit_manager=qm).all_qubits()) == 7 + + def test_contruct_op_from_gate(): and_gate = And() in_quregs = {'ctrl': np.array([*cirq.LineQubit.range(2)]).reshape(2, 1)} From b38b230f245eaa260b030b093fb56df93bb0a507 Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Sun, 30 Jun 2024 15:26:04 -0700 Subject: [PATCH 2/8] Docstring for _PrioritizedItem --- qualtran/_infra/binst_graph_iterators.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qualtran/_infra/binst_graph_iterators.py b/qualtran/_infra/binst_graph_iterators.py index 83eeab9c8..dba247abc 100644 --- a/qualtran/_infra/binst_graph_iterators.py +++ b/qualtran/_infra/binst_graph_iterators.py @@ -44,7 +44,9 @@ def _priority(node: 'BloqInstance') -> int: @attrs.frozen(order=True) -class PrioritizedItem: +class _PrioritizedItem: + """Helper dataclass to insert items in a heap as part of `greedy_topological_sort`.""" + item: 'BloqInstance' = attrs.field(eq=_priority, order=_priority) priority: int @@ -78,14 +80,14 @@ def greedy_topological_sort(binst_graph: nx.DiGraph) -> Iterator['BloqInstance'] goal to minimize qubit allocations and deallocations by pushing allocations to the right and de-allocations to the left. """ - heap: List[PrioritizedItem] = [] + heap: List[_PrioritizedItem] = [] idx: int = 0 in_degree: Dict[BloqInstance, int] = Counter() for x in binst_graph.nodes(): in_degree[x] = binst_graph.in_degree(x) if not in_degree[x]: - heapq.heappush(heap, PrioritizedItem(x, idx)) + heapq.heappush(heap, _PrioritizedItem(x, idx)) idx = idx + 1 while heap: @@ -94,5 +96,5 @@ def greedy_topological_sort(binst_graph: nx.DiGraph) -> Iterator['BloqInstance'] for y in binst_graph.neighbors(x): in_degree[y] -= 1 if not in_degree[y]: - heapq.heappush(heap, PrioritizedItem(y, idx)) + heapq.heappush(heap, _PrioritizedItem(y, idx)) idx = idx + 1 From ae3fd59a9b140ca814cdcbc7e51d74267cebf7fb Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Mon, 1 Jul 2024 00:37:00 -0700 Subject: [PATCH 3/8] Fix failing CI --- qualtran/bloqs/chemistry/thc/thc.ipynb | 2 +- qualtran/drawing/qpic_diagram_test.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/qualtran/bloqs/chemistry/thc/thc.ipynb b/qualtran/bloqs/chemistry/thc/thc.ipynb index bebdac055..c5e42144d 100644 --- a/qualtran/bloqs/chemistry/thc/thc.ipynb +++ b/qualtran/bloqs/chemistry/thc/thc.ipynb @@ -433,7 +433,7 @@ " print(f\"{k+':':20s} qualtran = {binned_counts[k]:5d} vs paper cost = {v:5d}.\")\n", "\n", "print(f\"Total cost = {sum(v for v in binned_counts.values())}\")\n", - "assert binned_counts['data_loading'] == 304" + "assert binned_counts['data_loading'] == 296" ] }, { diff --git a/qualtran/drawing/qpic_diagram_test.py b/qualtran/drawing/qpic_diagram_test.py index 9e9c42d7e..6dfbd6fc8 100644 --- a/qualtran/drawing/qpic_diagram_test.py +++ b/qualtran/drawing/qpic_diagram_test.py @@ -53,10 +53,10 @@ def test_qpic_data_for_reflect_using_prepare(): reg_1 W off reg_2 W off reg_3 W off -reg_4 W off reg[0] W off reg[1] W off -reg_5 W off +reg_4 W off +_empty_wire G:width=65:shape=8 GPhase((-0-1j)) reg:on G:width=25:shape=box \textrm{\scalebox{0.8}{alloc}} reg / \textrm{\scalebox{0.5}{QAny(2)}} reg_1:on G:width=25:shape=box \textrm{\scalebox{0.8}{alloc}} @@ -65,20 +65,20 @@ def test_qpic_data_for_reflect_using_prepare(): reg_2 / \textrm{\scalebox{0.5}{QAny(2)}} reg_3:on G:width=25:shape=box \textrm{\scalebox{0.8}{alloc}} reg_3 / \textrm{\scalebox{0.5}{QAny(1)}} +selection G:width=121:shape=box \textrm{\scalebox{0.8}{StatePreparationAliasSampling}} reg G:width=37:shape=box \textrm{\scalebox{0.8}{sigma\_mu}} reg_1 G:width=17:shape=box \textrm{\scalebox{0.8}{alt}} reg_2 G:width=21:shape=box \textrm{\scalebox{0.8}{keep}} reg_3 G:width=65:shape=box \textrm{\scalebox{0.8}{less\_than\_equal}} +selection:off G:width=5:shape=> \textrm{\scalebox{0.8}{}} reg[0]:on G:width=17:shape=box \textrm{\scalebox{0.8}{[0]}} reg[1]:on G:width=17:shape=box \textrm{\scalebox{0.8}{[1]}} reg_4:on G:width=25:shape=box \textrm{\scalebox{0.8}{alloc}} reg_4 / \textrm{\scalebox{0.5}{QAny(1)}} -_empty_wire G:width=65:shape=8 GPhase((-0-1j)) -selection G:width=121:shape=box \textrm{\scalebox{0.8}{StatePreparationAliasSampling}} reg G:width=37:shape=box \textrm{\scalebox{0.8}{sigma\_mu}} reg_1 G:width=17:shape=box \textrm{\scalebox{0.8}{alt}} reg_2 G:width=21:shape=box \textrm{\scalebox{0.8}{keep}} reg_3 G:width=65:shape=box \textrm{\scalebox{0.8}{less\_than\_equal}} +reg_4 -selection:off G:width=5:shape=> \textrm{\scalebox{0.8}{}} reg[0]:on G:width=17:shape=box \textrm{\scalebox{0.8}{[0]}} reg[1]:on G:width=17:shape=box \textrm{\scalebox{0.8}{[1]}} reg_4 G:width=9:shape=box \textrm{\scalebox{0.8}{Z}} -reg[0] -reg[1] +reg_4 -reg[0]:off G:width=17:shape=box \textrm{\scalebox{0.8}{[0]}} reg[1]:off G:width=17:shape=box \textrm{\scalebox{0.8}{[1]}} reg_5:on G:width=5:shape=< \textrm{\scalebox{0.8}{}} -reg_5 / \textrm{\scalebox{0.5}{BoundedQUInt(2, 4)}} reg_4:off G:width=21:shape=box \textrm{\scalebox{0.8}{free}} -reg_5 G:width=121:shape=box \textrm{\scalebox{0.8}{StatePreparationAliasSampling}} reg G:width=37:shape=box \textrm{\scalebox{0.8}{sigma\_mu}} reg_1 G:width=17:shape=box \textrm{\scalebox{0.8}{alt}} reg_2 G:width=21:shape=box \textrm{\scalebox{0.8}{keep}} reg_3 G:width=65:shape=box \textrm{\scalebox{0.8}{less\_than\_equal}} +reg[0]:off G:width=17:shape=box \textrm{\scalebox{0.8}{[0]}} reg[1]:off G:width=17:shape=box \textrm{\scalebox{0.8}{[1]}} reg_4:on G:width=5:shape=< \textrm{\scalebox{0.8}{}} +reg_4 / \textrm{\scalebox{0.5}{BoundedQUInt(2, 4)}} +reg_4 G:width=121:shape=box \textrm{\scalebox{0.8}{StatePreparationAliasSampling}} reg G:width=37:shape=box \textrm{\scalebox{0.8}{sigma\_mu}} reg_1 G:width=17:shape=box \textrm{\scalebox{0.8}{alt}} reg_2 G:width=21:shape=box \textrm{\scalebox{0.8}{keep}} reg_3 G:width=65:shape=box \textrm{\scalebox{0.8}{less\_than\_equal}} reg:off G:width=21:shape=box \textrm{\scalebox{0.8}{free}} reg_1:off G:width=21:shape=box \textrm{\scalebox{0.8}{free}} reg_2:off G:width=21:shape=box \textrm{\scalebox{0.8}{free}} -reg_3:off G:width=21:shape=box \textrm{\scalebox{0.8}{free}}""", +reg_3:off G:width=21:shape=box \textrm{\scalebox{0.8}{free}} +""", ) From b3a4a92e24c0922a006ad5cfae5eac98e8ad2dd0 Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Mon, 1 Jul 2024 00:47:25 -0700 Subject: [PATCH 4/8] Undo thc.ipynb change --- qualtran/bloqs/chemistry/thc/thc.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualtran/bloqs/chemistry/thc/thc.ipynb b/qualtran/bloqs/chemistry/thc/thc.ipynb index c5e42144d..bebdac055 100644 --- a/qualtran/bloqs/chemistry/thc/thc.ipynb +++ b/qualtran/bloqs/chemistry/thc/thc.ipynb @@ -433,7 +433,7 @@ " print(f\"{k+':':20s} qualtran = {binned_counts[k]:5d} vs paper cost = {v:5d}.\")\n", "\n", "print(f\"Total cost = {sum(v for v in binned_counts.values())}\")\n", - "assert binned_counts['data_loading'] == 296" + "assert binned_counts['data_loading'] == 304" ] }, { From 1f2cf79a374cd6bbfdd1fe0d870eca2e2cf3f74e Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Mon, 1 Jul 2024 12:49:50 -0700 Subject: [PATCH 5/8] Delegate to nx.lexicographical_topological_sort --- qualtran/_infra/binst_graph_iterators.py | 39 ++++-------------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/qualtran/_infra/binst_graph_iterators.py b/qualtran/_infra/binst_graph_iterators.py index dba247abc..9cb704615 100644 --- a/qualtran/_infra/binst_graph_iterators.py +++ b/qualtran/_infra/binst_graph_iterators.py @@ -12,17 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -import heapq -from collections import Counter -from typing import Dict, Iterator, List, TYPE_CHECKING +from typing import Iterator, TYPE_CHECKING -import attrs import networkx as nx if TYPE_CHECKING: from qualtran import BloqInstance -_INFINITY: int = int(1e18) +_INFINITY: int = int(1e16) def _priority(node: 'BloqInstance') -> int: @@ -43,16 +40,8 @@ def _priority(node: 'BloqInstance') -> int: return total_bits(signature.rights()) - total_bits(signature.lefts()) -@attrs.frozen(order=True) -class _PrioritizedItem: - """Helper dataclass to insert items in a heap as part of `greedy_topological_sort`.""" - - item: 'BloqInstance' = attrs.field(eq=_priority, order=_priority) - priority: int - - def greedy_topological_sort(binst_graph: nx.DiGraph) -> Iterator['BloqInstance']: - """Stable greedy topological sorting for the bloq instance graph. + """Stable greedy topological sorting for the bloq instance graph to minimize qubit counts. Topological sorting for the Bloq Instances graph which maintains a priority queue instead of a queue. Priority for each bloq is a tuple of the form @@ -69,7 +58,8 @@ def greedy_topological_sort(binst_graph: nx.DiGraph) -> Iterator['BloqInstance'] The stability condition guarantees that two networkx graphs constructed with identical ordering of Graph.nodes and Graph.edges will have the same topological - sorting. + sorting. The method delegates to `networkx.lexicographical_topological_sort` with + the `_priority` function used as a key. Args: binst_graph: A networkx DiGraph with `BloqInstances` as nodes. Usually obtained @@ -80,21 +70,4 @@ def greedy_topological_sort(binst_graph: nx.DiGraph) -> Iterator['BloqInstance'] goal to minimize qubit allocations and deallocations by pushing allocations to the right and de-allocations to the left. """ - heap: List[_PrioritizedItem] = [] - idx: int = 0 - in_degree: Dict[BloqInstance, int] = Counter() - - for x in binst_graph.nodes(): - in_degree[x] = binst_graph.in_degree(x) - if not in_degree[x]: - heapq.heappush(heap, _PrioritizedItem(x, idx)) - idx = idx + 1 - - while heap: - x = heapq.heappop(heap).item - yield x - for y in binst_graph.neighbors(x): - in_degree[y] -= 1 - if not in_degree[y]: - heapq.heappush(heap, _PrioritizedItem(y, idx)) - idx = idx + 1 + yield from nx.lexicographical_topological_sort(binst_graph, key=_priority) From 45971d1075001af7be2890ab1a7666c8d9db25b8 Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Fri, 5 Jul 2024 17:43:31 -0700 Subject: [PATCH 6/8] Address comments, fix failing tests --- qualtran/_infra/binst_graph_iterators.py | 9 ++-- .../bloqs/swap_network/swap_with_zero_test.py | 50 ++++++++++--------- qualtran/cirq_interop/_cirq_to_bloq_test.py | 8 +-- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/qualtran/_infra/binst_graph_iterators.py b/qualtran/_infra/binst_graph_iterators.py index 9cb704615..37a398961 100644 --- a/qualtran/_infra/binst_graph_iterators.py +++ b/qualtran/_infra/binst_graph_iterators.py @@ -19,7 +19,10 @@ if TYPE_CHECKING: from qualtran import BloqInstance -_INFINITY: int = int(1e16) +_ALLOCATION_PRIORITY: int = int(1e16) +"""A large constant value to ensure that allocations are performed as late as possible +and de-allocations (with -_ALLOCATION_PRIORITY priority) are performed as early as possible. +To determine ordering among allocations, we may add a priority to this base value.""" def _priority(node: 'BloqInstance') -> int: @@ -31,10 +34,10 @@ def _priority(node: 'BloqInstance') -> int: return 0 if node.bloq_is(Allocate): - return _INFINITY + return _ALLOCATION_PRIORITY if node.bloq_is(Free): - return -_INFINITY + return -_ALLOCATION_PRIORITY signature = node.bloq.signature return total_bits(signature.rights()) - total_bits(signature.lefts()) diff --git a/qualtran/bloqs/swap_network/swap_with_zero_test.py b/qualtran/bloqs/swap_network/swap_with_zero_test.py index 87fcdfee8..b788988ca 100644 --- a/qualtran/bloqs/swap_network/swap_with_zero_test.py +++ b/qualtran/bloqs/swap_network/swap_with_zero_test.py @@ -102,41 +102,42 @@ def test_swap_with_zero_cirq_gate_diagram(): def test_swap_with_zero_cirq_gate_diagram_multi_dim(): gate = SwapWithZero((2, 1), 2, (3, 2)) gh = cq_testing.GateHelper(gate) + # Bloq -> Cirq conversion preserves insertion ordering when all operations are THRU + # operations cirq.testing.assert_has_diagram( cirq.Circuit(gh.operation, cirq.decompose_once(gh.operation)), """ ┌──────────────────┐ selection0_0: ───────@(r⇋0)────────────────────────────────────────────────────@(approx)─── │ │ -selection0_1: ───────@(r⇋0)──────────────────────────────@(approx)─────────────┼─────────── - │ │ │ -selection1_: ────────@(r⇋0)─────@(approx)───@(approx)────┼────────@(approx)────┼─────────── +selection0_1: ───────@(r⇋0)───────────────────────────────────────@(approx)────┼─────────── + │ │ │ +selection1_: ────────@(r⇋0)─────@(approx)───@(approx)────@(approx)┼────────────┼─────────── │ │ │ │ │ │ -targets[0, 0][0]: ───swap_0_0───×(x)────────┼────────────×(x)─────┼────────────×(x)──────── +targets[0, 0][0]: ───swap_0_0───×(x)────────┼────────────┼────────×(x)─────────×(x)──────── │ │ │ │ │ │ -targets[0, 0][1]: ───swap_0_0───×(x)────────┼────────────×(x)─────┼────────────×(x)──────── +targets[0, 0][1]: ───swap_0_0───×(x)────────┼────────────┼────────×(x)─────────×(x)──────── │ │ │ │ │ │ targets[0, 1][0]: ───swap_0_1───×(y)────────┼────────────┼────────┼────────────┼─────────── │ │ │ │ │ │ targets[0, 1][1]: ───swap_0_1───×(y)────────┼────────────┼────────┼────────────┼─────────── │ │ │ │ │ -targets[1, 0][0]: ───swap_1_0───────────────×(x)─────────×(y)─────┼────────────┼─────────── +targets[1, 0][0]: ───swap_1_0───────────────×(x)─────────┼────────×(y)─────────┼─────────── │ │ │ │ │ -targets[1, 0][1]: ───swap_1_0───────────────×(x)─────────×(y)─────┼────────────┼─────────── - │ │ │ │ -targets[1, 1][0]: ───swap_1_1───────────────×(y)──────────────────┼────────────┼─────────── - │ │ │ │ -targets[1, 1][1]: ───swap_1_1───────────────×(y)──────────────────┼────────────┼─────────── - │ │ │ -targets[2, 0][0]: ───swap_2_0─────────────────────────────────────×(x)─────────×(y)──────── - │ │ │ -targets[2, 0][1]: ───swap_2_0─────────────────────────────────────×(x)─────────×(y)──────── - │ │ -targets[2, 1][0]: ───swap_2_1─────────────────────────────────────×(y)───────────────────── - │ │ -targets[2, 1][1]: ───swap_2_1─────────────────────────────────────×(y)───────────────────── - └──────────────────┘ -""", +targets[1, 0][1]: ───swap_1_0───────────────×(x)─────────┼────────×(y)─────────┼─────────── + │ │ │ │ +targets[1, 1][0]: ───swap_1_1───────────────×(y)─────────┼─────────────────────┼─────────── + │ │ │ │ +targets[1, 1][1]: ───swap_1_1───────────────×(y)─────────┼─────────────────────┼─────────── + │ │ │ +targets[2, 0][0]: ───swap_2_0────────────────────────────×(x)──────────────────×(y)──────── + │ │ │ +targets[2, 0][1]: ───swap_2_0────────────────────────────×(x)──────────────────×(y)──────── + │ │ +targets[2, 1][0]: ───swap_2_1────────────────────────────×(y)────────────────────────────── + │ │ +targets[2, 1][1]: ───swap_2_1────────────────────────────×(y)────────────────────────────── + └──────────────────┘""", ) @@ -144,9 +145,10 @@ def test_swap_with_zero_classically(): data = np.array([131, 255, 92, 2]) swz = SwapWithZero(selection_bitsizes=2, target_bitsize=8, n_target_registers=4) - for sel in range(2**2): - sel, out_data = swz.call_classically(selection=sel, targets=data) # type: ignore[assignment] - print(sel, out_data) + for sel_in in range(2**2): + sel_out, out_data = swz.call_classically(selection=sel_in, targets=data) # type: ignore[assignment] + assert sel_in == sel_out + assert out_data[0] == data[sel_in] @pytest.mark.parametrize( diff --git a/qualtran/cirq_interop/_cirq_to_bloq_test.py b/qualtran/cirq_interop/_cirq_to_bloq_test.py index 64a7c3087..c4b62fd31 100644 --- a/qualtran/cirq_interop/_cirq_to_bloq_test.py +++ b/qualtran/cirq_interop/_cirq_to_bloq_test.py @@ -162,9 +162,11 @@ def signature(self) -> Signature: cbloq = cirq_optree_to_cbloq(circuit) assert cbloq.signature == qualtran.Signature([qualtran.Register('qubits', QBit(), shape=(28,))]) bloq_instances = [binst for binst, _, _ in cbloq.iter_bloqnections()] - assert all(bloq_instances[i].bloq == Join(QAny(2)) for i in range(14)) - assert bloq_instances[14].bloq == CirqGateWithRegisters(reg1) - assert bloq_instances[14].bloq.signature == qualtran.Signature( + # Greedy iteration of iter_bloqnections first joins only qubits needed + # for the first gate. + assert all(bloq_instances[i].bloq == Join(QAny(2)) for i in range(12)) + assert bloq_instances[12].bloq == CirqGateWithRegisters(reg1) + assert bloq_instances[12].bloq.signature == qualtran.Signature( [qualtran.Register('x', QAny(bitsize=2), shape=(3, 4))] ) assert bloq_instances[15].bloq == CirqGateWithRegisters(anc_reg) From c74b57f2dc8c854d999c0ae9b15f59e91d9cb182 Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Fri, 5 Jul 2024 17:45:23 -0700 Subject: [PATCH 7/8] Fix mypy --- qualtran/bloqs/swap_network/swap_with_zero_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qualtran/bloqs/swap_network/swap_with_zero_test.py b/qualtran/bloqs/swap_network/swap_with_zero_test.py index b788988ca..c5a7c2abc 100644 --- a/qualtran/bloqs/swap_network/swap_with_zero_test.py +++ b/qualtran/bloqs/swap_network/swap_with_zero_test.py @@ -148,6 +148,7 @@ def test_swap_with_zero_classically(): for sel_in in range(2**2): sel_out, out_data = swz.call_classically(selection=sel_in, targets=data) # type: ignore[assignment] assert sel_in == sel_out + assert isinstance(out_data, np.ndarray) assert out_data[0] == data[sel_in] From 12c352827409af39dce5cb925ddac036ca80c86d Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Fri, 5 Jul 2024 17:52:23 -0700 Subject: [PATCH 8/8] Remove qpic diagram test for decomposition since its not DAMP --- qualtran/drawing/qpic_diagram_test.py | 43 --------------------------- 1 file changed, 43 deletions(-) diff --git a/qualtran/drawing/qpic_diagram_test.py b/qualtran/drawing/qpic_diagram_test.py index 6dfbd6fc8..456f375f1 100644 --- a/qualtran/drawing/qpic_diagram_test.py +++ b/qualtran/drawing/qpic_diagram_test.py @@ -37,48 +37,5 @@ def test_qpic_data_for_reflect_using_prepare(): selection / \textrm{\scalebox{0.5}{BoundedQUInt(2, 4)}} LABEL length=10 selection G:width=17:shape=box \textrm{\scalebox{0.8}{R\_L}} -""", - ) - - _assert_bloq_has_qpic_diagram( - bloq.decompose_bloq(), - r""" -DEFINE off color=white -DEFINE on color=black -_empty_wire W off -selection W \textrm{\scalebox{0.8}{selection}} -selection / \textrm{\scalebox{0.5}{BoundedQUInt(2, 4)}} -LABEL length=10 -reg W off -reg_1 W off -reg_2 W off -reg_3 W off -reg[0] W off -reg[1] W off -reg_4 W off -_empty_wire G:width=65:shape=8 GPhase((-0-1j)) -reg:on G:width=25:shape=box \textrm{\scalebox{0.8}{alloc}} -reg / \textrm{\scalebox{0.5}{QAny(2)}} -reg_1:on G:width=25:shape=box \textrm{\scalebox{0.8}{alloc}} -reg_1 / \textrm{\scalebox{0.5}{QAny(2)}} -reg_2:on G:width=25:shape=box \textrm{\scalebox{0.8}{alloc}} -reg_2 / \textrm{\scalebox{0.5}{QAny(2)}} -reg_3:on G:width=25:shape=box \textrm{\scalebox{0.8}{alloc}} -reg_3 / \textrm{\scalebox{0.5}{QAny(1)}} -selection G:width=121:shape=box \textrm{\scalebox{0.8}{StatePreparationAliasSampling}} reg G:width=37:shape=box \textrm{\scalebox{0.8}{sigma\_mu}} reg_1 G:width=17:shape=box \textrm{\scalebox{0.8}{alt}} reg_2 G:width=21:shape=box \textrm{\scalebox{0.8}{keep}} reg_3 G:width=65:shape=box \textrm{\scalebox{0.8}{less\_than\_equal}} -selection:off G:width=5:shape=> \textrm{\scalebox{0.8}{}} reg[0]:on G:width=17:shape=box \textrm{\scalebox{0.8}{[0]}} reg[1]:on G:width=17:shape=box \textrm{\scalebox{0.8}{[1]}} -reg_4:on G:width=25:shape=box \textrm{\scalebox{0.8}{alloc}} -reg_4 / \textrm{\scalebox{0.5}{QAny(1)}} -+reg_4 -reg_4 G:width=9:shape=box \textrm{\scalebox{0.8}{Z}} -reg[0] -reg[1] -+reg_4 -reg_4:off G:width=21:shape=box \textrm{\scalebox{0.8}{free}} -reg[0]:off G:width=17:shape=box \textrm{\scalebox{0.8}{[0]}} reg[1]:off G:width=17:shape=box \textrm{\scalebox{0.8}{[1]}} reg_4:on G:width=5:shape=< \textrm{\scalebox{0.8}{}} -reg_4 / \textrm{\scalebox{0.5}{BoundedQUInt(2, 4)}} -reg_4 G:width=121:shape=box \textrm{\scalebox{0.8}{StatePreparationAliasSampling}} reg G:width=37:shape=box \textrm{\scalebox{0.8}{sigma\_mu}} reg_1 G:width=17:shape=box \textrm{\scalebox{0.8}{alt}} reg_2 G:width=21:shape=box \textrm{\scalebox{0.8}{keep}} reg_3 G:width=65:shape=box \textrm{\scalebox{0.8}{less\_than\_equal}} -reg:off G:width=21:shape=box \textrm{\scalebox{0.8}{free}} -reg_1:off G:width=21:shape=box \textrm{\scalebox{0.8}{free}} -reg_2:off G:width=21:shape=box \textrm{\scalebox{0.8}{free}} -reg_3:off G:width=21:shape=box \textrm{\scalebox{0.8}{free}} """, )