From e3c15a38be403303dca8443f5c92daf5fc983f77 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Tue, 20 Aug 2024 14:17:22 -0700 Subject: [PATCH 01/10] move _HasEps --- qualtran/bloqs/basic_gates/rotation.py | 9 +-------- qualtran/resource_counting/t_counts_from_sigma.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/qualtran/bloqs/basic_gates/rotation.py b/qualtran/bloqs/basic_gates/rotation.py index 44901a0b7..a724ef2d3 100644 --- a/qualtran/bloqs/basic_gates/rotation.py +++ b/qualtran/bloqs/basic_gates/rotation.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property -from typing import Optional, Protocol, runtime_checkable, Tuple, Union +from typing import Optional, Tuple, Union import attrs import cirq @@ -27,13 +27,6 @@ from qualtran.symbolics import SymbolicFloat -@runtime_checkable -class _HasEps(Protocol): - """Protocol for typing `RotationBloq` base class mixin that has accuracy specified as eps.""" - - eps: float - - @frozen class ZPowGate(CirqGateAsBloqBase): r"""A gate that rotates around the Z axis of the Bloch sphere. diff --git a/qualtran/resource_counting/t_counts_from_sigma.py b/qualtran/resource_counting/t_counts_from_sigma.py index 78bbb8156..0d969544a 100644 --- a/qualtran/resource_counting/t_counts_from_sigma.py +++ b/qualtran/resource_counting/t_counts_from_sigma.py @@ -13,7 +13,7 @@ # limitations under the License. import inspect import sys -from typing import cast, Mapping, Optional, Tuple, Type, TYPE_CHECKING +from typing import cast, Mapping, Optional, Protocol, runtime_checkable, Tuple, Type, TYPE_CHECKING import cirq @@ -21,13 +21,18 @@ if TYPE_CHECKING: from qualtran import Bloq - from qualtran.bloqs.basic_gates.rotation import _HasEps + + +@runtime_checkable +class _HasEps(Protocol): + """Protocol for typing `RotationBloq` base class mixin that has accuracy specified as eps.""" + + eps: float def _get_all_rotation_types() -> Tuple[Type['_HasEps'], ...]: """Returns all classes defined in bloqs.basic_gates which have an attribute `eps`.""" from qualtran.bloqs.basic_gates import GlobalPhase - from qualtran.bloqs.basic_gates.rotation import _HasEps bloqs_to_exclude = [GlobalPhase] From 6af1b8b291bcaf7c6753e5da2afdcba0241b2c46 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Tue, 20 Aug 2024 14:31:24 -0700 Subject: [PATCH 02/10] reduce dependence on `_get_all_rotation_types` --- .../chemistry/trotter/hubbard/qpe_cost_optimization.ipynb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qualtran/bloqs/chemistry/trotter/hubbard/qpe_cost_optimization.ipynb b/qualtran/bloqs/chemistry/trotter/hubbard/qpe_cost_optimization.ipynb index a2fc00edf..6f4e10cdd 100644 --- a/qualtran/bloqs/chemistry/trotter/hubbard/qpe_cost_optimization.ipynb +++ b/qualtran/bloqs/chemistry/trotter/hubbard/qpe_cost_optimization.ipynb @@ -111,7 +111,7 @@ "import numpy as np\n", "import sympy\n", "\n", - "from qualtran.resource_counting.t_counts_from_sigma import _get_all_rotation_types\n", + "from qualtran.resource_counting.classify_bloqs import bloq_is_rotation\n", "from qualtran.resource_counting.generalizers import PHI\n", "from qualtran.cirq_interop.t_complexity_protocol import TComplexity\n", "from qualtran import Bloq\n", @@ -130,11 +130,10 @@ "\n", "\n", "def t_and_rot_counts_from_sigma(sigma: Dict['Bloq', Union[int, 'sympy.Expr']]) -> Tuple[int, int]:\n", - " rotation_types = _get_all_rotation_types()\n", " ret = sigma.get(TGate(), 0)\n", " n_rot = 0\n", " for bloq, counts in sigma.items():\n", - " if isinstance(bloq, rotation_types):\n", + " if bloq_is_rotation(bloq):\n", " n_rot += counts\n", " return ret, n_rot\n", "\n", From 5199028eb3f16bd4c765dbae471fdd1999e00a5b Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Tue, 20 Aug 2024 14:32:36 -0700 Subject: [PATCH 03/10] remove `_get_all_rotation_types` --- .../resource_counting/t_counts_from_sigma.py | 34 +++---------------- .../t_counts_from_sigma_test.py | 19 +---------- 2 files changed, 5 insertions(+), 48 deletions(-) diff --git a/qualtran/resource_counting/t_counts_from_sigma.py b/qualtran/resource_counting/t_counts_from_sigma.py index 0d969544a..036de4764 100644 --- a/qualtran/resource_counting/t_counts_from_sigma.py +++ b/qualtran/resource_counting/t_counts_from_sigma.py @@ -11,9 +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. -import inspect -import sys -from typing import cast, Mapping, Optional, Protocol, runtime_checkable, Tuple, Type, TYPE_CHECKING +from typing import Mapping, TYPE_CHECKING import cirq @@ -23,38 +21,14 @@ from qualtran import Bloq -@runtime_checkable -class _HasEps(Protocol): - """Protocol for typing `RotationBloq` base class mixin that has accuracy specified as eps.""" - - eps: float - - -def _get_all_rotation_types() -> Tuple[Type['_HasEps'], ...]: - """Returns all classes defined in bloqs.basic_gates which have an attribute `eps`.""" - from qualtran.bloqs.basic_gates import GlobalPhase - - bloqs_to_exclude = [GlobalPhase] - - return tuple( - cast(Type['_HasEps'], v) # Can't use `issubclass` with protocols with attributes. - for (_, v) in inspect.getmembers(sys.modules['qualtran.bloqs.basic_gates'], inspect.isclass) - if isinstance(v, _HasEps) and v not in bloqs_to_exclude - ) - - -def t_counts_from_sigma( - sigma: Mapping['Bloq', SymbolicInt], - rotation_types: Optional[Tuple[Type['_HasEps'], ...]] = None, -) -> SymbolicInt: +def t_counts_from_sigma(sigma: Mapping['Bloq', SymbolicInt]) -> SymbolicInt: """Aggregates T-counts from a sigma dictionary by summing T-costs for all rotation bloqs.""" from qualtran.bloqs.basic_gates import TGate from qualtran.cirq_interop.t_complexity_protocol import TComplexity + from qualtran.resource_counting.classify_bloqs import bloq_is_rotation - if rotation_types is None: - rotation_types = _get_all_rotation_types() ret = sigma.get(TGate(), 0) + sigma.get(TGate().adjoint(), 0) for bloq, counts in sigma.items(): - if isinstance(bloq, rotation_types) and not cirq.has_stabilizer_effect(bloq): + if bloq_is_rotation(bloq) and not cirq.has_stabilizer_effect(bloq): ret += ceil(TComplexity.rotation_cost(bloq.eps)) * counts return ret diff --git a/qualtran/resource_counting/t_counts_from_sigma_test.py b/qualtran/resource_counting/t_counts_from_sigma_test.py index 181002c9e..e374e408c 100644 --- a/qualtran/resource_counting/t_counts_from_sigma_test.py +++ b/qualtran/resource_counting/t_counts_from_sigma_test.py @@ -19,7 +19,6 @@ Rx, Ry, Rz, - SU2RotationGate, TGate, Toffoli, XPowGate, @@ -27,23 +26,7 @@ ZPowGate, ) from qualtran.cirq_interop.t_complexity_protocol import TComplexity -from qualtran.resource_counting.t_counts_from_sigma import ( - _get_all_rotation_types, - t_counts_from_sigma, -) - - -def test_all_rotation_types(): - assert set(_get_all_rotation_types()) == { - CZPowGate, - Rx, - Ry, - Rz, - XPowGate, - YPowGate, - ZPowGate, - SU2RotationGate, - } +from qualtran.resource_counting.t_counts_from_sigma import t_counts_from_sigma def test_t_counts_from_sigma(): From ab607e6e8e695c0fb9dbcc6d13ab3d230170e444 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Tue, 20 Aug 2024 16:08:31 -0700 Subject: [PATCH 04/10] mypy --- qualtran/resource_counting/t_counts_from_sigma.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qualtran/resource_counting/t_counts_from_sigma.py b/qualtran/resource_counting/t_counts_from_sigma.py index 036de4764..85906ce10 100644 --- a/qualtran/resource_counting/t_counts_from_sigma.py +++ b/qualtran/resource_counting/t_counts_from_sigma.py @@ -30,5 +30,6 @@ def t_counts_from_sigma(sigma: Mapping['Bloq', SymbolicInt]) -> SymbolicInt: ret = sigma.get(TGate(), 0) + sigma.get(TGate().adjoint(), 0) for bloq, counts in sigma.items(): if bloq_is_rotation(bloq) and not cirq.has_stabilizer_effect(bloq): + assert hasattr(bloq, 'eps') ret += ceil(TComplexity.rotation_cost(bloq.eps)) * counts return ret From 51af575e8d03bd2e35fcf1d7b5a70f1c188040fd Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Tue, 20 Aug 2024 16:09:41 -0700 Subject: [PATCH 05/10] reorganize tests for `t_counts_from_sigma` --- .../t_counts_from_sigma_test.py | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/qualtran/resource_counting/t_counts_from_sigma_test.py b/qualtran/resource_counting/t_counts_from_sigma_test.py index e374e408c..e2fc17e07 100644 --- a/qualtran/resource_counting/t_counts_from_sigma_test.py +++ b/qualtran/resource_counting/t_counts_from_sigma_test.py @@ -11,9 +11,10 @@ # 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 pytest import sympy +from qualtran import Bloq from qualtran.bloqs.basic_gates import ( CZPowGate, Rx, @@ -27,33 +28,37 @@ ) from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting.t_counts_from_sigma import t_counts_from_sigma +from qualtran.symbolics import SymbolicFloat + +EPS: SymbolicFloat = sympy.Symbol("eps") + + +@pytest.mark.parametrize( + ("bloq", "t_count"), [(TGate(), 1), pytest.param(Toffoli(), 4, marks=pytest.mark.xfail)] +) +def test_t_counts_from_sigma_known(bloq: Bloq, t_count: int): + assert t_counts_from_sigma({bloq: 1}) == t_count -def test_t_counts_from_sigma(): - z_eps1, z_eps2, x_eps, y_eps, cz_eps = sympy.symbols('z_eps1, z_eps2, x_eps, y_eps, cz_eps') - sigma = { - ZPowGate(eps=z_eps1): 1, - ZPowGate(eps=z_eps2): 2, - ZPowGate(0.01, eps=z_eps1): 1, - ZPowGate(0.01, eps=z_eps2): 2, - Rz(0.01, eps=z_eps2): 3, - Rx(0.01, eps=x_eps): 4, - XPowGate(eps=x_eps): 5, - XPowGate(0.01, eps=x_eps): 5, - Ry(0.01, eps=y_eps): 6, - YPowGate(eps=y_eps): 7, - YPowGate(0.01, eps=y_eps): 7, - CZPowGate(eps=cz_eps): 20, - CZPowGate(0.01, eps=cz_eps): 20, - TGate(): 100, - Toffoli(): 200, - } - expected_t_count = ( - +100 - + 1 * TComplexity.rotation_cost(z_eps1) - + 5 * TComplexity.rotation_cost(z_eps2) - + 9 * TComplexity.rotation_cost(x_eps) - + 13 * TComplexity.rotation_cost(y_eps) - + 20 * TComplexity.rotation_cost(cz_eps) - ) - assert t_counts_from_sigma(sigma) == expected_t_count +@pytest.mark.parametrize( + "bloq", + [ + ZPowGate(0.01, eps=EPS), + Rz(0.01, eps=EPS), + Rx(0.01, eps=EPS), + XPowGate(0.01, eps=EPS), + Ry(0.01, eps=EPS), + YPowGate(0.01, eps=EPS), + CZPowGate(0.01, eps=EPS), + ], +) +def test_t_counts_from_sigma_for_rotation_with_eps(bloq: Bloq): + expected_t_count = TComplexity.rotation_cost(EPS) + assert t_counts_from_sigma({bloq: 1}) == expected_t_count + + +@pytest.mark.parametrize( + "bloq", [ZPowGate(eps=EPS), XPowGate(eps=EPS), YPowGate(eps=EPS), CZPowGate(eps=EPS)] +) +def test_t_counts_from_sigma_zero(bloq: Bloq): + assert t_counts_from_sigma({bloq: 1}) == 0 From 623413d52040e40c096d7b9dcf978d19946988a2 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Tue, 20 Aug 2024 16:13:18 -0700 Subject: [PATCH 06/10] mypy --- qualtran/bloqs/basic_gates/rotation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qualtran/bloqs/basic_gates/rotation.py b/qualtran/bloqs/basic_gates/rotation.py index a724ef2d3..b6941bfbc 100644 --- a/qualtran/bloqs/basic_gates/rotation.py +++ b/qualtran/bloqs/basic_gates/rotation.py @@ -108,7 +108,7 @@ def _z_pow() -> ZPowGate: class CZPowGate(CirqGateAsBloqBase): exponent: float = 1.0 global_shift: float = 0.0 - eps: float = 1e-11 + eps: SymbolicFloat = 1e-11 def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") @@ -173,7 +173,7 @@ class XPowGate(CirqGateAsBloqBase): """ exponent: Union[sympy.Expr, float] = 1.0 global_shift: float = 0.0 - eps: float = 1e-11 + eps: SymbolicFloat = 1e-11 def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") @@ -243,7 +243,7 @@ class YPowGate(CirqGateAsBloqBase): """ exponent: Union[sympy.Expr, float] = 1.0 global_shift: float = 0.0 - eps: float = 1e-11 + eps: SymbolicFloat = 1e-11 def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") @@ -311,7 +311,7 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) - @frozen class Rx(CirqGateAsBloqBase): angle: Union[sympy.Expr, float] - eps: float = 1e-11 + eps: SymbolicFloat = 1e-11 def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") @@ -332,7 +332,7 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) - @frozen class Ry(CirqGateAsBloqBase): angle: Union[sympy.Expr, float] - eps: float = 1e-11 + eps: SymbolicFloat = 1e-11 def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") From f974c3fbaff348d00c1c81562bd7cd8070ee821a Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Tue, 20 Aug 2024 23:16:56 -0700 Subject: [PATCH 07/10] fix error --- qualtran/resource_counting/t_counts_from_sigma.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qualtran/resource_counting/t_counts_from_sigma.py b/qualtran/resource_counting/t_counts_from_sigma.py index 85906ce10..2d04b75b7 100644 --- a/qualtran/resource_counting/t_counts_from_sigma.py +++ b/qualtran/resource_counting/t_counts_from_sigma.py @@ -11,15 +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 Mapping, TYPE_CHECKING +from typing import Mapping import cirq +from qualtran import Bloq, Controlled from qualtran.symbolics import ceil, SymbolicInt -if TYPE_CHECKING: - from qualtran import Bloq - def t_counts_from_sigma(sigma: Mapping['Bloq', SymbolicInt]) -> SymbolicInt: """Aggregates T-counts from a sigma dictionary by summing T-costs for all rotation bloqs.""" @@ -30,6 +28,9 @@ def t_counts_from_sigma(sigma: Mapping['Bloq', SymbolicInt]) -> SymbolicInt: ret = sigma.get(TGate(), 0) + sigma.get(TGate().adjoint(), 0) for bloq, counts in sigma.items(): if bloq_is_rotation(bloq) and not cirq.has_stabilizer_effect(bloq): + if isinstance(bloq, Controlled): + # TODO native controlled rotation bloqs missing (CRz, CRy etc.) + bloq = bloq.subbloq assert hasattr(bloq, 'eps') ret += ceil(TComplexity.rotation_cost(bloq.eps)) * counts return ret From a7d768151002d426276529773cb1627c9e21c316 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Tue, 20 Aug 2024 23:22:43 -0700 Subject: [PATCH 08/10] replace `t_counts_from_sigma(bloq)` with `get_cost_value(bloq, QECGatesCost())` --- qualtran/bloqs/data_loading/select_swap_qrom_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qualtran/bloqs/data_loading/select_swap_qrom_test.py b/qualtran/bloqs/data_loading/select_swap_qrom_test.py index e9d7f146a..49a0bab72 100644 --- a/qualtran/bloqs/data_loading/select_swap_qrom_test.py +++ b/qualtran/bloqs/data_loading/select_swap_qrom_test.py @@ -27,7 +27,7 @@ ) from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity from qualtran.cirq_interop.testing import assert_circuit_inp_out_cirqsim -from qualtran.resource_counting.t_counts_from_sigma import t_counts_from_sigma +from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost from qualtran.testing import assert_valid_bloq_decomposition @@ -187,8 +187,9 @@ def test_qroam_t_complexity(): qroam = SelectSwapQROM.build_from_data( [1, 2, 3, 4, 5, 6, 7, 8], target_bitsizes=(4,), log_block_sizes=(2,) ) - _, sigma = qroam.call_graph() - assert t_counts_from_sigma(sigma) == qroam.t_complexity().t == 192 + gate_counts = get_cost_value(qroam, QECGatesCost()) + assert gate_counts == GateCounts(t=192, clifford=1082) + assert qroam.t_complexity() == TComplexity(t=192, clifford=1082) def test_qroam_many_registers(): From 25c5c90d99f7d789f5f2bef96f19da84cbb21180 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Thu, 22 Aug 2024 16:52:58 -0700 Subject: [PATCH 09/10] revert test --- .../t_counts_from_sigma_test.py | 63 +++++++++---------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/qualtran/resource_counting/t_counts_from_sigma_test.py b/qualtran/resource_counting/t_counts_from_sigma_test.py index e2fc17e07..e374e408c 100644 --- a/qualtran/resource_counting/t_counts_from_sigma_test.py +++ b/qualtran/resource_counting/t_counts_from_sigma_test.py @@ -11,10 +11,9 @@ # 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 pytest + import sympy -from qualtran import Bloq from qualtran.bloqs.basic_gates import ( CZPowGate, Rx, @@ -28,37 +27,33 @@ ) from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting.t_counts_from_sigma import t_counts_from_sigma -from qualtran.symbolics import SymbolicFloat - -EPS: SymbolicFloat = sympy.Symbol("eps") - - -@pytest.mark.parametrize( - ("bloq", "t_count"), [(TGate(), 1), pytest.param(Toffoli(), 4, marks=pytest.mark.xfail)] -) -def test_t_counts_from_sigma_known(bloq: Bloq, t_count: int): - assert t_counts_from_sigma({bloq: 1}) == t_count -@pytest.mark.parametrize( - "bloq", - [ - ZPowGate(0.01, eps=EPS), - Rz(0.01, eps=EPS), - Rx(0.01, eps=EPS), - XPowGate(0.01, eps=EPS), - Ry(0.01, eps=EPS), - YPowGate(0.01, eps=EPS), - CZPowGate(0.01, eps=EPS), - ], -) -def test_t_counts_from_sigma_for_rotation_with_eps(bloq: Bloq): - expected_t_count = TComplexity.rotation_cost(EPS) - assert t_counts_from_sigma({bloq: 1}) == expected_t_count - - -@pytest.mark.parametrize( - "bloq", [ZPowGate(eps=EPS), XPowGate(eps=EPS), YPowGate(eps=EPS), CZPowGate(eps=EPS)] -) -def test_t_counts_from_sigma_zero(bloq: Bloq): - assert t_counts_from_sigma({bloq: 1}) == 0 +def test_t_counts_from_sigma(): + z_eps1, z_eps2, x_eps, y_eps, cz_eps = sympy.symbols('z_eps1, z_eps2, x_eps, y_eps, cz_eps') + sigma = { + ZPowGate(eps=z_eps1): 1, + ZPowGate(eps=z_eps2): 2, + ZPowGate(0.01, eps=z_eps1): 1, + ZPowGate(0.01, eps=z_eps2): 2, + Rz(0.01, eps=z_eps2): 3, + Rx(0.01, eps=x_eps): 4, + XPowGate(eps=x_eps): 5, + XPowGate(0.01, eps=x_eps): 5, + Ry(0.01, eps=y_eps): 6, + YPowGate(eps=y_eps): 7, + YPowGate(0.01, eps=y_eps): 7, + CZPowGate(eps=cz_eps): 20, + CZPowGate(0.01, eps=cz_eps): 20, + TGate(): 100, + Toffoli(): 200, + } + expected_t_count = ( + +100 + + 1 * TComplexity.rotation_cost(z_eps1) + + 5 * TComplexity.rotation_cost(z_eps2) + + 9 * TComplexity.rotation_cost(x_eps) + + 13 * TComplexity.rotation_cost(y_eps) + + 20 * TComplexity.rotation_cost(cz_eps) + ) + assert t_counts_from_sigma(sigma) == expected_t_count From 52080550a2a998dd085d9adb11bfd11e4675b9b0 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Thu, 22 Aug 2024 17:45:20 -0700 Subject: [PATCH 10/10] link issue --- qualtran/resource_counting/t_counts_from_sigma.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qualtran/resource_counting/t_counts_from_sigma.py b/qualtran/resource_counting/t_counts_from_sigma.py index 2d04b75b7..07ffc8f8e 100644 --- a/qualtran/resource_counting/t_counts_from_sigma.py +++ b/qualtran/resource_counting/t_counts_from_sigma.py @@ -30,6 +30,7 @@ def t_counts_from_sigma(sigma: Mapping['Bloq', SymbolicInt]) -> SymbolicInt: if bloq_is_rotation(bloq) and not cirq.has_stabilizer_effect(bloq): if isinstance(bloq, Controlled): # TODO native controlled rotation bloqs missing (CRz, CRy etc.) + # https://github.com/quantumlib/Qualtran/issues/878 bloq = bloq.subbloq assert hasattr(bloq, 'eps') ret += ceil(TComplexity.rotation_cost(bloq.eps)) * counts