From 7cd87f06f4b11fcd4773d8124edf0f3bab177ef6 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Fri, 11 Oct 2024 11:00:34 -0700 Subject: [PATCH 1/2] Remove _t_complexity_ --- qualtran/_infra/adjoint.py | 18 ---- qualtran/_infra/bloq.py | 3 - .../conversions/contiguous_index.py | 5 - qualtran/bloqs/basic_gates/cnot.py | 4 - qualtran/bloqs/basic_gates/global_phase.py | 4 - qualtran/bloqs/basic_gates/hadamard.py | 12 --- qualtran/bloqs/basic_gates/identity.py | 4 - qualtran/bloqs/basic_gates/rotation.py | 6 -- qualtran/bloqs/basic_gates/s_gate.py | 4 - qualtran/bloqs/basic_gates/swap.py | 4 - qualtran/bloqs/basic_gates/toffoli.py | 4 - qualtran/bloqs/basic_gates/x_basis.py | 4 - qualtran/bloqs/basic_gates/y_gate.py | 4 - qualtran/bloqs/basic_gates/z_basis.py | 4 - .../bloqs/bookkeeping/_bookkeeping_bloq.py | 4 - .../bloqs/bookkeeping/arbitrary_clifford.py | 4 - qualtran/bloqs/for_testing/atom.py | 10 +- qualtran/bloqs/mcmt/and_bloq.py | 14 --- qualtran/cirq_interop/_bloq_to_cirq.py | 4 - qualtran/cirq_interop/_bloq_to_cirq_test.py | 3 - qualtran/cirq_interop/_cirq_to_bloq.py | 8 +- .../cirq_interop/t_complexity_protocol.py | 101 +----------------- .../t_complexity_protocol_test.py | 23 +--- qualtran/cirq_interop/testing.py | 22 ++-- qualtran/cirq_interop/testing_test.py | 26 +---- qualtran/resource_counting/_bloq_counts.py | 13 --- qualtran/serialization/bloq_test.py | 4 - 27 files changed, 25 insertions(+), 291 deletions(-) diff --git a/qualtran/_infra/adjoint.py b/qualtran/_infra/adjoint.py index 4fa14ff4e..675592287 100644 --- a/qualtran/_infra/adjoint.py +++ b/qualtran/_infra/adjoint.py @@ -181,21 +181,3 @@ def wire_symbol( return self.subbloq.wire_symbol(reg=None).adjoint() return self.subbloq.wire_symbol(reg=reg.adjoint(), idx=idx).adjoint() - - def _t_complexity_(self): - """The cirq-style `_t_complexity_` delegates to the subbloq's method with a special shim. - - The cirq-style t complexity protocol does not leverage the heirarchical decomposition - of high-level bloqs, so we need to shim in an extra `adjoint` boolean flag. - """ - # TODO: https://github.com/quantumlib/Qualtran/issues/735 - if not hasattr(self.subbloq, '_t_complexity_'): - return NotImplemented - - try: - return self.subbloq._t_complexity_(adjoint=True) # type: ignore[call-arg] - except TypeError as e: - if 'adjoint' in str(e): - return self.subbloq._t_complexity_() - else: - raise e diff --git a/qualtran/_infra/bloq.py b/qualtran/_infra/bloq.py index 880224f38..7ccd20037 100644 --- a/qualtran/_infra/bloq.py +++ b/qualtran/_infra/bloq.py @@ -432,9 +432,6 @@ def t_complexity(self) -> 'TComplexity': return t_complexity(self) - def _t_complexity_(self) -> 'TComplexity': - return NotImplemented - def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', **cirq_quregs: 'CirqQuregT' ) -> Tuple[Union['cirq.Operation', None], Dict[str, 'CirqQuregT']]: diff --git a/qualtran/bloqs/arithmetic/conversions/contiguous_index.py b/qualtran/bloqs/arithmetic/conversions/contiguous_index.py index 5444e62d3..79f552805 100644 --- a/qualtran/bloqs/arithmetic/conversions/contiguous_index.py +++ b/qualtran/bloqs/arithmetic/conversions/contiguous_index.py @@ -18,7 +18,6 @@ from qualtran import Bloq, bloq_example, BloqDocSpec, QUInt, Register, Signature from qualtran.bloqs.basic_gates import Toffoli -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import WireSymbol from qualtran.drawing.musical_score import Text, TextBox @@ -70,10 +69,6 @@ def on_classical_vals( ) -> Dict[str, 'ClassicalValT']: return {'mu': mu, 'nu': nu, 's': nu * (nu + 1) // 2 + mu} - def _t_complexity_(self) -> 'TComplexity': - num_toffoli = self.bitsize**2 + self.bitsize - 1 - return TComplexity(t=4 * num_toffoli) - def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> WireSymbol: if reg is None: return Text('') diff --git a/qualtran/bloqs/basic_gates/cnot.py b/qualtran/bloqs/basic_gates/cnot.py index bf19f090c..a6e762803 100644 --- a/qualtran/bloqs/basic_gates/cnot.py +++ b/qualtran/bloqs/basic_gates/cnot.py @@ -33,7 +33,6 @@ Signature, SoquetT, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, ModPlus, Text, WireSymbol if TYPE_CHECKING: @@ -132,9 +131,6 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) - return ModPlus() raise ValueError(f'Unknown wire symbol register name: {reg.name}') - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) - @bloq_example def _cnot() -> CNOT: diff --git a/qualtran/bloqs/basic_gates/global_phase.py b/qualtran/bloqs/basic_gates/global_phase.py index 7fcf2bafd..cae67a2fa 100644 --- a/qualtran/bloqs/basic_gates/global_phase.py +++ b/qualtran/bloqs/basic_gates/global_phase.py @@ -32,7 +32,6 @@ ) from qualtran.bloqs.basic_gates.rotation import ZPowGate from qualtran.cirq_interop import CirqGateAsBloqBase -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.symbolics import pi, sarg, sexp, SymbolicComplex, SymbolicFloat if TYPE_CHECKING: @@ -109,9 +108,6 @@ def _add_ctrled( def __str__(self) -> str: return f'GPhase({self.coefficient})' - def _t_complexity_(self) -> 'TComplexity': - return TComplexity() - @bloq_example def _global_phase() -> GlobalPhase: diff --git a/qualtran/bloqs/basic_gates/hadamard.py b/qualtran/bloqs/basic_gates/hadamard.py index 5dc55c20a..5efa9c2f5 100644 --- a/qualtran/bloqs/basic_gates/hadamard.py +++ b/qualtran/bloqs/basic_gates/hadamard.py @@ -32,7 +32,6 @@ Signature, SoquetT, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, Text, TextBox, WireSymbol if TYPE_CHECKING: @@ -106,9 +105,6 @@ def as_cirq_op( (q,) = q return cirq.H(q), {'q': np.array([q])} - def _t_complexity_(self): - return TComplexity(clifford=1) - def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': if reg is None: return Text('') @@ -175,14 +171,6 @@ def as_cirq_op( 'target': np.array([target]), } - def _t_complexity_(self) -> 'TComplexity': - # 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. - # The other two are considered 'rotations'. - # https://github.com/quantumlib/Qualtran/issues/237 - return TComplexity(rotations=2, clifford=4) - def my_static_costs(self, cost_key: 'CostKey'): from qualtran.resource_counting import GateCounts, QECGatesCost diff --git a/qualtran/bloqs/basic_gates/identity.py b/qualtran/bloqs/basic_gates/identity.py index 434cf1b8b..c5eeafa1a 100644 --- a/qualtran/bloqs/basic_gates/identity.py +++ b/qualtran/bloqs/basic_gates/identity.py @@ -32,7 +32,6 @@ Signature, SoquetT, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Text, TextBox, WireSymbol from qualtran.symbolics import is_symbolic, SymbolicInt @@ -89,9 +88,6 @@ def as_cirq_op( return cirq.IdentityGate(self.bitsize).on(*q), {'q': q} - def _t_complexity_(self): - return TComplexity() - def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': if reg is None: return Text('') diff --git a/qualtran/bloqs/basic_gates/rotation.py b/qualtran/bloqs/basic_gates/rotation.py index 28a0c9bb3..9adb439e1 100644 --- a/qualtran/bloqs/basic_gates/rotation.py +++ b/qualtran/bloqs/basic_gates/rotation.py @@ -22,7 +22,6 @@ from qualtran import bloq_example, BloqDocSpec, CompositeBloq, DecomposeTypeError, Register from qualtran.cirq_interop import CirqGateAsBloqBase -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Text, TextBox, WireSymbol from qualtran.symbolics import SymbolicFloat @@ -120,11 +119,6 @@ def decompose_bloq(self) -> 'CompositeBloq': def cirq_gate(self) -> cirq.Gate: return cirq.CZPowGate(exponent=self.exponent, global_shift=self.global_shift) - def _t_complexity_(self) -> 'TComplexity': - if cirq.has_stabilizer_effect(self.cirq_gate): - return TComplexity(clifford=1) - return TComplexity(rotations=1) - def __pow__(self, power): g = self.cirq_gate**power return CZPowGate(g.exponent, g.global_shift, self.eps) diff --git a/qualtran/bloqs/basic_gates/s_gate.py b/qualtran/bloqs/basic_gates/s_gate.py index b6a4d9678..3971f59d0 100644 --- a/qualtran/bloqs/basic_gates/s_gate.py +++ b/qualtran/bloqs/basic_gates/s_gate.py @@ -20,7 +20,6 @@ from attrs import frozen from qualtran import Bloq, bloq_example, BloqDocSpec, ConnectionT, Register, Signature -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Text, TextBox, WireSymbol if TYPE_CHECKING: @@ -57,9 +56,6 @@ class SGate(Bloq): def signature(self) -> 'Signature': return Signature.build(q=1) - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) - def my_tensors( self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] ) -> List['qtn.Tensor']: diff --git a/qualtran/bloqs/basic_gates/swap.py b/qualtran/bloqs/basic_gates/swap.py index 62b104f26..84aaf666a 100644 --- a/qualtran/bloqs/basic_gates/swap.py +++ b/qualtran/bloqs/basic_gates/swap.py @@ -36,7 +36,6 @@ SoquetT, ) from qualtran.cirq_interop import CirqQuregT -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, Text, TextBox, WireSymbol from qualtran.resource_counting.generalizers import ignore_split_join @@ -84,9 +83,6 @@ def as_cirq_op( (y,) = y return cirq.SWAP.on(x, y), {'x': np.asarray([x]), 'y': np.asarray([y])} - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) - def my_tensors( self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] ) -> List['qtn.Tensor']: diff --git a/qualtran/bloqs/basic_gates/toffoli.py b/qualtran/bloqs/basic_gates/toffoli.py index ee3b96ae6..597193856 100644 --- a/qualtran/bloqs/basic_gates/toffoli.py +++ b/qualtran/bloqs/basic_gates/toffoli.py @@ -30,7 +30,6 @@ Register, Signature, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity if TYPE_CHECKING: import cirq @@ -66,9 +65,6 @@ def adjoint(self) -> 'Bloq': def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") - def _t_complexity_(self): - return TComplexity(t=4) - def my_tensors( self, incoming: Dict[str, NDArray[Connection]], # type: ignore[type-var] diff --git a/qualtran/bloqs/basic_gates/x_basis.py b/qualtran/bloqs/basic_gates/x_basis.py index 76fd85dcb..c215a4ad2 100644 --- a/qualtran/bloqs/basic_gates/x_basis.py +++ b/qualtran/bloqs/basic_gates/x_basis.py @@ -32,7 +32,6 @@ Signature, SoquetT, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import directional_text_box, Text, WireSymbol if TYPE_CHECKING: @@ -257,9 +256,6 @@ def as_cirq_op( (q,) = q return cirq.X(q), {'q': np.asarray([q])} - def _t_complexity_(self): - return TComplexity(clifford=1) - def wire_symbol(self, reg: Register, idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': from qualtran.drawing import ModPlus diff --git a/qualtran/bloqs/basic_gates/y_gate.py b/qualtran/bloqs/basic_gates/y_gate.py index e4819a235..b418fc406 100644 --- a/qualtran/bloqs/basic_gates/y_gate.py +++ b/qualtran/bloqs/basic_gates/y_gate.py @@ -32,7 +32,6 @@ Signature, SoquetT, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, Text, TextBox, WireSymbol if TYPE_CHECKING: @@ -98,9 +97,6 @@ def as_cirq_op( (q,) = q return cirq.Y(q), {'q': np.asarray([q])} - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) - def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() ) -> 'WireSymbol': diff --git a/qualtran/bloqs/basic_gates/z_basis.py b/qualtran/bloqs/basic_gates/z_basis.py index e7b017eeb..b0f39b132 100644 --- a/qualtran/bloqs/basic_gates/z_basis.py +++ b/qualtran/bloqs/basic_gates/z_basis.py @@ -41,7 +41,6 @@ SoquetT, ) from qualtran.bloqs.bookkeeping import ArbitraryClifford -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, directional_text_box, Text, TextBox, WireSymbol if TYPE_CHECKING: @@ -277,9 +276,6 @@ def as_cirq_op( (q,) = q return cirq.Z(q), {'q': np.asarray([q])} - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) - def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() ) -> 'WireSymbol': diff --git a/qualtran/bloqs/bookkeeping/_bookkeeping_bloq.py b/qualtran/bloqs/bookkeeping/_bookkeeping_bloq.py index 5e2dab7ec..32b9b7498 100644 --- a/qualtran/bloqs/bookkeeping/_bookkeeping_bloq.py +++ b/qualtran/bloqs/bookkeeping/_bookkeeping_bloq.py @@ -16,7 +16,6 @@ from typing import Dict, Iterable, Optional, Sequence, Tuple, TYPE_CHECKING from qualtran import Bloq, BloqBuilder, SoquetT -from qualtran.cirq_interop.t_complexity_protocol import TComplexity if TYPE_CHECKING: @@ -42,6 +41,3 @@ def add_controlled( return ctrl_soqs, out_soqs return self, add_controlled - - def _t_complexity_(self) -> 'TComplexity': - return TComplexity() diff --git a/qualtran/bloqs/bookkeeping/arbitrary_clifford.py b/qualtran/bloqs/bookkeeping/arbitrary_clifford.py index e9f7a751c..619ac9275 100644 --- a/qualtran/bloqs/bookkeeping/arbitrary_clifford.py +++ b/qualtran/bloqs/bookkeeping/arbitrary_clifford.py @@ -18,7 +18,6 @@ from sympy import Expr from qualtran import Bloq, QAny, Register, Signature -from qualtran.cirq_interop.t_complexity_protocol import TComplexity @frozen @@ -40,8 +39,5 @@ class ArbitraryClifford(Bloq): def signature(self) -> Signature: return Signature([Register('x', QAny(bitsize=self.n))]) - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) - def __str__(self): return f'ArbitraryClifford(n={self.n})' diff --git a/qualtran/bloqs/for_testing/atom.py b/qualtran/bloqs/for_testing/atom.py index c7e998611..210ede7e8 100644 --- a/qualtran/bloqs/for_testing/atom.py +++ b/qualtran/bloqs/for_testing/atom.py @@ -27,7 +27,6 @@ GateWithRegisters, Signature, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting import CostKey, GateCounts, QECGatesCost if TYPE_CHECKING: @@ -72,9 +71,6 @@ def my_static_costs(self, cost_key: 'CostKey'): return GateCounts(t=100) return NotImplemented - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(100) - def __repr__(self): if self.tag: return f'TestAtom({self.tag!r})' @@ -154,8 +150,10 @@ def _unitary_(self): def adjoint(self) -> 'TestGWRAtom': return attrs.evolve(self, is_adjoint=not self.is_adjoint) - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(100) + def my_static_costs(self, cost_key: 'CostKey'): + if isinstance(cost_key, QECGatesCost): + return GateCounts(t=100) + return NotImplemented def __repr__(self): tag = f'{self.tag!r}' if self.tag else '' diff --git a/qualtran/bloqs/mcmt/and_bloq.py b/qualtran/bloqs/mcmt/and_bloq.py index 9ca197238..e1da7fff5 100644 --- a/qualtran/bloqs/mcmt/and_bloq.py +++ b/qualtran/bloqs/mcmt/and_bloq.py @@ -48,7 +48,6 @@ from qualtran.bloqs.basic_gates import TGate, XGate from qualtran.bloqs.bookkeeping import ArbitraryClifford from qualtran.cirq_interop import decompose_from_cirq_style_method -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, directional_text_box, Text, WireSymbol from qualtran.resource_counting import ( big_O, @@ -126,19 +125,6 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': return {ArbitraryClifford(n=2): 9 + 2 * pre_post_cliffords, TGate(): 4} - def _t_complexity_(self) -> 'TComplexity': - if not FLAG_AND_AS_LEAF: - return NotImplemented - - if isinstance(self.cv1, sympy.Expr) or isinstance(self.cv2, sympy.Expr): - pre_post_cliffords: Union[sympy.Order, int] = 0 - else: - pre_post_cliffords = 2 - self.cv1 - self.cv2 - if self.uncompute: - return TComplexity(clifford=4 + 2 * pre_post_cliffords) - - return TComplexity(t=4, clifford=9 + 2 * pre_post_cliffords) - def on_classical_vals( self, *, ctrl: NDArray[np.uint8], target: Optional[int] = None ) -> Dict[str, ClassicalValT]: diff --git a/qualtran/cirq_interop/_bloq_to_cirq.py b/qualtran/cirq_interop/_bloq_to_cirq.py index 61475b8c8..70f6d122c 100644 --- a/qualtran/cirq_interop/_bloq_to_cirq.py +++ b/qualtran/cirq_interop/_bloq_to_cirq.py @@ -122,10 +122,6 @@ def bloq_on( ) return BloqAsCirqGate(bloq=bloq).on_registers(**all_quregs), out_quregs - def _t_complexity_(self): - """Delegate to the bloq's t complexity.""" - return self._bloq.t_complexity() - def _num_qubits_(self) -> int: return total_bits(self.signature) diff --git a/qualtran/cirq_interop/_bloq_to_cirq_test.py b/qualtran/cirq_interop/_bloq_to_cirq_test.py index 8ecc6a59b..95dae1cef 100644 --- a/qualtran/cirq_interop/_bloq_to_cirq_test.py +++ b/qualtran/cirq_interop/_bloq_to_cirq_test.py @@ -25,7 +25,6 @@ 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_compat from qualtran.testing import execute_notebook if TYPE_CHECKING: @@ -235,7 +234,6 @@ def test_bloq_as_cirq_gate_for_mod_exp(): # newly allocated RIGHT registers in the decomposition to the one's specified by the user # when constructing the original operation (in this case, register `x`). circuit = cirq.Circuit(op, cirq.decompose_once(op)) - assert t_complexity_compat(circuit) == 2 * mod_exp.t_complexity() cirq.testing.assert_has_diagram( circuit, ''' @@ -260,7 +258,6 @@ def test_bloq_as_cirq_gate_for_mod_exp(): # Whereas when directly applying a cirq gate on qubits to get an operations, we need to # specify both input and output registers. circuit = cirq.Circuit(gate.on_registers(**out_regs), decomposed_circuit) - assert t_complexity_compat(circuit) == 2 * mod_exp.t_complexity() # Notice the newly allocated qubits _C(0) and _C(1) for output register x. cirq.testing.assert_has_diagram( circuit, diff --git a/qualtran/cirq_interop/_cirq_to_bloq.py b/qualtran/cirq_interop/_cirq_to_bloq.py index 7dd96fc7c..f71810df9 100644 --- a/qualtran/cirq_interop/_cirq_to_bloq.py +++ b/qualtran/cirq_interop/_cirq_to_bloq.py @@ -47,7 +47,7 @@ split_qubits, ) from qualtran.cirq_interop._interop_qubit_manager import InteropQubitManager -from qualtran.cirq_interop.t_complexity_protocol import _from_directly_countable_cirq, TComplexity +from qualtran.cirq_interop.t_complexity_protocol import _from_directly_countable_cirq from qualtran.resource_counting import CostKey, GateCounts, QECGatesCost if TYPE_CHECKING: @@ -147,12 +147,6 @@ def __str__(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) diff --git a/qualtran/cirq_interop/t_complexity_protocol.py b/qualtran/cirq_interop/t_complexity_protocol.py index 00114bf17..55b937d23 100644 --- a/qualtran/cirq_interop/t_complexity_protocol.py +++ b/qualtran/cirq_interop/t_complexity_protocol.py @@ -11,17 +11,14 @@ # 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 warnings -from typing import Any, Callable, Iterable, Optional, Protocol, Union +from typing import Any, Callable, Iterable, Optional, Union import attrs import cachetools import cirq -from qualtran import Bloq, DecomposeNotImplementedError, DecomposeTypeError -from qualtran.resource_counting import BloqCountT, SympySymbolAllocator +from qualtran import Bloq from qualtran.symbolics import ceil, log2, SymbolicFloat, SymbolicInt - from .decompose_protocol import _decompose_once_considering_known_decomposition _T_GATESET = cirq.Gateset(cirq.T, cirq.T**-1, unroll_circuit_op=False) @@ -72,17 +69,6 @@ def __str__(self) -> str: ) -class SupportsTComplexity(Protocol): - """An object whose TComplexity can be computed. - - An object whose TComplexity can be computed either implements the `_t_complexity_` function - or is of a type that SupportsDecompose. - """ - - def _t_complexity_(self) -> TComplexity: - """Returns the TComplexity.""" - - def _from_explicit_annotation(stc: Any) -> Optional[TComplexity]: """Returns TComplexity of stc by calling `stc._t_complexity_()` method, if it exists.""" estimator = getattr(stc, '_t_complexity_', None) @@ -95,24 +81,6 @@ def _from_explicit_annotation(stc: Any) -> Optional[TComplexity]: return None -def _from_directly_countable_bloqs(bloq: Bloq) -> Optional[TComplexity]: - """Directly count a clifford, T or Rotation (if it is one).""" - from qualtran.bloqs.basic_gates import TGate - from qualtran.resource_counting.classify_bloqs import bloq_is_clifford, bloq_is_rotation - - if isinstance(bloq, TGate): - return TComplexity(t=1) - - if bloq_is_clifford(bloq): - return TComplexity(clifford=1) - - if bloq_is_rotation(bloq): - return TComplexity(rotations=1) - - # Else - return None - - def _from_directly_countable_cirq(stc: Any) -> Optional[TComplexity]: """Directly count a clifford, T or Rotation (if it is one).""" if not isinstance(stc, (cirq.Gate, cirq.Operation)): @@ -156,28 +124,6 @@ def _from_iterable(it: Any) -> Optional[TComplexity]: return t -def _from_bloq_build_call_graph(bloq: Bloq) -> Optional[TComplexity]: - # Uses the depth 1 call graph of Bloq `stc` to recursively compute the complexity. - try: - callee_counts = bloq.build_call_graph(ssa=SympySymbolAllocator()) - except (DecomposeNotImplementedError, DecomposeTypeError): - # We must explicitly catch these exceptions rather than using `get_bloq_callee_counts` - # to distinguish between no decomposition and an empty list of callees. - return None - - ret = TComplexity() - if isinstance(callee_counts, set): - callee_iterator: Iterable[BloqCountT] = callee_counts - else: - callee_iterator = callee_counts.items() - for callee, n in callee_iterator: - r = t_complexity(callee) - if r is None: - return None - ret += n * r - return ret - - def _from_cirq_decomposition(stc: Any) -> Optional[TComplexity]: # Decompose the object and recursively compute the complexity. decomposition = _decompose_once_considering_known_decomposition(stc) @@ -233,19 +179,6 @@ def _t_complexity_for_gate_or_op( return _t_complexity_from_strategies(gate_or_op, strategies) -@cachetools.cached(cachetools.LRUCache(128), key=_get_hash, info=True) -def _t_complexity_for_bloq(bloq: Bloq) -> Optional[TComplexity]: - strategies = [ - _from_explicit_annotation, - _from_bloq_build_call_graph, - _from_directly_countable_bloqs, - ] - 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. @@ -258,17 +191,9 @@ 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 + 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( - "couldn't compute TComplexity of:\n" f"type: {type(bloq)}\n" f"value: {bloq}" - ) - return ret + return get_cost_value(bloq, QECGatesCost(legacy_shims=True)).to_legacy_t_complexity() def t_complexity_compat(stc: Any) -> TComplexity: @@ -287,18 +212,7 @@ def t_complexity_compat(stc: Any) -> TComplexity: Raises: TypeError: if the methods fails to compute TComplexity. """ - from qualtran.cirq_interop import BloqAsCirqGate - - if isinstance(stc, Bloq): - warnings.warn("Please use `t_complexity`, not the _compat version.") - ret = _t_complexity_for_bloq(stc) - elif isinstance(stc, BloqAsCirqGate): - ret = _t_complexity_for_bloq(stc.bloq) - elif isinstance(stc, cirq.Operation) and isinstance(stc.gate, Bloq): - ret = _t_complexity_for_bloq(stc.gate) - elif isinstance(stc, cirq.Operation) and isinstance(stc.gate, BloqAsCirqGate): - ret = _t_complexity_for_bloq(stc.gate.bloq) - elif isinstance(stc, (cirq.AbstractCircuit, cirq.Moment, list)): + if isinstance(stc, (cirq.AbstractCircuit, cirq.Moment, list)): ret = _from_iterable(stc) elif isinstance(stc, (cirq.Gate, cirq.Operation)): ret = _t_complexity_for_gate_or_op(stc) @@ -308,8 +222,3 @@ def t_complexity_compat(stc: Any) -> TComplexity: if ret is None: raise TypeError("couldn't compute TComplexity of:\n" f"type: {type(stc)}\n" f"value: {stc}") return ret - - -t_complexity.cache_clear = _t_complexity_for_bloq.cache_clear # type: ignore[attr-defined] -t_complexity.cache_info = _t_complexity_for_bloq.cache_info # type: ignore[attr-defined] -t_complexity.cache = _t_complexity_for_bloq.cache # type: ignore[attr-defined] diff --git a/qualtran/cirq_interop/t_complexity_protocol_test.py b/qualtran/cirq_interop/t_complexity_protocol_test.py index cf5d9166f..7ffe16ac4 100644 --- a/qualtran/cirq_interop/t_complexity_protocol_test.py +++ b/qualtran/cirq_interop/t_complexity_protocol_test.py @@ -11,7 +11,7 @@ # 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 Set, TYPE_CHECKING +from typing import TYPE_CHECKING import cirq import pytest @@ -31,7 +31,7 @@ from qualtran.testing import execute_notebook if TYPE_CHECKING: - from qualtran.resource_counting import BloqCountT, SympySymbolAllocator + pass class DoesNotSupportTComplexity: ... @@ -66,23 +66,6 @@ def signature(self) -> 'Signature': return Signature.build(q=1) -@frozen -class SupportsTComplexityBloqViaBuildCallGraph(Bloq): - @property - def signature(self) -> 'Signature': - return Signature.build(q=1) - - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: - return {(SupportsTComplexityGateWithRegisters(), 5)} - - -def test_t_complexity_for_bloq_via_build_call_graph(): - bloq = SupportsTComplexityBloqViaBuildCallGraph() - _, sigma = bloq.call_graph(max_depth=1) - assert sigma != {} - assert t_complexity(bloq) == TComplexity(t=5, clifford=10) - - def test_t_complexity_for_bloq_does_not_support(): with pytest.raises(DecomposeNotImplementedError): _ = t_complexity(DoesNotSupportTComplexityBloq()) @@ -100,7 +83,7 @@ def test_t_complexity_compat(): g = GateHelper(SupportsTComplexityGateWithRegisters()) assert g.gate._decompose_with_context_(g.operation.qubits) is NotImplemented # type: ignore[attr-defined] - assert t_complexity(g.gate) == TComplexity(t=1, clifford=2) + assert t_complexity_compat(g.gate) == TComplexity(t=1, clifford=2) assert t_complexity_compat(g.operation) == TComplexity(t=1, clifford=2) assert t_complexity_compat([cirq.T, cirq.X]) == TComplexity(t=1, clifford=1) diff --git a/qualtran/cirq_interop/testing.py b/qualtran/cirq_interop/testing.py index 34428b838..203207f75 100644 --- a/qualtran/cirq_interop/testing.py +++ b/qualtran/cirq_interop/testing.py @@ -20,7 +20,7 @@ import numpy as np from numpy.typing import NDArray -from qualtran import Bloq, DecomposeNotImplementedError, DecomposeTypeError, Signature +from qualtran import Bloq, Signature from qualtran._infra.gate_with_registers import get_named_qubits, merge_qubits from qualtran.cirq_interop import t_complexity_protocol from qualtran.cirq_interop.decompose_protocol import _decompose_once_considering_known_decomposition @@ -126,21 +126,19 @@ def get_circuit_inp_out_cirqsim( def assert_decompose_is_consistent_with_t_complexity(val: Any): + if isinstance(val, Bloq): + raise ValueError( + f"Use `qlt_testing.assert_equivalent_bloq_counts` for testing bloqs: {val}." + ) + t_complexity_method = getattr(val, '_t_complexity_', None) expected = NotImplemented if t_complexity_method is None else t_complexity_method() if expected is NotImplemented or expected is None: raise AssertionError("No consistent t_complexity: no _t_complexity_.") - if isinstance(val, Bloq): - try: - cbloq = val.decompose_bloq() - except (DecomposeNotImplementedError, DecomposeTypeError) as e: - raise AssertionError("No consistent t_complexity: no decomposition.") from e - from_decomposition = t_complexity_protocol._from_bloq_build_call_graph(cbloq) - else: - decomposition = _decompose_once_considering_known_decomposition(val) - if decomposition is None: - raise AssertionError("No consistent t_complexity: no decomposition.") - from_decomposition = t_complexity_protocol._from_iterable(decomposition) + decomposition = _decompose_once_considering_known_decomposition(val) + if decomposition is None: + raise AssertionError("No consistent t_complexity: no decomposition.") + from_decomposition = t_complexity_protocol._from_iterable(decomposition) assert expected == from_decomposition, f'{expected} != {from_decomposition}' diff --git a/qualtran/cirq_interop/testing_test.py b/qualtran/cirq_interop/testing_test.py index 72c75e75c..cc35f145e 100644 --- a/qualtran/cirq_interop/testing_test.py +++ b/qualtran/cirq_interop/testing_test.py @@ -11,14 +11,13 @@ # 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 Dict, Iterator +from typing import Iterator import cirq import numpy as np import pytest -from qualtran import Bloq, BloqBuilder, QBit, Register, Side, Signature, SoquetT -from qualtran.bloqs.basic_gates import XGate +from qualtran import QBit, Register, Side, Signature from qualtran.bloqs.mcmt.and_bloq import MultiAnd from qualtran.cirq_interop import testing from qualtran.cirq_interop.t_complexity_protocol import TComplexity @@ -59,9 +58,6 @@ def test_gate_helper(): class DoesNotDecompose(cirq.Operation): - def _t_complexity_(self) -> TComplexity: - return TComplexity(t=1, clifford=2, rotations=3) - @property def qubits(self): return [] @@ -85,23 +81,5 @@ def with_qubits(self, _): pass -class InconsistentDecompostion(Bloq): - @property - def signature(self) -> 'Signature': - return Signature.build(q=1) - - def _t_complexity_(self) -> TComplexity: - return TComplexity(rotations=1) - - def build_composite_bloq(self, bb: 'BloqBuilder', q: 'SoquetT') -> Dict[str, 'SoquetT']: - q = bb.add(XGate(), q=q) - return {'q': q} - - def test_assert_decompose_is_consistent_with_t_complexity(): testing.assert_decompose_is_consistent_with_t_complexity(ConsistentDecompostion()) - - -def test_assert_decompose_is_consistent_with_t_complexity_raises(): - with pytest.raises(AssertionError): - testing.assert_decompose_is_consistent_with_t_complexity(InconsistentDecompostion()) diff --git a/qualtran/resource_counting/_bloq_counts.py b/qualtran/resource_counting/_bloq_counts.py index 24a9bc672..0ca025bb8 100644 --- a/qualtran/resource_counting/_bloq_counts.py +++ b/qualtran/resource_counting/_bloq_counts.py @@ -12,7 +12,6 @@ # 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, cast, Dict, Sequence, Tuple, TYPE_CHECKING @@ -296,18 +295,6 @@ def compute(self, bloq: 'Bloq', get_callee_cost: Callable[['Bloq'], GateCounts]) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq 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): return GateCounts(t=1) diff --git a/qualtran/serialization/bloq_test.py b/qualtran/serialization/bloq_test.py index 5964908ee..218206d7f 100644 --- a/qualtran/serialization/bloq_test.py +++ b/qualtran/serialization/bloq_test.py @@ -26,7 +26,6 @@ from qualtran.bloqs.factoring.mod_exp import ModExp from qualtran.cirq_interop import CirqGateAsBloq 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 @@ -96,9 +95,6 @@ class TestCSwap(Bloq): def signature(self) -> 'Signature': return Signature.build(ctrl=1, x=self.bitsize, y=self.bitsize) - 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) From c334b60eaf82b2725f0b9f574716b8e88e5c941d Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Fri, 11 Oct 2024 11:15:57 -0700 Subject: [PATCH 2/2] Legacy _t_complexity_ still supported via getattr --- qualtran/cirq_interop/t_complexity_protocol.py | 1 + .../cirq_interop/t_complexity_protocol_test.py | 9 +++++---- qualtran/resource_counting/_bloq_counts.py | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/qualtran/cirq_interop/t_complexity_protocol.py b/qualtran/cirq_interop/t_complexity_protocol.py index 55b937d23..64e3da56f 100644 --- a/qualtran/cirq_interop/t_complexity_protocol.py +++ b/qualtran/cirq_interop/t_complexity_protocol.py @@ -19,6 +19,7 @@ from qualtran import Bloq from qualtran.symbolics import ceil, log2, SymbolicFloat, SymbolicInt + from .decompose_protocol import _decompose_once_considering_known_decomposition _T_GATESET = cirq.Gateset(cirq.T, cirq.T**-1, unroll_circuit_op=False) diff --git a/qualtran/cirq_interop/t_complexity_protocol_test.py b/qualtran/cirq_interop/t_complexity_protocol_test.py index 7ffe16ac4..99496f4d7 100644 --- a/qualtran/cirq_interop/t_complexity_protocol_test.py +++ b/qualtran/cirq_interop/t_complexity_protocol_test.py @@ -11,7 +11,6 @@ # 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 TYPE_CHECKING import cirq import pytest @@ -30,9 +29,6 @@ from qualtran.cirq_interop.testing import GateHelper from qualtran.testing import execute_notebook -if TYPE_CHECKING: - pass - class DoesNotSupportTComplexity: ... @@ -66,6 +62,11 @@ def signature(self) -> 'Signature': return Signature.build(q=1) +def test_legacy_t_complexity_annotation(): + # Test the deprecated `getattr(.., '_t_complexity_') + assert SupportsTComplexityGateWithRegisters().t_complexity() == TComplexity(t=1, clifford=2) + + def test_t_complexity_for_bloq_does_not_support(): with pytest.raises(DecomposeNotImplementedError): _ = t_complexity(DoesNotSupportTComplexityBloq()) diff --git a/qualtran/resource_counting/_bloq_counts.py b/qualtran/resource_counting/_bloq_counts.py index 0ca025bb8..02ad3c404 100644 --- a/qualtran/resource_counting/_bloq_counts.py +++ b/qualtran/resource_counting/_bloq_counts.py @@ -12,6 +12,7 @@ # 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, cast, Dict, Sequence, Tuple, TYPE_CHECKING @@ -295,6 +296,21 @@ def compute(self, bloq: 'Bloq', get_callee_cost: Callable[['Bloq'], GateCounts]) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq from qualtran.bloqs.mcmt import And, MultiTargetCNOT + if self.legacy_shims: + if hasattr(bloq, '_t_complexity_'): + legacy_val = bloq._t_complexity_() + else: + legacy_val = NotImplemented + 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): return GateCounts(t=1)