diff --git a/qualtran/bloqs/arithmetic/t_complexity_of_comparison_gates.ipynb b/qualtran/bloqs/arithmetic/t_complexity_of_comparison_gates.ipynb index 390c98630..9ff6e0d10 100644 --- a/qualtran/bloqs/arithmetic/t_complexity_of_comparison_gates.ipynb +++ b/qualtran/bloqs/arithmetic/t_complexity_of_comparison_gates.ipynb @@ -78,7 +78,7 @@ "from typing import Sequence\n", "\n", "import cirq\n", - "from qualtran.cirq_interop.t_complexity_protocol import t_complexity\n", + "from qualtran.cirq_interop.t_complexity_protocol import t_complexity_compat\n", "from qualtran.bloqs.mcmt import And, MultiAnd" ] }, @@ -135,7 +135,7 @@ "outputs": [], "source": [ "# This should have T count of 4*(3 qubits) - 4 = 8\n", - "t_complexity(equality_circuit)" + "t_complexity_compat(equality_circuit)" ] }, { @@ -302,7 +302,7 @@ "outputs": [], "source": [ "# This should have T count of 4*(2 qubits) - 4 = 4\n", - "t_complexity(quantum_quantum_equality)" + "t_complexity_compat(quantum_quantum_equality)" ] }, { @@ -679,7 +679,7 @@ "outputs": [], "source": [ "# This should have T count of 8*(3 qubits) - 4 = 20\n", - "t_complexity(quantum_compare)" + "t_complexity_compat(quantum_compare)" ] }, { @@ -820,7 +820,7 @@ "outputs": [], "source": [ "# This should have T complexity of 6*(2 qubits) + O(1)\n", - "t_complexity(circuit)" + "t_complexity_compat(circuit)" ] }, { @@ -959,7 +959,7 @@ "outputs": [], "source": [ "# T count should be 4*(3 qubits) = 12\n", - "t_complexity(less_than_circuit)" + "t_complexity_compat(less_than_circuit)" ] }, { @@ -1037,9 +1037,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.11.8" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/qualtran/bloqs/phase_estimation/phase_estimation_of_quantum_walk.ipynb b/qualtran/bloqs/phase_estimation/phase_estimation_of_quantum_walk.ipynb index 08c81f523..cbdd56583 100644 --- a/qualtran/bloqs/phase_estimation/phase_estimation_of_quantum_walk.ipynb +++ b/qualtran/bloqs/phase_estimation/phase_estimation_of_quantum_walk.ipynb @@ -142,7 +142,7 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.cirq_interop.t_complexity_protocol import t_complexity\n", + "from qualtran.cirq_interop.t_complexity_protocol import t_complexity_compat\n", "\n", "num_sites: int = 200\n", "eps: float = 1e-5\n", @@ -151,7 +151,7 @@ "walk, _ = get_walk_operator_for_1d_ising_model(num_sites, eps)\n", "\n", "circuit = cirq.Circuit(phase_estimation(walk, m=m_bits))\n", - "%time result = t_complexity(circuit[1:-1])\n", + "%time result = t_complexity_compat(circuit[1:-1])\n", "print(result)" ] }, @@ -178,8 +178,7 @@ "m_bits = int(np.ceil(np.log2(qlambda * np.pi * np.sqrt(2) / delta_E)))\n", "walk = get_walk_operator_for_hubbard_model(x_dim, y_dim, t, mu)\n", "circuit = cirq.Circuit(phase_estimation(walk, m=m_bits))\n", - "t_complexity.cache_clear()\n", - "%time result = t_complexity(circuit[1:-1])\n", + "%time result = t_complexity_compat(circuit[1:-1])\n", "print(result)" ] }, @@ -191,7 +190,6 @@ "source": [ "from qualtran.bloqs.phase_estimation.qubitization_qpe import _qubitization_qpe_hubbard_model_large\n", "qpe = _qubitization_qpe_hubbard_model_large.make()\n", - "t_complexity.cache_clear()\n", "%time result = qpe.t_complexity()\n", "print(result)" ] diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.ipynb b/qualtran/bloqs/rotations/hamming_weight_phasing.ipynb index 0d6deeafa..dccded102 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.ipynb +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.ipynb @@ -108,7 +108,7 @@ "outputs": [], "source": [ "hamming_weight_phasing = HammingWeightPhasing(4, np.pi / 2.0)\n", - "print(\"Applying this unitary to |1111> should be the identity, and |0101> will flip the sign.\")" + "# Applying this unitary to |1111> should be the identity, and |0101> will flip the sign." ] }, { diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.py b/qualtran/bloqs/rotations/hamming_weight_phasing.py index bd59b3ecc..80532184a 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.py @@ -110,7 +110,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: @bloq_example def _hamming_weight_phasing() -> HammingWeightPhasing: hamming_weight_phasing = HammingWeightPhasing(4, np.pi / 2.0) - print("Applying this unitary to |1111> should be the identity, and |0101> will flip the sign.") + # Applying this unitary to |1111> should be the identity, and |0101> will flip the sign. return hamming_weight_phasing diff --git a/qualtran/cirq_interop/_bloq_to_cirq_test.py b/qualtran/cirq_interop/_bloq_to_cirq_test.py index 5bd238606..bee180dad 100644 --- a/qualtran/cirq_interop/_bloq_to_cirq_test.py +++ b/qualtran/cirq_interop/_bloq_to_cirq_test.py @@ -25,7 +25,7 @@ from qualtran.bloqs.mcmt.and_bloq import And, MultiAnd from qualtran.bloqs.state_preparation import PrepareUniformSuperposition from qualtran.cirq_interop._bloq_to_cirq import BloqAsCirqGate, CirqQuregT -from qualtran.cirq_interop.t_complexity_protocol import t_complexity +from qualtran.cirq_interop.t_complexity_protocol import t_complexity_compat from qualtran.testing import execute_notebook if TYPE_CHECKING: @@ -225,7 +225,7 @@ 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(circuit) == 2 * mod_exp.t_complexity() + assert t_complexity_compat(circuit) == 2 * mod_exp.t_complexity() cirq.testing.assert_has_diagram( circuit, ''' @@ -250,7 +250,7 @@ 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(circuit) == 2 * mod_exp.t_complexity() + 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 c06d1cab2..8b20172d4 100644 --- a/qualtran/cirq_interop/_cirq_to_bloq.py +++ b/qualtran/cirq_interop/_cirq_to_bloq.py @@ -48,7 +48,7 @@ split_qubits, ) from qualtran.cirq_interop._interop_qubit_manager import InteropQubitManager -from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity +from qualtran.cirq_interop.t_complexity_protocol import _from_directly_countable_cirq, TComplexity if TYPE_CHECKING: import quimb.tensor as qtn @@ -110,7 +110,10 @@ def my_tensors( ) def _t_complexity_(self) -> 'TComplexity': - return t_complexity(self.cirq_gate) + 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' @@ -335,6 +338,7 @@ def cirq_gate_to_bloq(gate: cirq.Gate) -> Bloq: CZPowGate, GlobalPhase, Hadamard, + Identity, Rx, Ry, Rz, @@ -380,6 +384,7 @@ def cirq_gate_to_bloq(gate: cirq.Gate) -> Bloq: cirq.CZ: CZ(), cirq.SWAP: TwoBitSwap(), cirq.CSWAP: CSwap(1), + cirq.I: Identity(), } if gate in CIRQ_GATE_TO_BLOQ_MAP: return CIRQ_GATE_TO_BLOQ_MAP[gate] diff --git a/qualtran/cirq_interop/jupyter_tools.py b/qualtran/cirq_interop/jupyter_tools.py index c8100cdc5..fab86169c 100644 --- a/qualtran/cirq_interop/jupyter_tools.py +++ b/qualtran/cirq_interop/jupyter_tools.py @@ -62,7 +62,7 @@ def circuit_with_costs(circuit: 'cirq.AbstractCircuit') -> 'cirq.AbstractCircuit """Annotates each operation in the circuit with its T-complexity cost.""" def _map_func(op: cirq.Operation, _): - t_cost = t_complexity_protocol.t_complexity(op) + t_cost = t_complexity_protocol.t_complexity_compat(op) return op.with_tags(f't:{t_cost.t:g},r:{t_cost.rotations:g}') return cirq.map_operations(circuit, map_func=_map_func) diff --git a/qualtran/cirq_interop/t_complexity.ipynb b/qualtran/cirq_interop/t_complexity.ipynb index f9a23d35c..8bdf67ee5 100644 --- a/qualtran/cirq_interop/t_complexity.ipynb +++ b/qualtran/cirq_interop/t_complexity.ipynb @@ -62,10 +62,8 @@ "source": [ "# And of two qubits\n", "gate = And() # create an And gate\n", - "# create an operation\n", - "operation = gate.on_registers(**get_named_qubits(gate.signature))\n", - "# this operation doesn't directly support TComplexity but it's decomposable and its components are simple.\n", - "print(t_complexity(operation))" + "# this gate doesn't directly support TComplexity but it's decomposable and its components are simple.\n", + "print(t_complexity(gate))" ] }, { @@ -84,9 +82,8 @@ "outputs": [], "source": [ "gate = And() ** -1 # adjoint of And\n", - "operation = gate.on_registers(**get_named_qubits(gate.signature))\n", "# the deomposition is H, measure, CZ, and Reset\n", - "print(t_complexity(operation))" + "print(t_complexity(gate))" ] }, { @@ -105,9 +102,8 @@ "outputs": [], "source": [ "n = 5\n", - "gate = MultiAnd((1, )*n)\n", - "operation = gate.on_registers(**get_named_qubits(gate.signature))\n", - "print(t_complexity(operation))" + "gate = MultiAnd((1,)*n)\n", + "print(t_complexity(gate))" ] }, { @@ -117,15 +113,14 @@ "metadata": {}, "outputs": [], "source": [ - "def Generate(n_max: int = 10):\n", + "def get_t_count_plot_data_for_and(n_max: int = 10):\n", " \"\"\"Returns the #T when the number of qubits is between 3 and n_max inclusive\"\"\"\n", " n_controls = []\n", " t_count = []\n", " for n in range(3, n_max + 2):\n", " n_controls.append(n)\n", " gate = MultiAnd(cvs=(1, )*n)\n", - " op = gate.on_registers(**get_named_qubits(gate.signature))\n", - " c = t_complexity(op)\n", + " c = t_complexity(gate)\n", " t_count.append(c.t)\n", " return n_controls, t_count" ] @@ -137,8 +132,8 @@ "metadata": {}, "outputs": [], "source": [ - "n_controls, t_count = Generate()\n", - "plt.plot(n_controls, t_count, label='T')\n", + "n_controls, t_count = get_t_count_plot_data_for_and()\n", + "plt.plot(n_controls, t_count, 'o-', label='T')\n", "plt.ylabel('count')\n", "plt.xlabel('number of qubits')\n", "plt.title('And gate')\n", @@ -163,7 +158,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.11.8" }, "vscode": { "interpreter": { diff --git a/qualtran/cirq_interop/t_complexity_protocol.py b/qualtran/cirq_interop/t_complexity_protocol.py index c253ee1a5..ce39fb5e6 100644 --- a/qualtran/cirq_interop/t_complexity_protocol.py +++ b/qualtran/cirq_interop/t_complexity_protocol.py @@ -11,16 +11,19 @@ # 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 Any, Callable, Hashable, Iterable, Optional, Protocol, Union +import warnings +from typing import Any, Callable, Iterable, Optional, Protocol, Union import attrs import cachetools import cirq -from qualtran import Bloq, Controlled -from qualtran.cirq_interop.decompose_protocol import _decompose_once_considering_known_decomposition +from qualtran import Bloq, Controlled, DecomposeNotImplementedError, DecomposeTypeError +from qualtran.resource_counting import SympySymbolAllocator 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) _ROTS_GATESET = cirq.Gateset(cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate, cirq.CZPowGate) @@ -89,19 +92,33 @@ def _from_explicit_annotation(stc: Any) -> Optional[TComplexity]: return None -def _from_directly_countable(stc: Any) -> Optional[TComplexity]: +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 + from qualtran.bloqs.basic_gates import Identity, TGate + from qualtran.resource_counting.classify_bloqs import bloq_is_clifford, bloq_is_rotation - if isinstance(stc, TGate): + if isinstance(bloq, TGate): return TComplexity(t=1) - if bloq_is_clifford(stc): + if bloq_is_clifford(bloq): return TComplexity(clifford=1) + if bloq_is_rotation(bloq): + return TComplexity(rotations=1) + + # TODO: https://github.com/quantumlib/Qualtran/issues/1207). This logic should + # be implemented by `Identity` directly + if isinstance(bloq, Controlled) and bloq.subbloq == Identity(): + return TComplexity() + + # 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)): - return None + raise TypeError(f"This strategy should only be used on Cirq gates/operations, not {stc!r}") if isinstance(stc, cirq.GlobalPhaseGate) or ( isinstance(stc, cirq.Operation) and isinstance(stc.gate, cirq.GlobalPhaseGate) @@ -122,21 +139,10 @@ def _from_directly_countable(stc: Any) -> Optional[TComplexity]: if stc in _ROTS_GATESET: return TComplexity(rotations=1) - if isinstance(stc, Controlled) and cirq.num_qubits(stc) <= 2: - # We need this hack temporarily because we assume access to decomposition - # of a C-U gate where $U$ is a single qubit rotation. Cirq has this decomposition - # but the right thing to do in Qualtran is to add explicit bloqs and annotate - # them with costs. See https://github.com/quantumlib/Qualtran/issues/878 - from qualtran._infra.gate_with_registers import get_named_qubits - - quregs = get_named_qubits(stc.signature) - qm = cirq.SimpleQubitManager() - op, _ = stc.as_cirq_op(qubit_manager=qm, **quregs) - return t_complexity(op) - if cirq.num_qubits(stc) == 1 and cirq.has_unitary(stc): # Single qubit rotation operation. return TComplexity(rotations=1) + return None @@ -145,26 +151,25 @@ def _from_iterable(it: Any) -> Optional[TComplexity]: return None t = TComplexity() for v in it: - r = t_complexity(v) + r = t_complexity_compat(v) if r is None: return None t = t + r return t -def _from_bloq_build_call_graph(stc: Any) -> Optional[TComplexity]: +def _from_bloq_build_call_graph(bloq: Bloq) -> Optional[TComplexity]: # Uses the depth 1 call graph of Bloq `stc` to recursively compute the complexity. - from qualtran.resource_counting import get_bloq_callee_counts - from qualtran.resource_counting.generalizers import cirq_to_bloqs - - if not isinstance(stc, Bloq): - return None - callee_counts = get_bloq_callee_counts(bloq=stc, generalizer=cirq_to_bloqs) - if len(callee_counts) == 0: + 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() - for bloq, n in callee_counts: - r = t_complexity(bloq) + for callee, n in callee_counts: + r = t_complexity(callee) if r is None: return None ret += n * r @@ -220,15 +225,48 @@ def _t_complexity_for_gate_or_op( strategies = [ _from_explicit_annotation, - _from_bloq_build_call_graph, - _from_directly_countable, + _from_directly_countable_cirq, _from_cirq_decomposition, ] return _t_complexity_from_strategies(gate_or_op, strategies) -def t_complexity(stc: Any) -> TComplexity: - """Returns the TComplexity. +@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) + + +def t_complexity(bloq: Bloq) -> TComplexity: + """Returns the TComplexity of a bloq. + + Args: + bloq: The bloq to compute the T complexity. + + Returns: + The TComplexity of the given object. + + Raises: + TypeError: if none of the strategies can derive the 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 + + +def t_complexity_compat(stc: Any) -> TComplexity: + """Returns the TComplexity of a bloq or some Cirq objects. + + The main `t_complexity` function now expects a `Bloq`. Historically, there were strategies + to derive t complexities from other container classes (`cirq.Circuit`, `cirq.Moment`) and + gates/operations. Args: stc: an object to compute its TComplexity. @@ -239,23 +277,29 @@ def t_complexity(stc: Any) -> TComplexity: Raises: TypeError: if the methods fails to compute TComplexity. """ - if isinstance(stc, (cirq.Gate, cirq.Operation, Bloq)) and isinstance(stc, Hashable): + 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)): + ret = _from_iterable(stc) + elif isinstance(stc, (cirq.Gate, cirq.Operation)): ret = _t_complexity_for_gate_or_op(stc) else: - strategies = [ - _from_explicit_annotation, - _from_directly_countable, - _from_bloq_build_call_graph, - _from_cirq_decomposition, - _from_iterable, - ] - ret = _t_complexity_from_strategies(stc, strategies) + ret = None 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_gate_or_op.cache_clear # type: ignore[attr-defined] -t_complexity.cache_info = _t_complexity_for_gate_or_op.cache_info # type: ignore[attr-defined] -t_complexity.cache = _t_complexity_for_gate_or_op.cache # type: ignore[attr-defined] +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 178a19862..1b54a320d 100644 --- a/qualtran/cirq_interop/t_complexity_protocol_test.py +++ b/qualtran/cirq_interop/t_complexity_protocol_test.py @@ -19,9 +19,14 @@ from qualtran import Bloq, GateWithRegisters, Signature from qualtran._infra.gate_with_registers import get_named_qubits -from qualtran.bloqs.basic_gates import CHadamard +from qualtran.bloqs.basic_gates import CHadamard, GlobalPhase from qualtran.bloqs.mcmt.and_bloq import And -from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity +from qualtran.cirq_interop.t_complexity_protocol import ( + _from_directly_countable_cirq, + t_complexity, + t_complexity_compat, + TComplexity, +) from qualtran.cirq_interop.testing import GateHelper from qualtran.testing import execute_notebook @@ -29,11 +34,6 @@ from qualtran.resource_counting import BloqCountT, SympySymbolAllocator -class SupportTComplexity: - def _t_complexity_(self) -> TComplexity: - return TComplexity(t=1) - - class DoesNotSupportTComplexity: ... @@ -89,73 +89,73 @@ def test_t_complexity_for_bloq_does_not_support(): _ = t_complexity(DoesNotSupportTComplexityBloq()) -def test_t_complexity(): +def test_t_complexity_compat(): with pytest.raises(TypeError): - _ = t_complexity(DoesNotSupportTComplexity()) + _ = t_complexity_compat(DoesNotSupportTComplexity()) with pytest.raises(TypeError): - t_complexity([DoesNotSupportTComplexity()]) + t_complexity_compat([DoesNotSupportTComplexity()]) with pytest.raises(TypeError): - _ = t_complexity(DoesNotSupportTComplexityGate()) + _ = t_complexity_compat(DoesNotSupportTComplexityGate()) - assert t_complexity(SupportTComplexity()) == TComplexity(t=1) - assert t_complexity(SupportTComplexityGate().on(cirq.q('t'))) == TComplexity(t=1) + assert t_complexity_compat(SupportTComplexityGate().on(cirq.q('t'))) == TComplexity(t=1) 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(g.operation) == TComplexity(t=1, clifford=2) + assert t_complexity_compat(g.operation) == TComplexity(t=1, clifford=2) - assert t_complexity([cirq.T, cirq.X]) == TComplexity(t=1, clifford=1) + assert t_complexity_compat([cirq.T, cirq.X]) == TComplexity(t=1, clifford=1) q = cirq.NamedQubit('q') - assert t_complexity([cirq.T(q), cirq.X(q)]) == TComplexity(t=1, clifford=1) + assert t_complexity_compat([cirq.T(q), cirq.X(q)]) == TComplexity(t=1, clifford=1) def test_gates(): # T gate and its adjoint - assert t_complexity(cirq.T) == TComplexity(t=1) - assert t_complexity(cirq.T**-1) == TComplexity(t=1) + assert _from_directly_countable_cirq(cirq.T) == TComplexity(t=1) + assert _from_directly_countable_cirq(cirq.T**-1) == TComplexity(t=1) - assert t_complexity(cirq.H) == TComplexity(clifford=1) # Hadamard - assert t_complexity(cirq.CNOT) == TComplexity(clifford=1) # CNOT - assert t_complexity(cirq.S) == TComplexity(clifford=1) # S - assert t_complexity(cirq.S**-1) == TComplexity(clifford=1) # S† + assert _from_directly_countable_cirq(cirq.H) == TComplexity(clifford=1) # Hadamard + assert _from_directly_countable_cirq(cirq.CNOT) == TComplexity(clifford=1) # CNOT + assert _from_directly_countable_cirq(cirq.S) == TComplexity(clifford=1) # S + assert _from_directly_countable_cirq(cirq.S**-1) == TComplexity(clifford=1) # S† # Pauli operators are clifford - assert t_complexity(cirq.X) == TComplexity(clifford=1) - assert t_complexity(cirq.Y) == TComplexity(clifford=1) - assert t_complexity(cirq.Z) == TComplexity(clifford=1) + assert _from_directly_countable_cirq(cirq.X) == TComplexity(clifford=1) + assert _from_directly_countable_cirq(cirq.Y) == TComplexity(clifford=1) + assert _from_directly_countable_cirq(cirq.Z) == TComplexity(clifford=1) # Rotation about X, Y, and Z axes - assert t_complexity(cirq.Rx(rads=2)) == TComplexity(rotations=1) - assert t_complexity(cirq.Ry(rads=2)) == TComplexity(rotations=1) - assert t_complexity(cirq.Rz(rads=2)) == TComplexity(rotations=1) + assert _from_directly_countable_cirq(cirq.Rx(rads=2)) == TComplexity(rotations=1) + assert _from_directly_countable_cirq(cirq.Ry(rads=2)) == TComplexity(rotations=1) + assert _from_directly_countable_cirq(cirq.Rz(rads=2)) == TComplexity(rotations=1) # clifford+T assert t_complexity(And()) == TComplexity(t=4, clifford=9) assert t_complexity(And() ** -1) == TComplexity(clifford=4) - assert t_complexity(cirq.FREDKIN) == TComplexity(t=7, clifford=14) - assert t_complexity(cirq.H.controlled()) == TComplexity(clifford=4, rotations=2) + assert t_complexity_compat(cirq.FREDKIN) == TComplexity(t=7, clifford=14) + assert t_complexity_compat(cirq.H.controlled()) == TComplexity(clifford=4, rotations=2) assert t_complexity(CHadamard()) == TComplexity(clifford=4, rotations=2) # Global phase - assert t_complexity(cirq.GlobalPhaseGate(1j)) == TComplexity() + assert _from_directly_countable_cirq(cirq.GlobalPhaseGate(1j)) == TComplexity() + assert t_complexity(GlobalPhase(exponent=1)) == TComplexity() def test_operations(): q = cirq.NamedQubit('q') - assert t_complexity(cirq.T(q)) == TComplexity(t=1) + assert t_complexity_compat(cirq.T(q)) == TComplexity(t=1) gate = And() op = gate.on_registers(**get_named_qubits(gate.signature)) - assert t_complexity(op) == TComplexity(t=4, clifford=9) + assert t_complexity_compat(op) == TComplexity(t=4, clifford=9) gate = And() ** -1 op = gate.on_registers(**get_named_qubits(gate.signature)) - assert t_complexity(op) == TComplexity(clifford=4) + assert t_complexity_compat(op) == TComplexity(clifford=4) - assert t_complexity(cirq.global_phase_operation(1j)) == TComplexity() + assert t_complexity_compat(cirq.global_phase_operation(1j)) == TComplexity() def test_circuits(): @@ -168,10 +168,10 @@ def test_circuits(): cirq.Ry(rads=0.6)(q), cirq.measure(q, key='m'), ) - assert t_complexity(circuit) == TComplexity(clifford=2, rotations=3, t=1) + assert t_complexity_compat(circuit) == TComplexity(clifford=2, rotations=3, t=1) circuit = cirq.FrozenCircuit(cirq.T(q) ** -1, cirq.Rx(rads=0.1)(q), cirq.measure(q, key='m')) - assert t_complexity(circuit) == TComplexity(clifford=1, rotations=1, t=1) + assert t_complexity_compat(circuit) == TComplexity(clifford=1, rotations=1, t=1) def test_circuit_operations(): @@ -179,32 +179,38 @@ def test_circuit_operations(): circuit = cirq.FrozenCircuit( cirq.T(q), cirq.X(q) ** 0.5, cirq.Rx(rads=0.1)(q), cirq.measure(q, key='m') ) - assert t_complexity(cirq.CircuitOperation(circuit)) == TComplexity(clifford=2, rotations=1, t=1) - assert t_complexity(cirq.CircuitOperation(circuit, repetitions=10)) == TComplexity( + assert t_complexity_compat(cirq.CircuitOperation(circuit)) == TComplexity( + clifford=2, rotations=1, t=1 + ) + assert t_complexity_compat(cirq.CircuitOperation(circuit, repetitions=10)) == TComplexity( clifford=20, rotations=10, t=10 ) circuit = cirq.FrozenCircuit(cirq.T(q) ** -1, cirq.Rx(rads=0.1)(q), cirq.measure(q, key='m')) - assert t_complexity(cirq.CircuitOperation(circuit)) == TComplexity(clifford=1, rotations=1, t=1) - assert t_complexity(cirq.CircuitOperation(circuit, repetitions=3)) == TComplexity( + assert t_complexity_compat(cirq.CircuitOperation(circuit)) == TComplexity( + clifford=1, rotations=1, t=1 + ) + assert t_complexity_compat(cirq.CircuitOperation(circuit, repetitions=3)) == TComplexity( clifford=3, rotations=3, t=3 ) def test_classically_controlled_operations(): q = cirq.NamedQubit('q') - assert t_complexity(cirq.X(q).with_classical_controls('c')) == TComplexity(clifford=1) - assert t_complexity(cirq.Rx(rads=0.1)(q).with_classical_controls('c')) == TComplexity( + assert t_complexity_compat(cirq.X(q).with_classical_controls('c')) == TComplexity(clifford=1) + assert t_complexity_compat(cirq.Rx(rads=0.1)(q).with_classical_controls('c')) == TComplexity( rotations=1 ) - assert t_complexity(cirq.T(q).with_classical_controls('c')) == TComplexity(t=1) + assert t_complexity_compat(cirq.T(q).with_classical_controls('c')) == TComplexity(t=1) def test_tagged_operations(): q = cirq.NamedQubit('q') - assert t_complexity(cirq.X(q).with_tags('tag1')) == TComplexity(clifford=1) - assert t_complexity(cirq.T(q).with_tags('tage1')) == TComplexity(t=1) - assert t_complexity(cirq.Ry(rads=0.1)(q).with_tags('tag1', 'tag2')) == TComplexity(rotations=1) + assert t_complexity_compat(cirq.X(q).with_tags('tag1')) == TComplexity(clifford=1) + assert t_complexity_compat(cirq.T(q).with_tags('tage1')) == TComplexity(t=1) + assert t_complexity_compat(cirq.Ry(rads=0.1)(q).with_tags('tag1', 'tag2')) == TComplexity( + rotations=1 + ) def test_cache_clear(): @@ -240,7 +246,8 @@ def __hash__(self): # TODO: t_complexity protocol will be refactored (#735) t_complexity.cache_clear() # type: ignore[attr-defined] op = Cachable2() - assert t_complexity([op, op]) == TComplexity() + assert t_complexity(op) == TComplexity() + assert t_complexity(op) == TComplexity() assert op.num_calls == 1 t_complexity.cache_clear() # type: ignore[attr-defined] diff --git a/qualtran/cirq_interop/testing.py b/qualtran/cirq_interop/testing.py index c8c31c69c..34428b838 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, Signature +from qualtran import Bloq, DecomposeNotImplementedError, DecomposeTypeError, 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 @@ -130,17 +130,17 @@ def assert_decompose_is_consistent_with_t_complexity(val: Any): 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_.") - 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}' - from qualtran import Bloq - from qualtran.bloqs.basic_gates import TGate + 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) - if not isinstance(val, Bloq): - return - _, sigma = val.call_graph() - actual = sigma.get(TGate(), 0) + sigma.get(TGate(is_adjoint=True), 0) - assert expected.t == actual, f'{expected.t} != {actual}' + 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 4f34c0c76..72c75e75c 100644 --- a/qualtran/cirq_interop/testing_test.py +++ b/qualtran/cirq_interop/testing_test.py @@ -11,13 +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. -from typing import Iterator +from typing import Dict, Iterator import cirq import numpy as np import pytest -from qualtran import QBit, Register, Side, Signature +from qualtran import Bloq, BloqBuilder, QBit, Register, Side, Signature, SoquetT +from qualtran.bloqs.basic_gates import XGate from qualtran.bloqs.mcmt.and_bloq import MultiAnd from qualtran.cirq_interop import testing from qualtran.cirq_interop.t_complexity_protocol import TComplexity @@ -84,19 +85,17 @@ def with_qubits(self, _): pass -class InconsistentDecompostion(cirq.Operation): +class InconsistentDecompostion(Bloq): + @property + def signature(self) -> 'Signature': + return Signature.build(q=1) + def _t_complexity_(self) -> TComplexity: return TComplexity(rotations=1) - def _decompose_(self) -> Iterator[cirq.OP_TREE]: - yield cirq.X(self.qubits[0]) - - @property - def qubits(self): - return tuple(cirq.LineQubit(3).range(3)) - - def with_qubits(self, _): - pass + 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(): diff --git a/qualtran/resource_counting/classify_bloqs.py b/qualtran/resource_counting/classify_bloqs.py index 948de03dc..afd4de394 100644 --- a/qualtran/resource_counting/classify_bloqs.py +++ b/qualtran/resource_counting/classify_bloqs.py @@ -17,7 +17,7 @@ import sympy -from qualtran import Adjoint, Bloq +from qualtran import Adjoint, Bloq, Controlled from qualtran.resource_counting.generalizers import ( ignore_alloc_free, ignore_cliffords, @@ -26,7 +26,7 @@ from qualtran.resource_counting.t_counts_from_sigma import t_counts_from_sigma if TYPE_CHECKING: - from qualtran.resource_counting import BloqCountT, GeneralizerT, SympySymbolAllocator + from qualtran.resource_counting import GeneralizerT def _get_basic_bloq_classification() -> Dict[str, str]: @@ -149,6 +149,15 @@ def bloq_is_clifford(b: Bloq): def bloq_is_rotation(b: Bloq): + from qualtran.bloqs.basic_gates import GlobalPhase, SGate, TGate from qualtran.bloqs.basic_gates.rotation import Rx, Ry, Rz, XPowGate, YPowGate, ZPowGate + if isinstance(b, Controlled): + # TODO https://github.com/quantumlib/Qualtran/issues/878 + # explicit representation of all two-qubit rotations. + if isinstance(b.subbloq, (SGate, TGate, GlobalPhase)): + return True + + return bloq_is_rotation(b.subbloq) + return isinstance(b, (Rz, Rx, Ry, ZPowGate, XPowGate, YPowGate))