diff --git a/dev_tools/autogenerate-bloqs-notebooks.py b/dev_tools/autogenerate-bloqs-notebooks.py index f3c255467..019dcf7e0 100644 --- a/dev_tools/autogenerate-bloqs-notebooks.py +++ b/dev_tools/autogenerate-bloqs-notebooks.py @@ -55,6 +55,7 @@ import qualtran.bloqs.basic_gates.rotation_test import qualtran.bloqs.basic_gates.swap_test import qualtran.bloqs.basic_gates.t_gate_test +import qualtran.bloqs.basic_gates.toffoli_test import qualtran.bloqs.basic_gates.x_basis_test import qualtran.bloqs.basic_gates.z_basis_test import qualtran.bloqs.factoring.mod_exp @@ -87,6 +88,7 @@ BloqNbSpec(qualtran.bloqs.basic_gates.z_basis_test._make_zero_state), BloqNbSpec(qualtran.bloqs.basic_gates.t_gate_test._make_t_gate), BloqNbSpec(qualtran.bloqs.basic_gates.rotation_test._make_Rz), + BloqNbSpec(qualtran.bloqs.basic_gates.toffoli_test._make_Toffoli), ], directory=f'{SOURCE_DIR}/bloqs', ), diff --git a/qualtran/bloqs/basic_gates.ipynb b/qualtran/bloqs/basic_gates.ipynb index 90c456c88..ee46eb48c 100644 --- a/qualtran/bloqs/basic_gates.ipynb +++ b/qualtran/bloqs/basic_gates.ipynb @@ -13,7 +13,9 @@ "\n", "The bloqs in this module encode gates you'd expect to find in any quantum computing\n", "framework. It includes single-qubit unitary gates like rotations, bit- and phase-flip;\n", - "basic multi-qubit unitary gates; and states and effects in the Pauli basis." + "basic multi-qubit unitary gates; states and effects in the Pauli basis; and non-Clifford\n", + "gates `TGate` and `Toffoli` which are commonly counted to estimate algorithm resource\n", + "requirements." ] }, { @@ -373,6 +375,53 @@ "psi = circuit.final_state_vector()\n", "psi * np.sqrt(2)" ] + }, + { + "cell_type": "markdown", + "id": "30a5458b", + "metadata": { + "cq.autogen": "_make_Toffoli.md" + }, + "source": [ + "## `Toffoli`\n", + "The Toffoli gate.\n", + "\n", + "This will flip the target bit if both controls are active. It can be thought of as\n", + "a reversible AND gate.\n", + "\n", + "Like `TGate`, this is a common compilation target. The Clifford+Toffoli gateset is\n", + "universal.\n", + "\n", + "#### References\n", + "[Novel constructions for the fault-tolerant Toffoli gate](https://arxiv.org/abs/1212.5069). Cody Jones. 2012. Provides a decomposition into 4 `TGate`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "020ce731", + "metadata": { + "cq.autogen": "_make_Toffoli.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.basic_gates import Toffoli\n", + "\n", + "bloq = Toffoli()\n", + "show_bloq(bloq)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0aca6c53", + "metadata": {}, + "outputs": [], + "source": [ + "from qualtran.resource_counting import get_bloq_counts_graph, GraphvizCounts\n", + "g, sigma = get_bloq_counts_graph(bloq)\n", + "GraphvizCounts(g).get_svg()" + ] } ], "metadata": { diff --git a/qualtran/bloqs/basic_gates/__init__.py b/qualtran/bloqs/basic_gates/__init__.py index 3be93f2bf..a2c2430e1 100644 --- a/qualtran/bloqs/basic_gates/__init__.py +++ b/qualtran/bloqs/basic_gates/__init__.py @@ -16,12 +16,15 @@ The bloqs in this module encode gates you'd expect to find in any quantum computing framework. It includes single-qubit unitary gates like rotations, bit- and phase-flip; -basic multi-qubit unitary gates; and states and effects in the Pauli basis. +basic multi-qubit unitary gates; states and effects in the Pauli basis; and non-Clifford +gates `TGate` and `Toffoli` which are commonly counted to estimate algorithm resource +requirements. """ from .cnot import CNOT from .rotation import Rx, Ry, Rz from .swap import CSwap, TwoBitCSwap, TwoBitSwap from .t_gate import TGate +from .toffoli import Toffoli from .x_basis import MinusEffect, MinusState, PlusEffect, PlusState, XGate from .z_basis import IntEffect, IntState, OneEffect, OneState, ZeroEffect, ZeroState, ZGate diff --git a/qualtran/bloqs/basic_gates/toffoli.py b/qualtran/bloqs/basic_gates/toffoli.py new file mode 100644 index 000000000..8bc61107f --- /dev/null +++ b/qualtran/bloqs/basic_gates/toffoli.py @@ -0,0 +1,68 @@ +# Copyright 2023 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 functools import cached_property +from typing import Dict, Optional, Set, Tuple, TYPE_CHECKING, Union + +from attrs import frozen + +from qualtran import Bloq, Register, Signature +from qualtran.bloqs.basic_gates import TGate + +if TYPE_CHECKING: + import cirq + + from qualtran.cirq_interop import CirqQuregT + from qualtran.resource_counting import BloqCountT, SympySymbolAllocator + from qualtran.simulation.classical_sim import ClassicalValT + + +@frozen +class Toffoli(Bloq): + """The Toffoli gate. + + This will flip the target bit if both controls are active. It can be thought of as + a reversible AND gate. + + Like `TGate`, this is a common compilation target. The Clifford+Toffoli gateset is + universal. + + References: + [Novel constructions for the fault-tolerant Toffoli gate](https://arxiv.org/abs/1212.5069). + Cody Jones. 2012. Provides a decomposition into 4 `TGate`. + """ + + @cached_property + def signature(self) -> Signature: + return Signature([Register('ctrl', 1, shape=(2,)), Register('target', 1)]) + + def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set['BloqCountT']: + return {(4, TGate())} + + def on_classical_vals( + self, ctrl: 'ClassicalValT', target: 'ClassicalValT' + ) -> Dict[str, 'ClassicalValT']: + assert target in [0, 1] + if ctrl[0] == 1 and ctrl[1] == 1: + target = (target + 1) % 2 + + return {'ctrl': ctrl, 'target': target} + + def as_cirq_op( + self, qubit_manager: 'cirq.QubitManager', ctrl: 'CirqQuregT', target: 'CirqQuregT' + ) -> Tuple[Union['cirq.Operation', None], Dict[str, 'CirqQuregT']]: + import cirq + + (trg,) = target + return cirq.CCNOT(*ctrl[:, 0], trg), {'ctrl': ctrl, 'target': target} diff --git a/qualtran/bloqs/basic_gates/toffoli_test.py b/qualtran/bloqs/basic_gates/toffoli_test.py new file mode 100644 index 000000000..5dd2cda59 --- /dev/null +++ b/qualtran/bloqs/basic_gates/toffoli_test.py @@ -0,0 +1,78 @@ +# Copyright 2023 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 itertools + +import cirq + +from qualtran import BloqBuilder +from qualtran.bloqs.basic_gates import TGate, Toffoli, ZeroState +from qualtran.resource_counting import get_bloq_counts_graph + + +def _make_Toffoli(): + from qualtran.bloqs.basic_gates import Toffoli + + return Toffoli() + + +def test_toffoli_t_count(): + counts = Toffoli().bloq_counts() + assert counts == {(4, TGate())} + + _, sigma = get_bloq_counts_graph(Toffoli()) + assert sigma == {TGate(): 4} + + +def test_toffoli_cirq(): + bb = BloqBuilder() + c0, c1, trg = [bb.add(ZeroState()) for _ in range(3)] + ctrl, target = bb.add(Toffoli(), ctrl=[c0, c1], target=trg) + ctrl, target = bb.add(Toffoli(), ctrl=ctrl, target=target) + cbloq = bb.finalize(q0=ctrl[0], q1=ctrl[1], q2=target) + + circuit, qubits = cbloq.to_cirq_circuit() + cirq.testing.assert_has_diagram( + circuit, + """\ +_c(0): ───@───@─── + │ │ +_c(1): ───@───@─── + │ │ +_c(2): ───X───X───""", + ) + + +def test_classical_sim(): + tof = Toffoli() + + for c0, c1 in itertools.product([0, 1], repeat=2): + ctrl, target = tof.call_classically(ctrl=[c0, c1], target=0) + assert ctrl.tolist() == [c0, c1] + if c0 == 1 and c1 == 1: + assert target == 1 + else: + assert target == 0 + + +def test_classical_sim_2(): + bb = BloqBuilder() + c0, c1, trg = [bb.add(ZeroState()) for _ in range(3)] + ctrl, target = bb.add(Toffoli(), ctrl=[c0, c1], target=trg) + ctrl, target = bb.add(Toffoli(), ctrl=ctrl, target=target) + cbloq = bb.finalize(q0=ctrl[0], q1=ctrl[1], q2=target) + + b0, b1, b2 = cbloq.call_classically() + assert b0 == 0 + assert b1 == 0 + assert b2 == 0