diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index c08b5a21b34..39aae0adc75 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -224,6 +224,7 @@ givens, GlobalPhaseGate, global_phase_operation, + GreedyQubitManager, H, HPowGate, I, @@ -301,6 +302,7 @@ ry, rz, S, + SimpleQubitManager, SingleQubitCliffordGate, SingleQubitPauliStringGateOperation, SQRT_ISWAP, diff --git a/cirq-core/cirq/ops/__init__.py b/cirq-core/cirq/ops/__init__.py index ae352645ce2..5cadb6ad9af 100644 --- a/cirq-core/cirq/ops/__init__.py +++ b/cirq-core/cirq/ops/__init__.py @@ -122,6 +122,8 @@ from cirq.ops.qubit_manager import BorrowableQubit, CleanQubit, QubitManager, SimpleQubitManager +from cirq.ops.greedy_qubit_manager import GreedyQubitManager + from cirq.ops.qubit_order import QubitOrder from cirq.ops.qubit_order_or_list import QubitOrderOrList diff --git a/cirq-core/cirq/ops/greedy_qubit_manager.py b/cirq-core/cirq/ops/greedy_qubit_manager.py new file mode 100644 index 00000000000..abed4c8d51d --- /dev/null +++ b/cirq-core/cirq/ops/greedy_qubit_manager.py @@ -0,0 +1,86 @@ +# Copyright 2023 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 Iterable, List, Set, TYPE_CHECKING + +from cirq.ops import named_qubit, qid_util, qubit_manager + +if TYPE_CHECKING: + import cirq + + +class GreedyQubitManager(qubit_manager.QubitManager): + """Greedy allocator that maximizes/minimizes qubit reuse based on a configurable parameter. + + GreedyQubitManager can be configured, using `maximize_reuse` flag, to work in one of two modes: + - Minimize qubit reuse (maximize_reuse=False): For a fixed width, this mode uses a FIFO (First + in First out) strategy s.t. next allocated qubit is one which was freed the earliest. + - Maximize qubit reuse (maximize_reuse=True): For a fixed width, this mode uses a LIFO (Last in + First out) strategy s.t. the next allocated qubit is one which was freed the latest. + + If the requested qubits are more than the set of free qubits, the qubit manager automatically + resizes the size of the managed qubit pool and adds new free qubits, that have their last + freed time to be -infinity. + + For borrowing qubits, the qubit manager simply delegates borrow requests to `self.qalloc`, thus + always allocating new clean qubits. + """ + + def __init__(self, prefix: str, *, size: int = 0, maximize_reuse: bool = False): + """Initializes `GreedyQubitManager` + + Args: + prefix: The prefix to use for naming new clean ancillas allocated by the qubit manager. + The i'th allocated qubit is of the type `cirq.NamedQubit(f'{prefix}_{i}')`. + size: The initial size of the pool of ancilla qubits managed by the qubit manager. The + qubit manager can automatically resize itself when the allocation request + exceeds the number of available qubits. + maximize_reuse: Flag to control a FIFO vs LIFO strategy, defaults to False (FIFO). + """ + self._prefix = prefix + self._used_qubits: Set['cirq.Qid'] = set() + self._free_qubits: List['cirq.Qid'] = [] + self._size = 0 + self.maximize_reuse = maximize_reuse + self.resize(size) + + def _allocate_qid(self, name: str, dim: int) -> 'cirq.Qid': + return qid_util.q(name) if dim == 2 else named_qubit.NamedQid(name, dimension=dim) + + def resize(self, new_size: int, dim: int = 2) -> None: + if new_size <= self._size: + return + new_qubits: List['cirq.Qid'] = [ + self._allocate_qid(f'{self._prefix}_{s}', dim) for s in range(self._size, new_size) + ] + self._free_qubits = new_qubits + self._free_qubits + self._size = new_size + + def qalloc(self, n: int, dim: int = 2) -> List['cirq.Qid']: + if not n: + return [] + self.resize(self._size + n - len(self._free_qubits), dim=dim) + ret_qubits = self._free_qubits[-n:] if self.maximize_reuse else self._free_qubits[:n] + self._free_qubits = self._free_qubits[:-n] if self.maximize_reuse else self._free_qubits[n:] + self._used_qubits.update(ret_qubits) + return ret_qubits + + def qfree(self, qubits: Iterable['cirq.Qid']) -> None: + qs = list(dict(zip(qubits, qubits)).keys()) + assert self._used_qubits.issuperset(qs), "Only managed qubits currently in-use can be freed" + self._used_qubits = self._used_qubits.difference(qs) + self._free_qubits.extend(qs) + + def qborrow(self, n: int, dim: int = 2) -> List['cirq.Qid']: + return self.qalloc(n, dim) diff --git a/cirq-core/cirq/ops/greedy_qubit_manager_test.py b/cirq-core/cirq/ops/greedy_qubit_manager_test.py new file mode 100644 index 00000000000..c20c8854bc8 --- /dev/null +++ b/cirq-core/cirq/ops/greedy_qubit_manager_test.py @@ -0,0 +1,98 @@ +# Copyright 2023 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. + +import cirq + + +class GateAllocInDecompose(cirq.Gate): + def __init__(self, num_alloc: int = 1): + self.num_alloc = num_alloc + + def _num_qubits_(self) -> int: + return 1 + + def _decompose_with_context_(self, qubits, context): + assert context is not None + qm = context.qubit_manager + for q in qm.qalloc(self.num_alloc): + yield cirq.CNOT(qubits[0], q) + qm.qfree([q]) + + +def test_greedy_qubit_manager(): + def make_circuit(qm: cirq.QubitManager): + q = cirq.LineQubit.range(2) + g = GateAllocInDecompose(1) + context = cirq.DecompositionContext(qubit_manager=qm) + circuit = cirq.Circuit( + cirq.decompose_once(g.on(q[0]), context=context), + cirq.decompose_once(g.on(q[1]), context=context), + ) + return circuit + + qm = cirq.GreedyQubitManager(prefix="ancilla", size=1) + # Qubit manager with only 1 managed qubit. Will always repeat the same qubit. + circuit = make_circuit(qm) + cirq.testing.assert_has_diagram( + circuit, + """ +0: ───────────@─────── + │ +1: ───────────┼───@─── + │ │ +ancilla_0: ───X───X─── + """, + ) + + qm = cirq.GreedyQubitManager(prefix="ancilla", size=2) + # Qubit manager with 2 managed qubits and maximize_reuse=False, tries to minimize adding + # additional data dependencies by minimizing qubit reuse. + circuit = make_circuit(qm) + cirq.testing.assert_has_diagram( + circuit, + """ + ┌──┐ +0: ────────────@───── + │ +1: ────────────┼@──── + ││ +ancilla_0: ────X┼──── + │ +ancilla_1: ─────X──── + └──┘ + """, + ) + + qm = cirq.GreedyQubitManager(prefix="ancilla", size=2, maximize_reuse=True) + # Qubit manager with 2 managed qubits and maximize_reuse=True, tries to maximize reuse by + # potentially adding new data dependencies. + circuit = make_circuit(qm) + cirq.testing.assert_has_diagram( + circuit, + """ +0: ───────────@─────── + │ +1: ───────────┼───@─── + │ │ +ancilla_1: ───X───X─── + """, + ) + + +def test_greedy_qubit_manager_preserves_order(): + qm = cirq.GreedyQubitManager(prefix="anc") + ancillae = [cirq.q(f"anc_{i}") for i in range(100)] + assert qm.qalloc(100) == ancillae + qm.qfree(ancillae) + assert qm.qalloc(100) == ancillae diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index 7a92491267a..05aeaa6be86 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -89,6 +89,9 @@ 'LineInitialMapper', 'MappingManager', 'RouteCQC', + # Qubit Managers, + 'SimpleQubitManager', + 'GreedyQubitManager', # global objects 'CONTROL_TAG', 'PAULI_BASIS', diff --git a/cirq-ft/cirq_ft/__init__.py b/cirq-ft/cirq_ft/__init__.py index 48a7e334ef2..938720ba693 100644 --- a/cirq-ft/cirq_ft/__init__.py +++ b/cirq-ft/cirq_ft/__init__.py @@ -45,7 +45,6 @@ ) from cirq_ft.infra import ( GateWithRegisters, - GreedyQubitManager, Register, Signature, SelectionRegister, diff --git a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target_test.py b/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target_test.py index 074418048a5..4e6683b68f4 100644 --- a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target_test.py +++ b/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target_test.py @@ -22,7 +22,7 @@ @pytest.mark.parametrize("selection_bitsize,target_bitsize", [[3, 5], [3, 7], [4, 5]]) def test_apply_gate_to_lth_qubit(selection_bitsize, target_bitsize): - greedy_mm = cirq_ft.GreedyQubitManager(prefix="_a", maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) gate = cirq_ft.ApplyGateToLthQubit( cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), lambda _: cirq.X ) diff --git a/cirq-ft/cirq_ft/algos/arithmetic_gates_test.py b/cirq-ft/cirq_ft/algos/arithmetic_gates_test.py index b33e9f4c7ce..011f162ba7c 100644 --- a/cirq-ft/cirq_ft/algos/arithmetic_gates_test.py +++ b/cirq-ft/cirq_ft/algos/arithmetic_gates_test.py @@ -18,7 +18,7 @@ import cirq_ft import numpy as np import pytest -from cirq_ft.infra import bit_tools, GreedyQubitManager +from cirq_ft.infra import bit_tools def identity_map(n: int): @@ -174,7 +174,7 @@ def test_add(a: int, b: int, num_bits: int): num_anc = num_bits - 1 gate = cirq_ft.AdditionGate(num_bits) qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq_ft.GreedyQubitManager(prefix="_a", maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) context = cirq.DecompositionContext(greedy_mm) circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) ancillas = sorted(circuit.all_qubits())[-num_anc:] @@ -258,7 +258,7 @@ def test_add_truncated(): num_anc = num_bits - 1 gate = cirq_ft.AdditionGate(num_bits) qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq_ft.GreedyQubitManager(prefix="_a", maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) context = cirq.DecompositionContext(greedy_mm) circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) ancillas = sorted(circuit.all_qubits() - frozenset(qubits)) @@ -272,7 +272,7 @@ def test_add_truncated(): num_anc = num_bits - 1 gate = cirq_ft.AdditionGate(num_bits) qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq_ft.GreedyQubitManager(prefix="_a", maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) context = cirq.DecompositionContext(greedy_mm) circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) ancillas = sorted(circuit.all_qubits() - frozenset(qubits)) @@ -290,7 +290,7 @@ def test_subtract(a, b, num_bits): num_anc = num_bits - 1 gate = cirq_ft.AdditionGate(num_bits) qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq_ft.GreedyQubitManager(prefix="_a", maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) context = cirq.DecompositionContext(greedy_mm) circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) ancillas = sorted(circuit.all_qubits())[-num_anc:] @@ -340,7 +340,7 @@ def test_decompose_less_than_equal_gate(P: int, n: int, Q: int, m: int): circuit = cirq.Circuit( cirq.decompose_once( cirq_ft.LessThanEqualGate(n, m).on(*cirq.LineQubit.range(n + m + 1)), - context=cirq.DecompositionContext(GreedyQubitManager(prefix='_c')), + context=cirq.DecompositionContext(cirq.GreedyQubitManager(prefix='_c')), ) ) qubit_order = tuple(sorted(circuit.all_qubits())) diff --git a/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array_test.py b/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array_test.py index c1a275db06a..15dd277c37b 100644 --- a/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array_test.py +++ b/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array_test.py @@ -63,7 +63,7 @@ def construct_prga_with_identity(*args, **kwargs) -> cirq_ft.ProgrammableRotatio def test_programmable_rotation_gate_array(angles, kappa, constructor): rotation_gate = cirq.X programmable_rotation_gate = constructor(*angles, kappa=kappa, rotation_gate=rotation_gate) - greedy_mm = cirq_ft.GreedyQubitManager(prefix="_a") + greedy_mm = cirq.GreedyQubitManager(prefix="_a") g = cirq_ft.testing.GateHelper( programmable_rotation_gate, context=cirq.DecompositionContext(greedy_mm) ) diff --git a/cirq-ft/cirq_ft/algos/qrom_test.py b/cirq-ft/cirq_ft/algos/qrom_test.py index a9a56c53ff4..6a3f14d621b 100644 --- a/cirq-ft/cirq_ft/algos/qrom_test.py +++ b/cirq-ft/cirq_ft/algos/qrom_test.py @@ -29,7 +29,7 @@ @pytest.mark.parametrize("num_controls", [0, 1, 2]) def test_qrom_1d(data, num_controls): qrom = cirq_ft.QROM.build(*data, num_controls=num_controls) - greedy_mm = cirq_ft.GreedyQubitManager('a', maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager('a', maximize_reuse=True) g = cirq_ft.testing.GateHelper(qrom, context=cirq.DecompositionContext(greedy_mm)) decomposed_circuit = cirq.Circuit(cirq.decompose(g.operation, context=g.context)) inverse = cirq.Circuit(cirq.decompose(g.operation**-1, context=g.context)) @@ -121,7 +121,7 @@ def test_t_complexity(data): def _assert_qrom_has_diagram(qrom: cirq_ft.QROM, expected_diagram: str): gh = cirq_ft.testing.GateHelper(qrom) op = gh.operation - context = cirq.DecompositionContext(qubit_manager=cirq_ft.GreedyQubitManager(prefix="anc")) + context = cirq.DecompositionContext(qubit_manager=cirq.GreedyQubitManager(prefix="anc")) circuit = cirq.Circuit(cirq.decompose_once(op, context=context)) selection = [ *itertools.chain.from_iterable(gh.quregs[reg.name] for reg in qrom.selection_registers) @@ -208,7 +208,7 @@ def test_qrom_multi_dim(data, num_controls): target_bitsizes=target_bitsizes, num_controls=num_controls, ) - greedy_mm = cirq_ft.GreedyQubitManager('a', maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager('a', maximize_reuse=True) g = cirq_ft.testing.GateHelper(qrom, context=cirq.DecompositionContext(greedy_mm)) decomposed_circuit = cirq.Circuit(cirq.decompose(g.operation, context=g.context)) inverse = cirq.Circuit(cirq.decompose(g.operation**-1, context=g.context)) diff --git a/cirq-ft/cirq_ft/algos/qubitization_walk_operator_test.py b/cirq-ft/cirq_ft/algos/qubitization_walk_operator_test.py index 465bb0fdd71..8ef24255ed9 100644 --- a/cirq-ft/cirq_ft/algos/qubitization_walk_operator_test.py +++ b/cirq-ft/cirq_ft/algos/qubitization_walk_operator_test.py @@ -63,7 +63,7 @@ def test_qubitization_walk_operator(num_sites: int, eps: float): L_state = np.zeros(2 ** len(g.quregs['selection'])) L_state[: len(ham_coeff)] = np.sqrt(ham_coeff / qubitization_lambda) - greedy_mm = cirq_ft.GreedyQubitManager('ancilla', maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager('ancilla', maximize_reuse=True) walk_circuit = cirq_ft.map_clean_and_borrowable_qubits(walk_circuit, qm=greedy_mm) assert len(walk_circuit.all_qubits()) < 23 qubit_order = cirq.QubitOrder.explicit( diff --git a/cirq-ft/cirq_ft/algos/reflection_using_prepare_test.py b/cirq-ft/cirq_ft/algos/reflection_using_prepare_test.py index 1986b77f955..6a8179a3162 100644 --- a/cirq-ft/cirq_ft/algos/reflection_using_prepare_test.py +++ b/cirq-ft/cirq_ft/algos/reflection_using_prepare_test.py @@ -44,7 +44,7 @@ def keep(op: cirq.Operation): def greedily_allocate_ancilla(circuit: cirq.AbstractCircuit) -> cirq.Circuit: - greedy_mm = cirq_ft.GreedyQubitManager(prefix="ancilla", maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager(prefix="ancilla", maximize_reuse=True) circuit = cirq_ft.map_clean_and_borrowable_qubits(circuit, qm=greedy_mm) assert len(circuit.all_qubits()) < 30 return circuit diff --git a/cirq-ft/cirq_ft/algos/select_swap_qrom_test.py b/cirq-ft/cirq_ft/algos/select_swap_qrom_test.py index 615596ee15e..44a34c7622b 100644 --- a/cirq-ft/cirq_ft/algos/select_swap_qrom_test.py +++ b/cirq-ft/cirq_ft/algos/select_swap_qrom_test.py @@ -29,7 +29,7 @@ def test_select_swap_qrom(data, block_size): selection_q, selection_r = selection[: qrom.selection_q], selection[qrom.selection_q :] targets = [qubit_regs[f"target{i}"] for i in range(len(data))] - greedy_mm = cirq_ft.GreedyQubitManager(prefix="_a", maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) context = cirq.DecompositionContext(greedy_mm) qrom_circuit = cirq.Circuit(cirq.decompose(qrom.on_registers(**qubit_regs), context=context)) diff --git a/cirq-ft/cirq_ft/algos/selected_majorana_fermion_test.py b/cirq-ft/cirq_ft/algos/selected_majorana_fermion_test.py index 5a50af6a2bb..3e6a85d0e21 100644 --- a/cirq-ft/cirq_ft/algos/selected_majorana_fermion_test.py +++ b/cirq-ft/cirq_ft/algos/selected_majorana_fermion_test.py @@ -100,7 +100,7 @@ def test_selected_majorana_fermion_gate_decomposed_diagram(): cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), target_gate=cirq.X, ) - greedy_mm = cirq_ft.GreedyQubitManager(prefix="_a", maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) g = cirq_ft.testing.GateHelper(gate) context = cirq.DecompositionContext(greedy_mm) circuit = cirq.Circuit(cirq.decompose_once(g.operation, context=context)) diff --git a/cirq-ft/cirq_ft/algos/unary_iteration_gate.py b/cirq-ft/cirq_ft/algos/unary_iteration_gate.py index 245f57f43fe..5f830921bf0 100644 --- a/cirq-ft/cirq_ft/algos/unary_iteration_gate.py +++ b/cirq-ft/cirq_ft/algos/unary_iteration_gate.py @@ -182,13 +182,13 @@ def unary_iteration( Users can write multi-dimensional coherent for loops as follows: >>> import cirq - >>> from cirq_ft import unary_iteration, GreedyQubitManager + >>> from cirq_ft import unary_iteration >>> N, M = 5, 7 >>> target = [[cirq.q(f't({i}, {j})') for j in range(M)] for i in range(N)] >>> selection = [[cirq.q(f's({i}, {j})') for j in range(3)] for i in range(3)] >>> circuit = cirq.Circuit() >>> i_ops = [] - >>> qm = GreedyQubitManager("ancilla", maximize_reuse=True) + >>> qm = cirq.GreedyQubitManager("ancilla", maximize_reuse=True) >>> for i_optree, i_ctrl, i in unary_iteration(0, N, i_ops, [], selection[0], qm): ... circuit.append(i_optree) ... j_ops = [] diff --git a/cirq-ft/cirq_ft/algos/unary_iteration_gate_test.py b/cirq-ft/cirq_ft/algos/unary_iteration_gate_test.py index 7450e5e496a..213788397d9 100644 --- a/cirq-ft/cirq_ft/algos/unary_iteration_gate_test.py +++ b/cirq-ft/cirq_ft/algos/unary_iteration_gate_test.py @@ -58,7 +58,7 @@ def nth_operation( # type: ignore[override] "selection_bitsize, target_bitsize, control_bitsize", [(3, 5, 1), (2, 4, 2), (1, 2, 3)] ) def test_unary_iteration_gate(selection_bitsize, target_bitsize, control_bitsize): - greedy_mm = cirq_ft.GreedyQubitManager(prefix="_a", maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) gate = ApplyXToLthQubit(selection_bitsize, target_bitsize, control_bitsize) g = cirq_ft.testing.GateHelper(gate, context=cirq.DecompositionContext(greedy_mm)) assert len(g.all_qubits) <= 2 * (selection_bitsize + control_bitsize) + target_bitsize - 1 @@ -120,7 +120,7 @@ def nth_operation( # type: ignore[override] @pytest.mark.parametrize("target_shape", [(2, 3, 2), (2, 2, 2)]) def test_multi_dimensional_unary_iteration_gate(target_shape: Tuple[int, int, int]): - greedy_mm = cirq_ft.GreedyQubitManager(prefix="_a", maximize_reuse=True) + greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) gate = ApplyXToIJKthQubit(target_shape) g = cirq_ft.testing.GateHelper(gate, context=cirq.DecompositionContext(greedy_mm)) assert ( @@ -155,7 +155,7 @@ def test_unary_iteration_loop(): ] selection = infra.get_named_qubits(selection_registers) target = {(n, m): cirq.q(f't({n}, {m})') for n in range(*n_range) for m in range(*m_range)} - qm = cirq_ft.GreedyQubitManager("ancilla", maximize_reuse=True) + qm = cirq.GreedyQubitManager("ancilla", maximize_reuse=True) circuit = cirq.Circuit() i_ops = [] # Build the unary iteration circuit diff --git a/cirq-ft/cirq_ft/infra/__init__.py b/cirq-ft/cirq_ft/infra/__init__.py index 13ea10ddca0..3159271f131 100644 --- a/cirq-ft/cirq_ft/infra/__init__.py +++ b/cirq-ft/cirq_ft/infra/__init__.py @@ -24,5 +24,4 @@ get_named_qubits, ) from cirq_ft.infra.qubit_management_transformers import map_clean_and_borrowable_qubits -from cirq_ft.infra.qubit_manager import GreedyQubitManager from cirq_ft.infra.t_complexity_protocol import TComplexity, t_complexity diff --git a/cirq-ft/cirq_ft/infra/decompose_protocol.py b/cirq-ft/cirq_ft/infra/decompose_protocol.py index 0a88de2de18..e7b6b6615d0 100644 --- a/cirq-ft/cirq_ft/infra/decompose_protocol.py +++ b/cirq-ft/cirq_ft/infra/decompose_protocol.py @@ -16,7 +16,6 @@ import cirq from cirq.protocols.decompose_protocol import DecomposeResult -from cirq_ft.infra import qubit_manager _FREDKIN_GATESET = cirq.Gateset(cirq.FREDKIN, unroll_circuit_op=False) @@ -82,9 +81,7 @@ def _decompose_once_considering_known_decomposition(val: Any) -> DecomposeResult import uuid context = cirq.DecompositionContext( - qubit_manager=qubit_manager.GreedyQubitManager( - prefix=f'_{uuid.uuid4()}', maximize_reuse=True - ) + qubit_manager=cirq.GreedyQubitManager(prefix=f'_{uuid.uuid4()}', maximize_reuse=True) ) decomposed = _try_decompose_from_known_decompositions(val, context) diff --git a/cirq-ft/cirq_ft/infra/qubit_management_transformers.py b/cirq-ft/cirq_ft/infra/qubit_management_transformers.py index b092545de1d..8e584a62c7c 100644 --- a/cirq-ft/cirq_ft/infra/qubit_management_transformers.py +++ b/cirq-ft/cirq_ft/infra/qubit_management_transformers.py @@ -15,7 +15,6 @@ from typing import Dict, Optional, Set, Tuple import cirq -from cirq_ft.infra import qubit_manager def _get_qubit_mapping_first_and_last_moment( @@ -87,7 +86,7 @@ def map_clean_and_borrowable_qubits( system qubits or new ancilla qubits allocated using the `qm` qubit manager. """ if qm is None: - qm = qubit_manager.GreedyQubitManager(prefix="ancilla") + qm = cirq.GreedyQubitManager(prefix="ancilla") allocated_qubits = {q for q in circuit.all_qubits() if _is_temp(q)} qubits_lifespan = _get_qubit_mapping_first_and_last_moment(circuit) diff --git a/cirq-ft/cirq_ft/infra/qubit_management_transformers_test.py b/cirq-ft/cirq_ft/infra/qubit_management_transformers_test.py index 0274787ec37..1266b709414 100644 --- a/cirq-ft/cirq_ft/infra/qubit_management_transformers_test.py +++ b/cirq-ft/cirq_ft/infra/qubit_management_transformers_test.py @@ -14,7 +14,24 @@ import cirq import cirq_ft -from cirq_ft.infra.qubit_manager_test import GateAllocInDecompose + + +class GateAllocInDecompose(cirq.Gate): + def __init__(self, num_alloc: int = 1): + self.num_alloc = num_alloc + + def _num_qubits_(self) -> int: + return 1 + + def _decompose_with_context_(self, qubits, context): + assert context is not None + qm = context.qubit_manager + for q in qm.qalloc(self.num_alloc): + yield cirq.CNOT(qubits[0], q) + qm.qfree([q]) + + def __str__(self): + return 'TestGateAlloc' class GateAllocAndBorrowInDecompose(cirq.Gate): @@ -82,7 +99,7 @@ def test_map_clean_and_borrowable_qubits_greedy_types(): ) # Maximize parallelism by maximizing qubit width and minimizing qubit reuse. - qubit_manager = cirq_ft.GreedyQubitManager(prefix='ancilla', size=2, maximize_reuse=False) + qubit_manager = cirq.GreedyQubitManager(prefix='ancilla', size=2, maximize_reuse=False) allocated_circuit = cirq_ft.map_clean_and_borrowable_qubits(unrolled_circuit, qm=qubit_manager) cirq.testing.assert_has_diagram( allocated_circuit, @@ -100,7 +117,7 @@ def test_map_clean_and_borrowable_qubits_greedy_types(): ) # Minimize parallelism by minimizing qubit width and maximizing qubit reuse. - qubit_manager = cirq_ft.GreedyQubitManager(prefix='ancilla', size=2, maximize_reuse=True) + qubit_manager = cirq.GreedyQubitManager(prefix='ancilla', size=2, maximize_reuse=True) allocated_circuit = cirq_ft.map_clean_and_borrowable_qubits(unrolled_circuit, qm=qubit_manager) cirq.testing.assert_has_diagram( allocated_circuit, @@ -220,7 +237,7 @@ def test_map_clean_and_borrowable_qubits_borrows(): def test_map_clean_and_borrowable_qubits_deallocates_only_once(): q = [cirq.ops.BorrowableQubit(i) for i in range(2)] + [cirq.q('q')] circuit = cirq.Circuit(cirq.X.on_each(*q), cirq.Y(q[1]), cirq.Z(q[1])) - greedy_mm = cirq_ft.GreedyQubitManager(prefix="a", size=2) + greedy_mm = cirq.GreedyQubitManager(prefix="a", size=2) mapped_circuit = cirq_ft.map_clean_and_borrowable_qubits(circuit, qm=greedy_mm) cirq.testing.assert_has_diagram( mapped_circuit, diff --git a/cirq-ft/cirq_ft/infra/qubit_manager.py b/cirq-ft/cirq_ft/infra/qubit_manager.py index 59e4bbded13..5b94b7a056d 100644 --- a/cirq-ft/cirq_ft/infra/qubit_manager.py +++ b/cirq-ft/cirq_ft/infra/qubit_manager.py @@ -12,72 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Iterable, List, Set - import cirq -class GreedyQubitManager(cirq.QubitManager): - """Greedy allocator that maximizes/minimizes qubit reuse based on a configurable parameter. - - GreedyQubitManager can be configured, using `maximize_reuse` flag, to work in one of two modes: - - Minimize qubit reuse (maximize_reuse=False): For a fixed width, this mode uses a FIFO (First - in First out) strategy s.t. next allocated qubit is one which was freed the earliest. - - Maximize qubit reuse (maximize_reuse=True): For a fixed width, this mode uses a LIFO (Last in - First out) strategy s.t. the next allocated qubit is one which was freed the latest. - - If the requested qubits are more than the set of free qubits, the qubit manager automatically - resizes the size of the managed qubit pool and adds new free qubits, that have their last - freed time to be -infinity. - - For borrowing qubits, the qubit manager simply delegates borrow requests to `self.qalloc`, thus - always allocating new clean qubits. - """ - - def __init__(self, prefix: str, *, size: int = 0, maximize_reuse: bool = False): - """Initializes `GreedyQubitManager` - - Args: - prefix: The prefix to use for naming new clean ancillas allocated by the qubit manager. - The i'th allocated qubit is of the type `cirq.NamedQubit(f'{prefix}_{i}')`. - size: The initial size of the pool of ancilla qubits managed by the qubit manager. The - qubit manager can automatically resize itself when the allocation request - exceeds the number of available qubits. - maximize_reuse: Flag to control a FIFO vs LIFO strategy, defaults to False (FIFO). - """ - self._prefix = prefix - self._used_qubits: Set[cirq.Qid] = set() - self._free_qubits: List[cirq.Qid] = [] - self._size = 0 - self.maximize_reuse = maximize_reuse - self.resize(size) - - def _allocate_qid(self, name: str, dim: int) -> cirq.Qid: - return cirq.q(name) if dim == 2 else cirq.NamedQid(name, dimension=dim) - - def resize(self, new_size: int, dim: int = 2) -> None: - if new_size <= self._size: - return - new_qubits: List[cirq.Qid] = [ - self._allocate_qid(f'{self._prefix}_{s}', dim) for s in range(self._size, new_size) - ] - self._free_qubits = new_qubits + self._free_qubits - self._size = new_size - - def qalloc(self, n: int, dim: int = 2) -> List[cirq.Qid]: - if not n: - return [] - self.resize(self._size + n - len(self._free_qubits), dim=dim) - ret_qubits = self._free_qubits[-n:] if self.maximize_reuse else self._free_qubits[:n] - self._free_qubits = self._free_qubits[:-n] if self.maximize_reuse else self._free_qubits[n:] - self._used_qubits.update(ret_qubits) - return ret_qubits - - def qfree(self, qubits: Iterable[cirq.Qid]) -> None: - qs = list(dict(zip(qubits, qubits)).keys()) - assert self._used_qubits.issuperset(qs), "Only managed qubits currently in-use can be freed" - self._used_qubits = self._used_qubits.difference(qs) - self._free_qubits.extend(qs) - - def qborrow(self, n: int, dim: int = 2) -> List[cirq.Qid]: - return self.qalloc(n, dim) +@cirq._compat.deprecated_class(deadline="v1.4", fix="Use cirq.GreedyQubitManager instead.") +class GreedyQubitManager(cirq.GreedyQubitManager): + pass diff --git a/cirq-ft/cirq_ft/infra/qubit_manager_test.py b/cirq-ft/cirq_ft/infra/qubit_manager_test.py index 5eda575958a..e4e88290ff3 100644 --- a/cirq-ft/cirq_ft/infra/qubit_manager_test.py +++ b/cirq-ft/cirq_ft/infra/qubit_manager_test.py @@ -13,90 +13,9 @@ # limitations under the License. import cirq -import cirq_ft +from cirq_ft.infra.qubit_manager import GreedyQubitManager -class GateAllocInDecompose(cirq.Gate): - def __init__(self, num_alloc: int = 1): - self.num_alloc = num_alloc - - def _num_qubits_(self) -> int: - return 1 - - def _decompose_with_context_(self, qubits, context): - assert context is not None - qm = context.qubit_manager - for q in qm.qalloc(self.num_alloc): - yield cirq.CNOT(qubits[0], q) - qm.qfree([q]) - - def __str__(self): - return 'TestGateAlloc' - - -def test_greedy_qubit_manager(): - def make_circuit(qm: cirq.QubitManager): - q = cirq.LineQubit.range(2) - g = GateAllocInDecompose(1) - context = cirq.DecompositionContext(qubit_manager=qm) - circuit = cirq.Circuit( - cirq.decompose_once(g.on(q[0]), context=context), - cirq.decompose_once(g.on(q[1]), context=context), - ) - return circuit - - qm = cirq_ft.GreedyQubitManager(prefix="ancilla", size=1) - # Qubit manager with only 1 managed qubit. Will always repeat the same qubit. - circuit = make_circuit(qm) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ───────────@─────── - │ -1: ───────────┼───@─── - │ │ -ancilla_0: ───X───X─── - """, - ) - - qm = cirq_ft.GreedyQubitManager(prefix="ancilla", size=2) - # Qubit manager with 2 managed qubits and maximize_reuse=False, tries to minimize adding - # additional data dependencies by minimizing qubit reuse. - circuit = make_circuit(qm) - cirq.testing.assert_has_diagram( - circuit, - """ - ┌──┐ -0: ────────────@───── - │ -1: ────────────┼@──── - ││ -ancilla_0: ────X┼──── - │ -ancilla_1: ─────X──── - └──┘ - """, - ) - - qm = cirq_ft.GreedyQubitManager(prefix="ancilla", size=2, maximize_reuse=True) - # Qubit manager with 2 managed qubits and maximize_reuse=True, tries to maximize reuse by - # potentially adding new data dependencies. - circuit = make_circuit(qm) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ───────────@─────── - │ -1: ───────────┼───@─── - │ │ -ancilla_1: ───X───X─── - """, - ) - - -def test_greedy_qubit_manager_preserves_order(): - qm = cirq_ft.GreedyQubitManager(prefix="anc") - ancillae = [cirq.q(f"anc_{i}") for i in range(100)] - assert qm.qalloc(100) == ancillae - qm.qfree(ancillae) - assert qm.qalloc(100) == ancillae +def test_deprecated(): + with cirq.testing.assert_deprecated("Use cirq.GreedyQubitManager instead", deadline="v1.4"): + _ = GreedyQubitManager('ancilla')