From 714303191d8781ce08e9138b1c10284e68b827f3 Mon Sep 17 00:00:00 2001 From: Noureldin Date: Thu, 15 Aug 2024 21:24:08 -0700 Subject: [PATCH] Create controlled greater than bloq (#1283) --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 1 + qualtran/bloqs/arithmetic/__init__.py | 1 + qualtran/bloqs/arithmetic/addition.py | 6 +- qualtran/bloqs/arithmetic/addition_test.py | 13 ++ qualtran/bloqs/arithmetic/bitwise.py | 2 +- qualtran/bloqs/arithmetic/comparison.ipynb | 120 ++++++++++++++- qualtran/bloqs/arithmetic/comparison.py | 145 ++++++++++++++++++- qualtran/bloqs/arithmetic/comparison_test.py | 61 +++++++- qualtran/bloqs/basic_gates/on_each.py | 13 +- qualtran/serialization/resolver_dict.py | 1 + 10 files changed, 356 insertions(+), 7 deletions(-) diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index 3943eda03..eadded3f7 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -438,6 +438,7 @@ qualtran.bloqs.arithmetic.comparison._BI_QUBITS_MIXER_DOC, qualtran.bloqs.arithmetic.comparison._SQ_CMP_DOC, qualtran.bloqs.arithmetic.comparison._LEQ_DOC, + qualtran.bloqs.arithmetic.comparison._CLinearDepthGreaterThan_DOC, ], ), NotebookSpecV2( diff --git a/qualtran/bloqs/arithmetic/__init__.py b/qualtran/bloqs/arithmetic/__init__.py index 68f726779..694b36bd2 100644 --- a/qualtran/bloqs/arithmetic/__init__.py +++ b/qualtran/bloqs/arithmetic/__init__.py @@ -16,6 +16,7 @@ from qualtran.bloqs.arithmetic.bitwise import BitwiseNot, Xor, XorK from qualtran.bloqs.arithmetic.comparison import ( BiQubitsMixer, + CLinearDepthGreaterThan, EqualsAConstant, GreaterThan, GreaterThanConstant, diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 0ef9cd1a2..c605b512d 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -291,10 +291,14 @@ def adjoint(self) -> 'OutOfPlaceAdder': return evolve(self, is_adjoint=not self.is_adjoint) def on_classical_vals( - self, *, a: 'ClassicalValT', b: 'ClassicalValT' + self, *, a: 'ClassicalValT', b: 'ClassicalValT', c: Optional['ClassicalValT'] = None ) -> Dict[str, 'ClassicalValT']: if isinstance(self.bitsize, sympy.Expr): raise ValueError(f'Classical simulation is not support for symbolic bloq {self}') + if self.is_adjoint: + assert c is not None + return {'a': a, 'b': b} + assert c is None return { 'a': a, 'b': b, diff --git a/qualtran/bloqs/arithmetic/addition_test.py b/qualtran/bloqs/arithmetic/addition_test.py index c54c5d916..229c6c78c 100644 --- a/qualtran/bloqs/arithmetic/addition_test.py +++ b/qualtran/bloqs/arithmetic/addition_test.py @@ -377,3 +377,16 @@ def test_classical_add_k_signed(bitsize, k, x, cvs, ctrls, result): @pytest.mark.notebook def test_notebook(): qlt_testing.execute_notebook('addition') + + +@pytest.mark.parametrize('bitsize', range(1, 5)) +def test_outofplaceadder_classical_action(bitsize): + b = OutOfPlaceAdder(bitsize) + cb = b.decompose_bloq() + for x, y in itertools.product(range(2**bitsize), repeat=2): + assert b.call_classically(a=x, b=y) == cb.call_classically(a=x, b=y) + + b = OutOfPlaceAdder(bitsize).adjoint() + cb = b.decompose_bloq() + for x, y in itertools.product(range(2**bitsize), repeat=2): + assert b.call_classically(a=x, b=y, c=x + y) == cb.call_classically(a=x, b=y, c=x + y) diff --git a/qualtran/bloqs/arithmetic/bitwise.py b/qualtran/bloqs/arithmetic/bitwise.py index 61b5eb6b9..fbc092b38 100644 --- a/qualtran/bloqs/arithmetic/bitwise.py +++ b/qualtran/bloqs/arithmetic/bitwise.py @@ -210,7 +210,7 @@ def adjoint(self) -> 'BitwiseNot': return self def build_composite_bloq(self, bb: 'BloqBuilder', x: 'Soquet') -> dict[str, 'SoquetT']: - x = bb.add(OnEach(self.dtype.num_qubits, XGate()), q=x) + x = bb.add(OnEach(self.dtype.num_qubits, XGate(), self.dtype), q=x) return {'x': x} def wire_symbol( diff --git a/qualtran/bloqs/arithmetic/comparison.ipynb b/qualtran/bloqs/arithmetic/comparison.ipynb index d8dfa2bb9..7001178f8 100644 --- a/qualtran/bloqs/arithmetic/comparison.ipynb +++ b/qualtran/bloqs/arithmetic/comparison.ipynb @@ -800,6 +800,124 @@ "show_call_graph(leq_g)\n", "show_counts_sigma(leq_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "27543be2", + "metadata": { + "cq.autogen": "CLinearDepthGreaterThan.bloq_doc.md" + }, + "source": [ + "## `CLinearDepthGreaterThan`\n", + "Controlled greater than between two integers.\n", + "\n", + "Implements $\\ket{c}\\ket{a}\\ket{b}\\ket{t} \\xrightarrow[]{} \\ket{c}\\ket{a}\\ket{b}\\ket{t ⨁ ((a > b)c)}>$\n", + "using $n+2$ Toffoli gates.\n", + "\n", + "Note: the true cost is $n+1$ but an extra Toffoli comes from OutOfPlaceAdder which operates\n", + "on $n+1$ qubits rather than $n$. Changing the definition of OutOfPlaceAdder will remove this\n", + "extra Toffoli.\n", + "\n", + "This comparator relies on the fact that ~(~b + a) = b - a. If a > b, then b - a < 0. We\n", + "implement it by flipping all the bits in b, computing the first half of the addition circuit,\n", + "copying out the carry, and uncomputing the addition circuit.\n", + "\n", + "#### Parameters\n", + " - `dtype`: type of the integer registers.\n", + " - `cv`: ctrl value at which the bloq is active. \n", + "\n", + "#### Registers\n", + " - `a`: dtype input registers.\n", + " - `b`: dtype input registers.\n", + " - `target`: A single bit output register to store the result of a > b. \n", + "\n", + "#### References\n", + " - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). \n", + " - [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2306.08585). page 7.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d4533e31", + "metadata": { + "cq.autogen": "CLinearDepthGreaterThan.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import CLinearDepthGreaterThan" + ] + }, + { + "cell_type": "markdown", + "id": "9dcfa76a", + "metadata": { + "cq.autogen": "CLinearDepthGreaterThan.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b7ec12f", + "metadata": { + "cq.autogen": "CLinearDepthGreaterThan.clineardepthgreaterthan_example" + }, + "outputs": [], + "source": [ + "clineardepthgreaterthan_example = CLinearDepthGreaterThan(QInt(5))" + ] + }, + { + "cell_type": "markdown", + "id": "70bc70c6", + "metadata": { + "cq.autogen": "CLinearDepthGreaterThan.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6bf0dce", + "metadata": { + "cq.autogen": "CLinearDepthGreaterThan.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([clineardepthgreaterthan_example],\n", + " ['`clineardepthgreaterthan_example`'])" + ] + }, + { + "cell_type": "markdown", + "id": "9a2d7925", + "metadata": { + "cq.autogen": "CLinearDepthGreaterThan.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30e9a77b", + "metadata": { + "cq.autogen": "CLinearDepthGreaterThan.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "clineardepthgreaterthan_example_g, clineardepthgreaterthan_example_sigma = clineardepthgreaterthan_example.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(clineardepthgreaterthan_example_g)\n", + "show_counts_sigma(clineardepthgreaterthan_example_sigma)" + ] } ], "metadata": { @@ -818,7 +936,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/qualtran/bloqs/arithmetic/comparison.py b/qualtran/bloqs/arithmetic/comparison.py index c4839839c..76cf7d3b6 100644 --- a/qualtran/bloqs/arithmetic/comparison.py +++ b/qualtran/bloqs/arithmetic/comparison.py @@ -42,6 +42,8 @@ GateWithRegisters, QAny, QBit, + QInt, + QMontgomeryUInt, QUInt, Register, Side, @@ -49,11 +51,16 @@ Soquet, SoquetT, ) +from qualtran.bloqs.arithmetic.addition import OutOfPlaceAdder +from qualtran.bloqs.arithmetic.bitwise import BitwiseNot +from qualtran.bloqs.arithmetic.conversions.sign_extension import SignExtend from qualtran.bloqs.basic_gates import CNOT, XGate +from qualtran.bloqs.bookkeeping import Cast from qualtran.bloqs.mcmt import MultiControlX from qualtran.bloqs.mcmt.and_bloq import And, MultiAnd from qualtran.drawing import WireSymbol -from qualtran.drawing.musical_score import Text, TextBox +from qualtran.drawing.musical_score import Circle, Text, TextBox +from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.symbolics import HasLength, is_symbolic, SymbolicInt if TYPE_CHECKING: @@ -1017,3 +1024,139 @@ def _eq_k() -> EqualsAConstant: _EQUALS_K_DOC = BloqDocSpec(bloq_cls=EqualsAConstant, examples=[_eq_k]) + + +@frozen +class CLinearDepthGreaterThan(Bloq): + r"""Controlled greater than between two integers. + + Implements $\ket{c}\ket{a}\ket{b}\ket{t} \xrightarrow[]{} \ket{c}\ket{a}\ket{b}\ket{t ⨁ ((a > b)c)}>$ + using $n+2$ Toffoli gates. + + Note: the true cost is $n+1$ but an extra Toffoli comes from OutOfPlaceAdder which operates + on $n+1$ qubits rather than $n$. Changing the definition of OutOfPlaceAdder will remove this + extra Toffoli. + + This comparator relies on the fact that ~(~b + a) = b - a. If a > b, then b - a < 0. We + implement it by flipping all the bits in b, computing the first half of the addition circuit, + copying out the carry, and uncomputing the addition circuit. + + Args: + dtype: type of the integer registers. + cv: ctrl value at which the bloq is active. + + Registers: + a: dtype input registers. + b: dtype input registers. + target: A single bit output register to store the result of a > b. + + References: + [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). + + [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2306.08585) + page 7. + """ + + dtype: Union[QInt, QUInt, QMontgomeryUInt] + cv: int = 1 + + @cached_property + def signature(self) -> Signature: + return Signature.build_from_dtypes(ctrl=QBit(), a=self.dtype, b=self.dtype, target=QBit()) + + def wire_symbol( + self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() + ) -> 'WireSymbol': + if reg is None: + return Text('') + if reg.name == 'ctrl': + return Circle(filled=self.cv == 1) + if reg.name == "a": + return TextBox('a') + if reg.name == "b": + return TextBox('b') + if reg.name == "target": + return TextBox('t⨁((a>b)c)') + raise ValueError(f'Unknown register name {reg.name}') + + def build_composite_bloq( + self, bb: 'BloqBuilder', ctrl: 'Soquet', a: 'Soquet', b: 'Soquet', target: 'Soquet' + ) -> Dict[str, 'SoquetT']: + + if isinstance(self.dtype, QInt): + a = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), x=a) + b = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), x=b) + else: + a = bb.join(np.concatenate([[bb.allocate(1)], bb.split(a)])) + b = bb.join(np.concatenate([[bb.allocate(1)], bb.split(b)])) + + dtype = attrs.evolve(self.dtype, bitsize=self.dtype.bitsize + 1) + b = bb.add(BitwiseNot(dtype), x=b) # b := -b-1 + a = bb.add(Cast(dtype, QUInt(dtype.bitsize)), reg=a) + b = bb.add(Cast(dtype, QUInt(dtype.bitsize)), reg=b) + a, b, c = bb.add(OutOfPlaceAdder(self.dtype.bitsize + 1), a=a, b=b) # c := a - b - 1 + c = bb.add(BitwiseNot(QUInt(dtype.bitsize + 1)), x=c) # c := b - a + + # Update `target` + c_arr = bb.split(c) + # The sign bit is usually the 0th bit however since we already appended an extra bit + # to the input registers and OutOfPlaceAdder is unsigned and stores the result in + # number bits + 1 (i.e. we are adding two extra bits), the sign bit becomes the 1st bit + # with the 0th bit indicating whether an overflow happened or not. + (ctrl, c_arr[1]), target = bb.add( + MultiControlX((self.cv, 1)), controls=np.array([ctrl, c_arr[1]]), target=target + ) + c = bb.join(c_arr) + + # Uncompute + c = bb.add(BitwiseNot(QUInt(dtype.bitsize + 1)), x=c) + a, b = bb.add(OutOfPlaceAdder(self.dtype.bitsize + 1).adjoint(), a=a, b=b, c=c) + a = bb.add(Cast(dtype, QUInt(dtype.bitsize)).adjoint(), reg=a) + b = bb.add(Cast(dtype, QUInt(dtype.bitsize)).adjoint(), reg=b) + b = bb.add(BitwiseNot(dtype), x=b) + + if isinstance(self.dtype, QInt): + a = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)).adjoint(), x=a) + b = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)).adjoint(), x=b) + else: + a_arr = bb.split(a) + a = bb.join(a_arr[1:]) + b_arr = bb.split(b) + b = bb.join(b_arr[1:]) + bb.free(a_arr[0]) + bb.free(b_arr[0]) + return {'ctrl': ctrl, 'a': a, 'b': b, 'target': target} + + def on_classical_vals( + self, ctrl: int, a: int, b: int, target: int + ) -> Dict[str, 'ClassicalValT']: + if ctrl == self.cv: + return {'ctrl': ctrl, 'a': a, 'b': b, 'target': target ^ (a > b)} + return {'ctrl': ctrl, 'a': a, 'b': b, 'target': target} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: + signed_ops = [] + if isinstance(self.dtype, QInt): + signed_ops = [ + (SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), 2), + (SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)).adjoint(), 2), + ] + dtype = attrs.evolve(self.dtype, bitsize=self.dtype.bitsize + 1) + return { + (BitwiseNot(dtype), 2), + (BitwiseNot(QUInt(dtype.bitsize + 1)), 2), + (OutOfPlaceAdder(self.dtype.bitsize + 1).adjoint(), 1), + (OutOfPlaceAdder(self.dtype.bitsize + 1), 1), + (MultiControlX((self.cv, 1)), 1), + }.union(signed_ops) + + +@bloq_example(generalizer=ignore_split_join) +def _clineardepthgreaterthan_example() -> CLinearDepthGreaterThan: + clineardepthgreaterthan_example = CLinearDepthGreaterThan(QInt(5)) + return clineardepthgreaterthan_example + + +_CLinearDepthGreaterThan_DOC = BloqDocSpec( + bloq_cls=CLinearDepthGreaterThan, examples=[_clineardepthgreaterthan_example] +) diff --git a/qualtran/bloqs/arithmetic/comparison_test.py b/qualtran/bloqs/arithmetic/comparison_test.py index cd54f4efd..f27e1dee6 100644 --- a/qualtran/bloqs/arithmetic/comparison_test.py +++ b/qualtran/bloqs/arithmetic/comparison_test.py @@ -17,16 +17,19 @@ import cirq import numpy as np import pytest +import sympy import qualtran.testing as qlt_testing -from qualtran import BloqBuilder, QUInt +from qualtran import BloqBuilder, QInt, QMontgomeryUInt, QUInt from qualtran.bloqs.arithmetic.comparison import ( + _clineardepthgreaterthan_example, _eq_k, _greater_than, _gt_k, _leq_symb, _lt_k_symb, BiQubitsMixer, + CLinearDepthGreaterThan, EqualsAConstant, GreaterThan, GreaterThanConstant, @@ -40,6 +43,10 @@ from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join +def test_clineardepthgreaterthan_example(bloq_autotester): + bloq_autotester(_clineardepthgreaterthan_example) + + def test_greater_than(bloq_autotester): bloq_autotester(_greater_than) @@ -321,3 +328,55 @@ def test_decomposition_frees_ancilla(gate): qubit_manager = cirq.ops.GreedyQubitManager(prefix='_test') _ = cirq.decompose(op, context=cirq.DecompositionContext(qubit_manager)) assert len(qubit_manager._used_qubits) == 0 + + +@pytest.mark.parametrize('ctrl', range(2)) +@pytest.mark.parametrize('dtype', [QUInt, QMontgomeryUInt]) +@pytest.mark.parametrize('bitsize', range(1, 5)) +def test_clineardepthgreaterthan_classical_action_unsigned(ctrl, dtype, bitsize): + b = CLinearDepthGreaterThan(dtype(bitsize), ctrl) + 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 + ) + + +@pytest.mark.parametrize('ctrl', range(2)) +@pytest.mark.parametrize('bitsize', range(2, 5)) +def test_clineardepthgreaterthan_classical_action_signed(ctrl, bitsize): + b = CLinearDepthGreaterThan(QInt(bitsize), ctrl) + 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 + ) + + +@pytest.mark.parametrize('ctrl', range(2)) +@pytest.mark.parametrize('dtype', [QInt, QUInt, QMontgomeryUInt]) +@pytest.mark.parametrize('bitsize', range(2, 5)) +def test_clineardepthgreaterthan_decomposition(ctrl, dtype, bitsize): + b = CLinearDepthGreaterThan(dtype(bitsize), ctrl) + qlt_testing.assert_valid_bloq_decomposition(b) + + +@pytest.mark.parametrize('ctrl', range(2)) +@pytest.mark.parametrize('dtype', [QInt, QUInt, QMontgomeryUInt]) +@pytest.mark.parametrize('bitsize', range(2, 5)) +def test_clineardepthgreaterthan_bloq_counts(ctrl, dtype, bitsize): + b = CLinearDepthGreaterThan(dtype(bitsize), ctrl) + qlt_testing.assert_equivalent_bloq_counts(b, [ignore_alloc_free, ignore_split_join]) + + +@pytest.mark.parametrize('ctrl', range(2)) +@pytest.mark.parametrize('dtype', [QInt, QUInt, QMontgomeryUInt]) +def test_clineardepthgreaterthan_tcomplexity(ctrl, dtype): + n = sympy.Symbol('n') + c = CLinearDepthGreaterThan(dtype(n), ctrl).t_complexity() + assert c.t == 4 * (n + 2) + assert c.rotations == 0 diff --git a/qualtran/bloqs/basic_gates/on_each.py b/qualtran/bloqs/basic_gates/on_each.py index 68fa62532..f2ccdc694 100644 --- a/qualtran/bloqs/basic_gates/on_each.py +++ b/qualtran/bloqs/basic_gates/on_each.py @@ -24,6 +24,7 @@ BloqBuilder, DecomposeTypeError, QAny, + QDType, Register, Signature, Soquet, @@ -49,15 +50,23 @@ class OnEach(Bloq): n: SymbolicInt gate: Bloq + target_dtype: Optional[QDType] = None def __attrs_post_init__(self): assert len(self.gate.signature) == 1, "Gate must only have a single register." assert self.gate.signature[0].bitsize == 1, "Must be single qubit gate." assert self.gate.signature[0].name == 'q', "Register must be named q." + assert ( + self.target_dtype is None + or not hasattr(self.target_dtype, 'bitsize') + or self.target_dtype.bitsize == self.n + ) @cached_property def signature(self) -> Signature: - reg = Register('q', QAny(bitsize=self.n)) + reg = Register( + 'q', QAny(bitsize=self.n) if self.target_dtype is None else self.target_dtype + ) return Signature([reg]) def build_composite_bloq(self, bb: BloqBuilder, *, q: Soquet) -> Dict[str, SoquetT]: @@ -66,7 +75,7 @@ def build_composite_bloq(self, bb: BloqBuilder, *, q: Soquet) -> Dict[str, Soque qs = bb.split(q) for i in range(self.n): qs[i] = bb.add(self.gate, q=qs[i]) - return {'q': bb.join(qs)} + return {'q': bb.join(qs, self.target_dtype)} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: return {(self.gate, self.n)} diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 4f3782808..a59d7b4f1 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -171,6 +171,7 @@ "qualtran.bloqs.arithmetic.comparison.LessThanEqual": qualtran.bloqs.arithmetic.comparison.LessThanEqual, "qualtran.bloqs.arithmetic.comparison.LinearDepthGreaterThan": qualtran.bloqs.arithmetic.comparison.LinearDepthGreaterThan, "qualtran.bloqs.arithmetic.comparison.SingleQubitCompare": qualtran.bloqs.arithmetic.comparison.SingleQubitCompare, + "qualtran.bloqs.arithmetic.comparison.CLinearDepthGreaterThan": qualtran.bloqs.arithmetic.comparison.CLinearDepthGreaterThan, "qualtran.bloqs.arithmetic.controlled_add_or_subtract.ControlledAddOrSubtract": qualtran.bloqs.arithmetic.controlled_add_or_subtract.ControlledAddOrSubtract, "qualtran.bloqs.arithmetic.conversions.contiguous_index.ToContiguousIndex": qualtran.bloqs.arithmetic.conversions.contiguous_index.ToContiguousIndex, "qualtran.bloqs.arithmetic.conversions.ones_complement_to_twos_complement.SignedIntegerToTwosComplement": qualtran.bloqs.arithmetic.conversions.ones_complement_to_twos_complement.SignedIntegerToTwosComplement,