diff --git a/qualtran/_infra/adjoint_test.py b/qualtran/_infra/adjoint_test.py index 1b8567cd8c..828214b195 100644 --- a/qualtran/_infra/adjoint_test.py +++ b/qualtran/_infra/adjoint_test.py @@ -11,25 +11,20 @@ # 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 cast, Dict, TYPE_CHECKING +from typing import cast import pytest import sympy import qualtran.testing as qlt_testing -from qualtran import Adjoint, Bloq, CompositeBloq, Side, Signature +from qualtran import Adjoint, CompositeBloq, Side from qualtran._infra.adjoint import _adjoint_cbloq from qualtran.bloqs.basic_gates import CNOT, CSwap, ZeroState from qualtran.bloqs.for_testing.atom import TestAtom from qualtran.bloqs.for_testing.with_call_graph import TestBloqWithCallGraph from qualtran.bloqs.for_testing.with_decomposition import TestParallelCombo, TestSerialCombo -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import LarrowTextBox, RarrowTextBox, Text -if TYPE_CHECKING: - from qualtran import BloqBuilder, SoquetT - def test_serial_combo_adjoint(): # The normal decomposition is three `TestAtom` tagged atom{0,1,2}. @@ -168,37 +163,6 @@ def test_wire_symbol(): assert isinstance(adj_ws, RarrowTextBox) -class TAcceptsAdjoint(TestAtom): - def _t_complexity_(self, adjoint: bool = False) -> TComplexity: - return TComplexity(t=2 if adjoint else 1) - - -class TDoesNotAcceptAdjoint(TestAtom): - def _t_complexity_(self) -> TComplexity: - return TComplexity(t=3) - - -class DecomposesIntoTAcceptsAdjoint(Bloq): - @cached_property - def signature(self) -> Signature: - return Signature.build(q=1) - - def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str, 'SoquetT']: - soqs = bb.add_d(TAcceptsAdjoint(), **soqs) - return soqs - - -def test_t_complexity(): - assert TAcceptsAdjoint().t_complexity().t == 1 - assert Adjoint(TAcceptsAdjoint()).t_complexity().t == 2 - - assert DecomposesIntoTAcceptsAdjoint().t_complexity().t == 1 - assert Adjoint(DecomposesIntoTAcceptsAdjoint()).t_complexity().t == 2 - - assert TDoesNotAcceptAdjoint().t_complexity().t == 3 - assert Adjoint(TDoesNotAcceptAdjoint()).t_complexity().t == 3 - - @pytest.mark.notebook def test_notebook(): qlt_testing.execute_notebook('../Adjoint') diff --git a/qualtran/bloqs/arithmetic/comparison_test.py b/qualtran/bloqs/arithmetic/comparison_test.py index f27e1dee60..368d45d3c0 100644 --- a/qualtran/bloqs/arithmetic/comparison_test.py +++ b/qualtran/bloqs/arithmetic/comparison_test.py @@ -338,7 +338,6 @@ def test_clineardepthgreaterthan_classical_action_unsigned(ctrl, dtype, bitsize) cb = b.decompose_bloq() for c, target in itertools.product(range(2), repeat=2): for (x, y) in itertools.product(range(2**bitsize), repeat=2): - print(f'{c=} {target=} {x=} {y=}') assert b.call_classically(ctrl=c, a=x, b=y, target=target) == cb.call_classically( ctrl=c, a=x, b=y, target=target ) @@ -351,7 +350,6 @@ def test_clineardepthgreaterthan_classical_action_signed(ctrl, bitsize): cb = b.decompose_bloq() for c, target in itertools.product(range(2), repeat=2): for (x, y) in itertools.product(range(-(2 ** (bitsize - 1)), 2 ** (bitsize - 1)), repeat=2): - print(f'{c=} {target=} {x=} {y=}') assert b.call_classically(ctrl=c, a=x, b=y, target=target) == cb.call_classically( ctrl=c, a=x, b=y, target=target ) diff --git a/qualtran/bloqs/arithmetic/controlled_addition.py b/qualtran/bloqs/arithmetic/controlled_addition.py index 49014ec6ad..aa3f744a6f 100644 --- a/qualtran/bloqs/arithmetic/controlled_addition.py +++ b/qualtran/bloqs/arithmetic/controlled_addition.py @@ -35,7 +35,6 @@ from qualtran.bloqs.arithmetic.addition import Add from qualtran.bloqs.bookkeeping import Cast from qualtran.bloqs.mcmt.and_bloq import And -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.simulation.classical_sim import add_ints @@ -156,12 +155,6 @@ def build_composite_bloq( ctrl = bb.join(np.array([ctrl_q])) return {'ctrl': ctrl, 'a': a, 'b': b} - def _t_complexity_(self): - n = self.b_dtype.bitsize - num_and = self.a_dtype.bitsize + self.b_dtype.bitsize - 1 - num_clifford = 33 * (n - 2) + 43 - return TComplexity(t=4 * num_and, clifford=num_clifford) - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: return { (And(self.cv, 1), self.a_dtype.bitsize), diff --git a/qualtran/bloqs/arithmetic/permutation.ipynb b/qualtran/bloqs/arithmetic/permutation.ipynb index 3cb5bd4de2..a2f9dc5d77 100644 --- a/qualtran/bloqs/arithmetic/permutation.ipynb +++ b/qualtran/bloqs/arithmetic/permutation.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "a48878df", + "id": "a697fa0f", "metadata": { "cq.autogen": "title_cell" }, @@ -13,7 +13,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d083d346", + "id": "f3a56020", "metadata": { "cq.autogen": "top_imports" }, @@ -30,7 +30,7 @@ }, { "cell_type": "markdown", - "id": "56539662", + "id": "39be898d", "metadata": { "cq.autogen": "Permutation.bloq_doc.md" }, @@ -62,7 +62,7 @@ { "cell_type": "code", "execution_count": null, - "id": "df73124f", + "id": "88b7712d", "metadata": { "cq.autogen": "Permutation.bloq_doc.py" }, @@ -73,7 +73,7 @@ }, { "cell_type": "markdown", - "id": "31147b0d", + "id": "5c4722c6", "metadata": { "cq.autogen": "Permutation.example_instances.md" }, @@ -84,7 +84,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ec6460b6", + "id": "8e38bf3a", "metadata": { "cq.autogen": "Permutation.permutation" }, @@ -96,7 +96,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0c8e04f0", + "id": "52218544", "metadata": { "cq.autogen": "Permutation.permutation_symb" }, @@ -113,7 +113,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8578d036", + "id": "cb1a6988", "metadata": { "cq.autogen": "Permutation.permutation_symb_with_cycles" }, @@ -132,7 +132,7 @@ { "cell_type": "code", "execution_count": null, - "id": "84b5606a", + "id": "f58b58f7", "metadata": { "cq.autogen": "Permutation.sparse_permutation" }, @@ -143,9 +143,26 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "e644f2ac", + "metadata": { + "cq.autogen": "Permutation.sparse_permutation_with_symbolic_N" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "N = sympy.symbols(\"N\", positive=True, integer=True)\n", + "sparse_permutation_with_symbolic_N = Permutation.from_partial_permutation_map(\n", + " N, {0: 1, 1: 3, 2: 4, 3: 7}\n", + ")" + ] + }, { "cell_type": "markdown", - "id": "369df9cb", + "id": "ace174d5", "metadata": { "cq.autogen": "Permutation.graphical_signature.md" }, @@ -156,20 +173,20 @@ { "cell_type": "code", "execution_count": null, - "id": "25b2dc2b", + "id": "1e55dcd4", "metadata": { "cq.autogen": "Permutation.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([permutation, permutation_symb, permutation_symb_with_cycles, sparse_permutation],\n", - " ['`permutation`', '`permutation_symb`', '`permutation_symb_with_cycles`', '`sparse_permutation`'])" + "show_bloqs([permutation, permutation_symb, permutation_symb_with_cycles, sparse_permutation, sparse_permutation_with_symbolic_N],\n", + " ['`permutation`', '`permutation_symb`', '`permutation_symb_with_cycles`', '`sparse_permutation`', '`sparse_permutation_with_symbolic_N`'])" ] }, { "cell_type": "markdown", - "id": "ad4321ad", + "id": "32e2b5dd", "metadata": { "cq.autogen": "Permutation.call_graph.md" }, @@ -180,7 +197,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3bfbf3f7", + "id": "fab3fd69", "metadata": { "cq.autogen": "Permutation.call_graph.py" }, @@ -194,7 +211,7 @@ }, { "cell_type": "markdown", - "id": "b3e895db", + "id": "408e209c", "metadata": { "cq.autogen": "PermutationCycle.bloq_doc.md" }, @@ -230,7 +247,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d569cd2c", + "id": "bc68fccd", "metadata": { "cq.autogen": "PermutationCycle.bloq_doc.py" }, @@ -241,7 +258,7 @@ }, { "cell_type": "markdown", - "id": "a93c6e89", + "id": "de4c7922", "metadata": { "cq.autogen": "PermutationCycle.example_instances.md" }, @@ -252,19 +269,23 @@ { "cell_type": "code", "execution_count": null, - "id": "264ba946", + "id": "071de1e1", "metadata": { - "cq.autogen": "PermutationCycle.permutation_cycle" + "cq.autogen": "PermutationCycle.permutation_cycle_symb_N" }, "outputs": [], "source": [ - "permutation_cycle = PermutationCycle(4, (0, 1, 2))" + "import sympy\n", + "\n", + "N = sympy.symbols(\"n\", positive=True, integer=True)\n", + "cycle = (3, 1, 2)\n", + "permutation_cycle_symb_N = PermutationCycle(N, cycle)" ] }, { "cell_type": "code", "execution_count": null, - "id": "78572528", + "id": "fd61e92a", "metadata": { "cq.autogen": "PermutationCycle.permutation_cycle_symb" }, @@ -279,9 +300,21 @@ "permutation_cycle_symb = PermutationCycle(N, cycle)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "b818f3b1", + "metadata": { + "cq.autogen": "PermutationCycle.permutation_cycle" + }, + "outputs": [], + "source": [ + "permutation_cycle = PermutationCycle(4, (0, 1, 2))" + ] + }, { "cell_type": "markdown", - "id": "87615783", + "id": "3d3c3e1b", "metadata": { "cq.autogen": "PermutationCycle.graphical_signature.md" }, @@ -292,20 +325,20 @@ { "cell_type": "code", "execution_count": null, - "id": "f6961010", + "id": "de967993", "metadata": { "cq.autogen": "PermutationCycle.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([permutation_cycle, permutation_cycle_symb],\n", - " ['`permutation_cycle`', '`permutation_cycle_symb`'])" + "show_bloqs([permutation_cycle_symb_N, permutation_cycle_symb, permutation_cycle],\n", + " ['`permutation_cycle_symb_N`', '`permutation_cycle_symb`', '`permutation_cycle`'])" ] }, { "cell_type": "markdown", - "id": "16e1c105", + "id": "58deda5c", "metadata": { "cq.autogen": "PermutationCycle.call_graph.md" }, @@ -316,16 +349,16 @@ { "cell_type": "code", "execution_count": null, - "id": "27628f10", + "id": "ec16903f", "metadata": { "cq.autogen": "PermutationCycle.call_graph.py" }, "outputs": [], "source": [ "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "permutation_cycle_g, permutation_cycle_sigma = permutation_cycle.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(permutation_cycle_g)\n", - "show_counts_sigma(permutation_cycle_sigma)" + "permutation_cycle_symb_N_g, permutation_cycle_symb_N_sigma = permutation_cycle_symb_N.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(permutation_cycle_symb_N_g)\n", + "show_counts_sigma(permutation_cycle_symb_N_sigma)" ] } ], diff --git a/qualtran/bloqs/arithmetic/permutation.py b/qualtran/bloqs/arithmetic/permutation.py index 4701e38610..e67b66cc4a 100644 --- a/qualtran/bloqs/arithmetic/permutation.py +++ b/qualtran/bloqs/arithmetic/permutation.py @@ -104,9 +104,6 @@ def signature(self) -> Signature: def bitsize(self): return bit_length(self.N - 1) - def is_symbolic(self): - return is_symbolic(self.N, self.cycle) - def build_composite_bloq(self, bb: 'BloqBuilder', x: 'SoquetT') -> dict[str, 'SoquetT']: if is_symbolic(self.cycle): raise DecomposeTypeError(f"cannot decompose symbolic {self}") @@ -126,7 +123,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: 'SoquetT') -> dict[str, 'So return {'x': x} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: - if self.is_symbolic(): + if is_symbolic(self.cycle): x = ssa.new_symbol('x') cycle_len = slen(self.cycle) return { @@ -143,6 +140,16 @@ def _permutation_cycle() -> PermutationCycle: return permutation_cycle +@bloq_example +def _permutation_cycle_symb_N() -> PermutationCycle: + import sympy + + N = sympy.symbols("n", positive=True, integer=True) + cycle = (3, 1, 2) + permutation_cycle_symb_N = PermutationCycle(N, cycle) + return permutation_cycle_symb_N + + @bloq_example def _permutation_cycle_symb() -> PermutationCycle: import sympy @@ -158,7 +165,7 @@ def _permutation_cycle_symb() -> PermutationCycle: _PERMUTATION_CYCLE_DOC = BloqDocSpec( bloq_cls=PermutationCycle, import_line='from qualtran.bloqs.arithmetic.permutation import PermutationCycle', - examples=[_permutation_cycle, _permutation_cycle_symb], + examples=[_permutation_cycle_symb_N, _permutation_cycle_symb, _permutation_cycle], ) @@ -307,8 +314,25 @@ def _sparse_permutation() -> Permutation: return sparse_permutation +@bloq_example +def _sparse_permutation_with_symbolic_N() -> Permutation: + import sympy + + N = sympy.symbols("N", positive=True, integer=True) + sparse_permutation_with_symbolic_N = Permutation.from_partial_permutation_map( + N, {0: 1, 1: 3, 2: 4, 3: 7} + ) + return sparse_permutation_with_symbolic_N + + _PERMUTATION_DOC = BloqDocSpec( bloq_cls=Permutation, import_line='from qualtran.bloqs.arithmetic.permutation import Permutation', - examples=[_permutation, _permutation_symb, _permutation_symb_with_cycles, _sparse_permutation], + examples=[ + _permutation, + _permutation_symb, + _permutation_symb_with_cycles, + _sparse_permutation, + _sparse_permutation_with_symbolic_N, + ], ) diff --git a/qualtran/bloqs/arithmetic/permutation_test.py b/qualtran/bloqs/arithmetic/permutation_test.py index 2414b97d64..07e2073005 100644 --- a/qualtran/bloqs/arithmetic/permutation_test.py +++ b/qualtran/bloqs/arithmetic/permutation_test.py @@ -24,16 +24,20 @@ # See the License for the specific language governing permissions and # limitations under the License. import numpy as np +import pytest import sympy +import qualtran.testing as qlt_testing from qualtran import QBit from qualtran.bloqs.arithmetic.permutation import ( _permutation, _permutation_cycle, _permutation_cycle_symb, + _permutation_cycle_symb_N, _permutation_symb, _permutation_symb_with_cycles, _sparse_permutation, + _sparse_permutation_with_symbolic_N, Permutation, PermutationCycle, ) @@ -44,16 +48,38 @@ from qualtran.symbolics import ceil, log2, slen -def test_examples(bloq_autotester): - bloq_autotester(_permutation_cycle) - bloq_autotester(_permutation) - bloq_autotester(_sparse_permutation) - - -def test_symbolic_examples(bloq_autotester): - bloq_autotester(_permutation_cycle_symb) - bloq_autotester(_permutation_symb) - bloq_autotester(_permutation_symb_with_cycles) +@pytest.mark.parametrize( + "bloq_ex", + [ + _permutation_cycle, + _permutation, + _sparse_permutation, + _permutation_cycle_symb, + _permutation_cycle_symb_N, + _permutation_symb, + _permutation_symb_with_cycles, + _sparse_permutation_with_symbolic_N, + ], + ids=lambda bloq_ex: bloq_ex.name, +) +def test_examples(bloq_autotester, bloq_ex): + bloq_autotester(bloq_ex) + + +@pytest.mark.parametrize( + "bloq_ex", + [ + _permutation_cycle, + _permutation, + _sparse_permutation, + _permutation_cycle_symb_N, + _permutation_symb_with_cycles, + _sparse_permutation_with_symbolic_N, + ], + ids=lambda bloq_ex: bloq_ex.name, +) +def test_decomposition(bloq_ex): + qlt_testing.assert_valid_bloq_decomposition(bloq_ex.make()) def test_permutation_cycle_unitary_and_call_graph(): diff --git a/qualtran/bloqs/basic_gates/hadamard.py b/qualtran/bloqs/basic_gates/hadamard.py index c3fd0d7916..eca9c99e8b 100644 --- a/qualtran/bloqs/basic_gates/hadamard.py +++ b/qualtran/bloqs/basic_gates/hadamard.py @@ -184,7 +184,7 @@ def _t_complexity_(self) -> 'TComplexity': def my_static_costs(self, cost_key: 'CostKey'): from qualtran.resource_counting import GateCounts, QECGatesCost - if cost_key == QECGatesCost(): + if isinstance(cost_key, QECGatesCost): # This is based on the decomposition provided by `cirq.decompose_multi_controlled_rotation` # which uses three cirq.MatrixGate's to do a controlled version of any single-qubit gate. # The first MatrixGate happens to be a clifford, Hadamard operation in this case. diff --git a/qualtran/bloqs/basic_gates/rotation_test.py b/qualtran/bloqs/basic_gates/rotation_test.py index dbfd317fc1..aa7b31ee2b 100644 --- a/qualtran/bloqs/basic_gates/rotation_test.py +++ b/qualtran/bloqs/basic_gates/rotation_test.py @@ -20,13 +20,28 @@ from qualtran._infra.gate_with_registers import get_named_qubits from qualtran.bloqs.basic_gates import CZPowGate, Rx, Ry, Rz, XPowGate, YPowGate, ZPowGate from qualtran.bloqs.basic_gates.rotation import _rx, _ry, _rz +from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost +from qualtran.resource_counting.classify_bloqs import bloq_is_rotation, bloq_is_t_like -def test_rotation_gates(): +def test_t_like_rotation_gates(): angle = np.pi / 4.0 - tcount = 52 - assert Rx(angle).t_complexity().t_incl_rotations() == tcount - assert Ry(angle).t_complexity().t_incl_rotations() == tcount + # In prior versions of the library, only Rz(pi/4) would simplify to a T gate in gate counts. + # The others would report the synthesis cost for an arbitrary angle, which was reported as + # 52 T-gates. + assert not bloq_is_rotation(Rx(angle)) + assert not bloq_is_rotation(Ry(angle)) + assert not bloq_is_rotation(Rz(angle)) + assert bloq_is_t_like(Rx(angle)) + assert bloq_is_t_like(Ry(angle)) + assert bloq_is_t_like(Rz(angle)) + + assert get_cost_value(Rx(angle), QECGatesCost()) == GateCounts(t=1) + assert get_cost_value(Ry(angle), QECGatesCost()) == GateCounts(t=1) + assert get_cost_value(Rz(angle), QECGatesCost()) == GateCounts(t=1) + + assert Rx(angle).t_complexity().t_incl_rotations() == 1 + assert Ry(angle).t_complexity().t_incl_rotations() == 1 assert Rz(angle).t_complexity().t_incl_rotations() == 1 diff --git a/qualtran/bloqs/for_testing/atom.py b/qualtran/bloqs/for_testing/atom.py index 7c04867b66..a52273dee2 100644 --- a/qualtran/bloqs/for_testing/atom.py +++ b/qualtran/bloqs/for_testing/atom.py @@ -68,7 +68,7 @@ def my_tensors( ] def my_static_costs(self, cost_key: 'CostKey'): - if cost_key == QECGatesCost(): + if isinstance(cost_key, QECGatesCost): return GateCounts(t=100) return NotImplemented diff --git a/qualtran/bloqs/state_preparation/state_preparation_via_rotation.ipynb b/qualtran/bloqs/state_preparation/state_preparation_via_rotation.ipynb index e5349906fa..ee713c6e8b 100644 --- a/qualtran/bloqs/state_preparation/state_preparation_via_rotation.ipynb +++ b/qualtran/bloqs/state_preparation/state_preparation_via_rotation.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "d317cae7", + "id": "2568299e", "metadata": { "cq.autogen": "title_cell" }, @@ -72,7 +72,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8503ea6a", + "id": "4f761ae7", "metadata": { "cq.autogen": "top_imports" }, @@ -89,7 +89,7 @@ }, { "cell_type": "markdown", - "id": "7e8c4520", + "id": "17790c77", "metadata": { "cq.autogen": "StatePreparationViaRotations.bloq_doc.md" }, @@ -118,7 +118,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f1e3b82f", + "id": "55c81370", "metadata": { "cq.autogen": "StatePreparationViaRotations.bloq_doc.py" }, @@ -129,7 +129,7 @@ }, { "cell_type": "markdown", - "id": "5f208fa8", + "id": "d8c2407c", "metadata": { "cq.autogen": "StatePreparationViaRotations.example_instances.md" }, @@ -140,7 +140,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c68e5fd9", + "id": "cd0552db", "metadata": { "cq.autogen": "StatePreparationViaRotations.state_prep_via_rotation" }, @@ -164,7 +164,7 @@ { "cell_type": "code", "execution_count": null, - "id": "88379aff", + "id": "a52e9cf3", "metadata": { "cq.autogen": "StatePreparationViaRotations.state_prep_via_rotation_symb" }, @@ -179,9 +179,35 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "5446822b", + "metadata": { + "cq.autogen": "StatePreparationViaRotations.state_prep_via_rotation_symb_phasegrad" + }, + "outputs": [], + "source": [ + "state_coefs = (\n", + " (-0.42677669529663675 - 0.1767766952966366j),\n", + " (0.17677669529663664 - 0.4267766952966367j),\n", + " (0.17677669529663675 - 0.1767766952966368j),\n", + " (0.07322330470336305 - 0.07322330470336309j),\n", + " (0.4267766952966366 - 0.17677669529663692j),\n", + " (0.42677669529663664 + 0.17677669529663675j),\n", + " (0.0732233047033631 + 0.17677669529663678j),\n", + " (-0.07322330470336308 - 0.17677669529663678j),\n", + ")\n", + "\n", + "phase_bitsize = sympy.Symbol(r\"b_\\text{grad}\")\n", + "state_prep_via_rotation_symb_phasegrad = StatePreparationViaRotations(\n", + " state_coefficients=state_coefs, phase_bitsize=phase_bitsize\n", + ")" + ] + }, { "cell_type": "markdown", - "id": "a2669c47", + "id": "facf9536", "metadata": { "cq.autogen": "StatePreparationViaRotations.graphical_signature.md" }, @@ -192,20 +218,20 @@ { "cell_type": "code", "execution_count": null, - "id": "09a897a8", + "id": "2d801788", "metadata": { "cq.autogen": "StatePreparationViaRotations.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([state_prep_via_rotation, state_prep_via_rotation_symb],\n", - " ['`state_prep_via_rotation`', '`state_prep_via_rotation_symb`'])" + "show_bloqs([state_prep_via_rotation, state_prep_via_rotation_symb, state_prep_via_rotation_symb_phasegrad],\n", + " ['`state_prep_via_rotation`', '`state_prep_via_rotation_symb`', '`state_prep_via_rotation_symb_phasegrad`'])" ] }, { "cell_type": "markdown", - "id": "6aa59344", + "id": "c1aeb064", "metadata": { "cq.autogen": "StatePreparationViaRotations.call_graph.md" }, @@ -216,7 +242,7 @@ { "cell_type": "code", "execution_count": null, - "id": "939bd8f8", + "id": "a6fee1d3", "metadata": { "cq.autogen": "StatePreparationViaRotations.call_graph.py" }, @@ -236,16 +262,7 @@ "name": "python3" }, "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.0" + "name": "python" } }, "nbformat": 4, diff --git a/qualtran/bloqs/state_preparation/state_preparation_via_rotation.py b/qualtran/bloqs/state_preparation/state_preparation_via_rotation.py index 6a668dabbb..5fdf8a8e4e 100644 --- a/qualtran/bloqs/state_preparation/state_preparation_via_rotation.py +++ b/qualtran/bloqs/state_preparation/state_preparation_via_rotation.py @@ -355,9 +355,33 @@ def _state_prep_via_rotation_symb() -> StatePreparationViaRotations: return state_prep_via_rotation_symb +@bloq_example +def _state_prep_via_rotation_symb_phasegrad() -> StatePreparationViaRotations: + state_coefs = ( + (-0.42677669529663675 - 0.1767766952966366j), + (0.17677669529663664 - 0.4267766952966367j), + (0.17677669529663675 - 0.1767766952966368j), + (0.07322330470336305 - 0.07322330470336309j), + (0.4267766952966366 - 0.17677669529663692j), + (0.42677669529663664 + 0.17677669529663675j), + (0.0732233047033631 + 0.17677669529663678j), + (-0.07322330470336308 - 0.17677669529663678j), + ) + + phase_bitsize = sympy.Symbol(r"b_\text{grad}") + state_prep_via_rotation_symb_phasegrad = StatePreparationViaRotations( + state_coefficients=state_coefs, phase_bitsize=phase_bitsize + ) + return state_prep_via_rotation_symb_phasegrad + + _STATE_PREP_VIA_ROTATIONS_DOC = BloqDocSpec( bloq_cls=StatePreparationViaRotations, - examples=(_state_prep_via_rotation, _state_prep_via_rotation_symb), + examples=( + _state_prep_via_rotation, + _state_prep_via_rotation_symb, + _state_prep_via_rotation_symb_phasegrad, + ), ) diff --git a/qualtran/bloqs/state_preparation/state_preparation_via_rotation_test.py b/qualtran/bloqs/state_preparation/state_preparation_via_rotation_test.py index dfca14355c..6b7a770579 100644 --- a/qualtran/bloqs/state_preparation/state_preparation_via_rotation_test.py +++ b/qualtran/bloqs/state_preparation/state_preparation_via_rotation_test.py @@ -24,6 +24,7 @@ from qualtran.bloqs.state_preparation.state_preparation_via_rotation import ( _state_prep_via_rotation, _state_prep_via_rotation_symb, + _state_prep_via_rotation_symb_phasegrad, PRGAViaPhaseGradient, StatePreparationViaRotations, ) @@ -34,8 +35,17 @@ def accuracy(state1, state2): return abs(np.dot(state1, state2.conj())) -def test_state_prep_via_rotation(bloq_autotester): - bloq_autotester(_state_prep_via_rotation) +@pytest.mark.parametrize( + "bloq_ex", + [ + _state_prep_via_rotation, + _state_prep_via_rotation_symb, + _state_prep_via_rotation_symb_phasegrad, + ], + ids=lambda bloq_ex: bloq_ex.name, +) +def test_state_prep_via_rotation(bloq_autotester, bloq_ex): + bloq_autotester(bloq_ex) def test_state_prep_via_rotation_symb_quick(): diff --git a/qualtran/cirq_interop/_cirq_to_bloq.py b/qualtran/cirq_interop/_cirq_to_bloq.py index 01192717ee..078051bb1b 100644 --- a/qualtran/cirq_interop/_cirq_to_bloq.py +++ b/qualtran/cirq_interop/_cirq_to_bloq.py @@ -48,6 +48,7 @@ ) from qualtran.cirq_interop._interop_qubit_manager import InteropQubitManager from qualtran.cirq_interop.t_complexity_protocol import _from_directly_countable_cirq, TComplexity +from qualtran.resource_counting import CostKey, GateCounts, QECGatesCost if TYPE_CHECKING: import quimb.tensor as qtn @@ -108,12 +109,6 @@ def my_tensors( self.cirq_gate, self.signature, incoming=incoming, outgoing=outgoing ) - def _t_complexity_(self) -> 'TComplexity': - t_count = _from_directly_countable_cirq(self.cirq_gate) - if t_count is None: - raise ValueError(f"Cirq gate must be directly countable, not {self.cirq_gate}") - return t_count - def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', **in_quregs: 'CirqQuregT' ) -> Tuple[Union['cirq.Operation', None], Dict[str, 'CirqQuregT']]: @@ -153,6 +148,19 @@ def pretty_name(self) -> str: def cirq_gate(self) -> cirq.Gate: return self.gate + def _t_complexity_(self) -> 'TComplexity': + t_count = _from_directly_countable_cirq(self.cirq_gate) + if t_count is None: + raise ValueError(f"Cirq gate must be directly countable, not {self.cirq_gate}") + return t_count + + def my_static_costs(self, cost_key: 'CostKey'): + if isinstance(cost_key, QECGatesCost): + t_count = _from_directly_countable_cirq(self.cirq_gate) + if t_count is None: + raise ValueError(f"Cirq gate must be directly countable, not {self.cirq_gate}") + return GateCounts(t=t_count.t, rotation=t_count.rotations, clifford=t_count.clifford) + def _cirq_wire_symbol_to_qualtran_wire_symbol(symbol: str, side: Side) -> 'WireSymbol': from qualtran.drawing import Circle, directional_text_box, ModPlus diff --git a/qualtran/cirq_interop/t_complexity_protocol.py b/qualtran/cirq_interop/t_complexity_protocol.py index 6e96057fd5..7f7c4748b8 100644 --- a/qualtran/cirq_interop/t_complexity_protocol.py +++ b/qualtran/cirq_interop/t_complexity_protocol.py @@ -61,6 +61,9 @@ def __mul__(self, other: int) -> 'TComplexity': def __rmul__(self, other: int) -> 'TComplexity': return self.__mul__(other) + def asdict(self): + return {'t': self.t, 'rotations': self.rotations, 'clifford': self.clifford} + def __str__(self) -> str: return ( f'T-count: {self.t:g}\n' @@ -236,6 +239,9 @@ def _t_complexity_for_bloq(bloq: Bloq) -> Optional[TComplexity]: return _t_complexity_from_strategies(bloq, strategies) +USE_NEW_GATE_COUNTING_FLAG = True + + def t_complexity(bloq: Bloq) -> TComplexity: """Returns the TComplexity of a bloq. @@ -248,6 +254,11 @@ def t_complexity(bloq: Bloq) -> TComplexity: Raises: TypeError: if none of the strategies can derive the t complexity. """ + if USE_NEW_GATE_COUNTING_FLAG: + from qualtran.resource_counting import get_cost_value, QECGatesCost + + return get_cost_value(bloq, QECGatesCost(legacy_shims=True)).to_legacy_t_complexity() + ret = _t_complexity_for_bloq(bloq) if ret is None: raise TypeError( diff --git a/qualtran/cirq_interop/t_complexity_protocol_test.py b/qualtran/cirq_interop/t_complexity_protocol_test.py index 1b54a320db..2572b85ec3 100644 --- a/qualtran/cirq_interop/t_complexity_protocol_test.py +++ b/qualtran/cirq_interop/t_complexity_protocol_test.py @@ -17,7 +17,7 @@ import pytest from attrs import frozen -from qualtran import Bloq, GateWithRegisters, Signature +from qualtran import Bloq, DecomposeNotImplementedError, GateWithRegisters, Signature from qualtran._infra.gate_with_registers import get_named_qubits from qualtran.bloqs.basic_gates import CHadamard, GlobalPhase from qualtran.bloqs.mcmt.and_bloq import And @@ -85,7 +85,7 @@ def test_t_complexity_for_bloq_via_build_call_graph(): def test_t_complexity_for_bloq_does_not_support(): - with pytest.raises(TypeError): + with pytest.raises(DecomposeNotImplementedError): _ = t_complexity(DoesNotSupportTComplexityBloq()) @@ -213,45 +213,6 @@ def test_tagged_operations(): ) -def test_cache_clear(): - class Cachable1(Bloq): - def __init__(self) -> None: - self.num_calls = 0 - - @property - def signature(self) -> 'Signature': - return Signature([]) - - def _t_complexity_(self) -> TComplexity: - self.num_calls += 1 - return TComplexity(clifford=1) - - def __hash__(self): - # Manufacture a hash collision - return hash(2) - - class Cachable2(Cachable1): - def _t_complexity_(self) -> TComplexity: - self.num_calls += 1 - return TComplexity() - - def __hash__(self): - # Manufacture a hash collision - return hash(2) - - assert t_complexity(Cachable1()) == TComplexity(clifford=1) - # Using a global cache will result in a failure of this test since `cirq.X` has - # `T-complexity(clifford=1)` but we explicitly return `TComplexity()` for IsCachable - # operation; for which the hash would be equivalent to the hash of its subgate i.e. `cirq.X`. - # TODO: t_complexity protocol will be refactored (#735) - t_complexity.cache_clear() # type: ignore[attr-defined] - op = Cachable2() - assert t_complexity(op) == TComplexity() - assert t_complexity(op) == TComplexity() - assert op.num_calls == 1 - t_complexity.cache_clear() # type: ignore[attr-defined] - - @pytest.mark.notebook def test_notebook(): execute_notebook('t_complexity') diff --git a/qualtran/conftest.py b/qualtran/conftest.py index bc15b19d40..36998fd67b 100644 --- a/qualtran/conftest.py +++ b/qualtran/conftest.py @@ -111,6 +111,12 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'sparse_state_prep_alias_symb', # cannot serialize Shaped 'sparse_permutation', # contains nested tuple of inhomogeneous shape 'permutation_cycle_symb', # cannot serialize Shaped + 'permutation_cycle_symb_N', # sympy variable assumptions dropped by serialized + 'permutation_symb', # cannot serialize shaped + 'permutation_symb_with_cycles', # Object arrays cannot be saved when allow_pickle=False + 'sparse_permutation_with_symbolic_N', # setting an array element with a sequence. + 'state_prep_via_rotation_symb', # cannot serialize HasLength + 'state_prep_via_rotation_symb_phasegrad', # cannot serialize Shaped 'sparse_state_prep_via_rotations', # cannot serialize Permutation 'explicit_matrix_block_encoding', # cannot serialize AutoPartition 'symmetric_banded_matrix_block_encoding', # cannot serialize AutoPartition diff --git a/qualtran/drawing/bloq_counts_graph.py b/qualtran/drawing/bloq_counts_graph.py index dbadfa84e9..09bb97c0c7 100644 --- a/qualtran/drawing/bloq_counts_graph.py +++ b/qualtran/drawing/bloq_counts_graph.py @@ -241,6 +241,7 @@ def format_qec_gates_cost(cls, val: 'GateCounts', agg: Optional[str] = None) -> 'and_bloq': 'Ands', 'clifford': 'Cliffords', 'rotation': 'Rotations', + 'rotations': 'Rotations', 'measurement': 'Measurements', } counts_dict: Mapping[str, SymbolicInt] @@ -252,6 +253,8 @@ def format_qec_gates_cost(cls, val: 'GateCounts', agg: Optional[str] = None) -> counts_dict = val.total_t_and_ccz_count() elif agg == 'beverland': counts_dict = val.total_beverland_count() + elif agg == 'legacy': + counts_dict = val.to_legacy_t_complexity().asdict() else: raise ValueError(f"Unknown aggregation mode {agg}.") diff --git a/qualtran/resource_counting/_bloq_counts.py b/qualtran/resource_counting/_bloq_counts.py index 95cf98d691..b3995b0cec 100644 --- a/qualtran/resource_counting/_bloq_counts.py +++ b/qualtran/resource_counting/_bloq_counts.py @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging +import warnings from collections import defaultdict -from typing import Callable, Dict, Sequence, Tuple, TYPE_CHECKING +from typing import Callable, cast, Dict, Sequence, Tuple, TYPE_CHECKING import attrs import networkx as nx @@ -33,6 +34,7 @@ if TYPE_CHECKING: from qualtran import Bloq + from qualtran.cirq_interop.t_complexity_protocol import TComplexity logger = logging.getLogger(__name__) @@ -209,6 +211,38 @@ def total_t_and_ccz_count(self, ts_per_rotation: int = 11) -> Dict[str, Symbolic n_t = self.t + ts_per_rotation * self.rotation return {'n_t': n_t, 'n_ccz': n_ccz} + def to_legacy_t_complexity( + self, + ts_per_toffoli: int = 4, + ts_per_cswap: int = 7, + ts_per_and_bloq: int = 4, + cliffords_per_and_bloq: int = 9, + cliffords_per_cswap: int = 10, + ) -> 'TComplexity': + """Return a legacy `TComplexity` object. + + This coalesces all the gate types into t, rotations, and clifford fields. The conversion + factors can be tweaked using the arguments to this method. + + The argument `cliffords_per_and_bloq` sets the base number of clifford gates to + add per `self.and_bloq`. To fully match the exact legacy `t_complexity` numbers, you + must enable `QECGatesCost(legacy_shims=True)`, which will enable a shim that directly + adds on clifford counts for the X-gates used to invert the And control lines. + """ + from qualtran.cirq_interop.t_complexity_protocol import TComplexity + + return TComplexity( + t=self.t + + ts_per_toffoli * self.toffoli + + ts_per_cswap * self.cswap + + ts_per_and_bloq * self.and_bloq, + rotations=cast(int, self.rotation), + clifford=self.clifford + + self.measurement + + cliffords_per_and_bloq * self.and_bloq + + cliffords_per_cswap * self.cswap, + ) + def total_beverland_count(self) -> Dict[str, SymbolicInt]: r"""Counts used by Beverland. et. al. using notation from the reference. @@ -235,18 +269,36 @@ def total_beverland_count(self) -> Dict[str, SymbolicInt]: } -@frozen +@frozen(kw_only=True) class QECGatesCost(CostKey[GateCounts]): """Counts specifically for 'expensive' gates in a surface code error correction scheme. The cost value type for this CostKey is `GateCounts`. + + Args: + legacy_shims: If enabled, modify the counting logic to match the peculiarities of + the legacy `t_complexity` protocol. """ + legacy_shims: bool = False + def compute(self, bloq: 'Bloq', get_callee_cost: Callable[['Bloq'], GateCounts]) -> GateCounts: from qualtran.bloqs.basic_gates import GlobalPhase, Identity, Toffoli, TwoBitCSwap from qualtran.bloqs.basic_gates._shims import Measure from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq - from qualtran.bloqs.mcmt.and_bloq import And + from qualtran.bloqs.mcmt import And, MultiTargetCNOT + + if self.legacy_shims: + legacy_val = bloq._t_complexity_() + if legacy_val is not NotImplemented: + warnings.warn( + "Please migrate explicit cost annotations to the general " + "`Bloq.my_static_costs` method override.", + DeprecationWarning, + ) + return GateCounts( + t=legacy_val.t, clifford=legacy_val.clifford, rotation=legacy_val.rotations + ) # T gates if bloq_is_t_like(bloq): @@ -262,14 +314,36 @@ def compute(self, bloq: 'Bloq', get_callee_cost: Callable[['Bloq'], GateCounts]) # 'And' bloqs if isinstance(bloq, And): + # To match the legacy `t_complexity` protocol, we can hack in the explicit + # counts for the clifford operations used to invert the control bit. + # Note: we *only* add in the clifford operations that correspond to correctly + # setting the control line. The other clifford operations inherent in compiling + # an And gate to the gateset considered by the legacy `t_complexity` protocol can be + # simply added in as part of `GateCounts.to_legacy_t_complexity()` + n_inverted_controls = (bloq.cv1 == 0) + int(bloq.cv2 == 0) if bloq.uncompute: - return GateCounts(measurement=1, clifford=1) - return GateCounts(and_bloq=1) + if self.legacy_shims: + return GateCounts(clifford=3 + 2 * n_inverted_controls, measurement=1) + else: + return GateCounts(measurement=1, clifford=1) + + if self.legacy_shims: + return GateCounts(and_bloq=1, clifford=2 * n_inverted_controls) + else: + return GateCounts(and_bloq=1) # CSwaps aka Fredkin if isinstance(bloq, TwoBitCSwap): return GateCounts(cswap=1) + if isinstance(bloq, MultiTargetCNOT): + # TODO(https://github.com/quantumlib/Qualtran/issues/1318): Decide how to count this. + if self.legacy_shims: + # Legacy mode: don't treat this as one clifford. Use its decomposition. + pass # fall through + else: + return GateCounts(clifford=1) + # Cliffords if bloq_is_clifford(bloq): return GateCounts(clifford=1) diff --git a/qualtran/resource_counting/_bloq_counts_test.py b/qualtran/resource_counting/_bloq_counts_test.py index 6688459a64..8c2328c5cc 100644 --- a/qualtran/resource_counting/_bloq_counts_test.py +++ b/qualtran/resource_counting/_bloq_counts_test.py @@ -18,6 +18,8 @@ from qualtran.bloqs.basic_gates import Hadamard, TGate, Toffoli from qualtran.bloqs.basic_gates._shims import Measure from qualtran.bloqs.for_testing.costing import make_example_costing_bloqs +from qualtran.bloqs.mcmt import MultiTargetCNOT +from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting import BloqCount, GateCounts, get_cost_value, QECGatesCost @@ -91,5 +93,17 @@ def test_qec_gates_cost(): [mcmt.MultiControlX(cvs=(1, 1, 1)), GateCounts(and_bloq=2, measurement=2, clifford=3)], ], ) -def test_algorithm_summary_counts(bloq, counts): +def test_get_cost_value_qec_gates_cost(bloq, counts): assert get_cost_value(bloq, QECGatesCost()) == counts + + +def test_count_multi_target_cnot(): + b = MultiTargetCNOT(bitsize=12) + + # MultiTargetCNOT can be done in one clifford cycle on the surface code. + assert get_cost_value(b, QECGatesCost()) == GateCounts(clifford=1) + + # And/or we could respect its decomposition. + # TODO: https://github.com/quantumlib/Qualtran/issues/1318 + assert get_cost_value(b, QECGatesCost(legacy_shims=True)) == GateCounts(clifford=23) + assert b.t_complexity() == TComplexity(clifford=23) diff --git a/qualtran/resource_counting/classify_bloqs.py b/qualtran/resource_counting/classify_bloqs.py index 60ab826e69..eae56e52e5 100644 --- a/qualtran/resource_counting/classify_bloqs.py +++ b/qualtran/resource_counting/classify_bloqs.py @@ -182,26 +182,12 @@ def bloq_is_clifford(b: Bloq) -> bool: ) from qualtran.bloqs.basic_gates.rotation import Rx, Ry, Rz, XPowGate, YPowGate, ZPowGate from qualtran.bloqs.bookkeeping import ArbitraryClifford - from qualtran.bloqs.mcmt.multi_target_cnot import MultiTargetCNOT if isinstance(b, Adjoint): b = b.subbloq if isinstance( - b, - ( - TwoBitSwap, - Hadamard, - XGate, - ZGate, - YGate, - ArbitraryClifford, - CNOT, - MultiTargetCNOT, - CYGate, - CZ, - SGate, - ), + b, (TwoBitSwap, Hadamard, XGate, ZGate, YGate, ArbitraryClifford, CNOT, CYGate, CZ, SGate) ): return True diff --git a/qualtran/serialization/bloq_test.py b/qualtran/serialization/bloq_test.py index fffd55bed3..5964908ee4 100644 --- a/qualtran/serialization/bloq_test.py +++ b/qualtran/serialization/bloq_test.py @@ -28,6 +28,7 @@ from qualtran.cirq_interop._cirq_to_bloq_test import TestCNOT as TestCNOTCirq from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.protos import registers_pb2 +from qualtran.resource_counting import CostKey, GateCounts, QECGatesCost from qualtran.serialization import bloq as bloq_serialization from qualtran.serialization import resolver_dict from qualtran.serialization.bloq import arg_from_proto @@ -98,6 +99,10 @@ def signature(self) -> 'Signature': def _t_complexity_(self) -> TComplexity: return TComplexity(t=7 * self.bitsize, clifford=10 * self.bitsize) + def my_static_costs(self, cost_key: 'CostKey'): + if isinstance(cost_key, QECGatesCost): + return GateCounts(t=7 * self.bitsize, clifford=10 * self.bitsize) + @dataclasses.dataclass(frozen=True) class TestTwoCSwap(Bloq): @@ -130,6 +135,7 @@ def test_cbloq_to_proto_test_two_cswap(): assert TestCSwap(bitsize) in bloq_serialization.bloqs_from_proto(cswap_proto_lib) cswap_proto = bloq_serialization.bloqs_to_proto(TestCSwap(100)).table[0].bloq + assert TestCSwap(100).t_complexity().t == 7 * 100 cbloq = TestTwoCSwap(100).decompose_bloq() proto_lib = bloq_serialization.bloqs_to_proto(cbloq) assert len(proto_lib.table) == 2