From d4769b6d09f99e4a5b048e05118452ae7f11ad5b Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Fri, 7 Jun 2024 13:03:58 -0700 Subject: [PATCH 01/17] Optimize gate count of Subtract to n-1 Toffolis --- qualtran/bloqs/arithmetic/subtraction.py | 62 +++++++++++-------- qualtran/bloqs/arithmetic/subtraction_test.py | 26 +++++--- 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index 4884150d8..bd23848e3 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -23,6 +23,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + QAny, QInt, QMontgomeryUInt, QUInt, @@ -31,8 +32,9 @@ Soquet, SoquetT, ) -from qualtran.bloqs.arithmetic.addition import Add, AddK -from qualtran.bloqs.basic_gates import XGate +from qualtran.bloqs.arithmetic.addition import Add +from qualtran.bloqs.basic_gates import OnEach, XGate +from qualtran.bloqs.bookkeeping import Allocate, Free from qualtran.drawing import Text if TYPE_CHECKING: @@ -61,7 +63,9 @@ class Subtract(Bloq): b: A b_dtype.bitsize-sized input/output register (register b above). """ - a_dtype: Union[QInt, QUInt, QMontgomeryUInt] = field() + a_dtype: Union[QInt, QUInt, QMontgomeryUInt] = field( + converter=lambda k: QUInt(k) if isinstance(k, int) else k + ) b_dtype: Union[QInt, QUInt, QMontgomeryUInt] = field() @b_dtype.default @@ -118,32 +122,38 @@ def wire_symbol( raise ValueError() def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: - a_dtype = ( - self.a_dtype if not isinstance(self.a_dtype, QInt) else QUInt(self.a_dtype.bitsize) - ) - b_dtype = ( - self.b_dtype if not isinstance(self.b_dtype, QInt) else QUInt(self.b_dtype.bitsize) - ) + delta = self.b_dtype.bitsize - self.a_dtype.bitsize return { - (XGate(), self.b_dtype.bitsize), - (AddK(self.b_dtype.bitsize, k=1), 1), - (Add(a_dtype, b_dtype), 1), - } - - def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[str, 'SoquetT']: - b = np.array([bb.add(XGate(), q=q) for q in bb.split(b)]) # 1s complement of b. - b = bb.add( - AddK(self.b_dtype.bitsize, k=1), x=bb.join(b, self.b_dtype) - ) # 2s complement of b. - - a_dtype = ( - self.a_dtype if not isinstance(self.a_dtype, QInt) else QUInt(self.a_dtype.bitsize) - ) - b_dtype = ( - self.b_dtype if not isinstance(self.b_dtype, QInt) else QUInt(self.b_dtype.bitsize) + (OnEach(self.b_dtype.bitsize, XGate()), 3), + (Add(QUInt(self.b_dtype.bitsize), QUInt(self.b_dtype.bitsize)), 1), + }.union( + [ + (Allocate(QAny(self.b_dtype.bitsize - self.a_dtype.bitsize)), 1), + (Free(QAny(self.b_dtype.bitsize - self.a_dtype.bitsize)), 1), + ] + if delta + else [] ) - a, b = bb.add(Add(a_dtype, b_dtype), a=a, b=b) # a - b + def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[str, 'SoquetT']: + delta = self.b_dtype.bitsize - self.a_dtype.bitsize + n_bits = self.b_dtype.bitsize + a = bb.split(a) + b = bb.split(b) + if delta: + # Add a zero prefix to `a` + a = np.concatenate([bb.split(bb.allocate(delta)), a]) + a = bb.join(a, QUInt(n_bits)) + b = bb.join(b, QUInt(n_bits)) + a = bb.add(OnEach(n_bits, XGate()), q=a) + a, b = bb.add(Add(QUInt(n_bits), QUInt(n_bits)), a=a, b=b) # a - b + b = bb.add(OnEach(n_bits, XGate()), q=b) + a = bb.add(OnEach(n_bits, XGate()), q=a) + b = bb.join(bb.split(b), self.b_dtype) + a = bb.split(a) + if delta: + bb.free(bb.join(a[:delta])) + a = bb.join(a[delta:], self.a_dtype) return {'a': a, 'b': b} diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index d878d77f8..c396fbd50 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -21,15 +21,20 @@ from qualtran.resource_counting.generalizers import ignore_split_join -def test_subtract_bloq_decomposition(): - gate = Subtract(QInt(3), QInt(5)) +@pytest.mark.parametrize( + ['a_bits', 'b_bits'], [(a, b) for a in range(1, 6) for b in range(a, 6) if a + b <= 10] +) +def test_subtract_bloq_decomposition(a_bits, b_bits): + gate = Subtract(QInt(a_bits), QInt(b_bits)) qlt_testing.assert_valid_bloq_decomposition(gate) - want = np.zeros((256, 256)) - for a_b in range(256): - a, b = a_b >> 5, a_b & 31 - c = (a - b) % 32 - want[(a << 5) | c][a_b] = 1 + tot = 1 << (a_bits + b_bits) + want = np.zeros((tot, tot)) + max_b = 1 << b_bits + for a_b in range(tot): + a, b = a_b >> b_bits, a_b & (max_b - 1) + c = (a - b) % max_b + want[(a << b_bits) | c][a_b] = 1 got = gate.tensor_contract() np.testing.assert_equal(got, want) @@ -45,3 +50,10 @@ def test_subtract_bloq_consitant_counts(): qlt_testing.assert_equivalent_bloq_counts( Subtract(QInt(3), QInt(4)), generalizer=ignore_split_join ) + + +@pytest.mark.parametrize('n_bits', range(1, 10)) +def test_t_complexity(n_bits): + complexity = Subtract(n_bits).t_complexity() + assert complexity.t == 4 * (n_bits - 1) + assert complexity.rotations == 0 From 3df59317f71b8cff47cf53878748be15a4dd355a Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Wed, 26 Jun 2024 16:29:46 -0700 Subject: [PATCH 02/17] tmp --- qualtran/bloqs/arithmetic/addition.py | 7 ++- qualtran/bloqs/arithmetic/subtraction.py | 47 ++++++++++--------- qualtran/bloqs/arithmetic/subtraction_test.py | 47 +++++++++++++++++-- 3 files changed, 73 insertions(+), 28 deletions(-) diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index b707b708f..89b94ce28 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -157,8 +157,11 @@ def on_classical_vals( ) -> Dict[str, 'ClassicalValT']: unsigned = isinstance(self.a_dtype, (QUInt, QMontgomeryUInt)) b_bitsize = self.b_dtype.bitsize - N = 2**b_bitsize if unsigned else 2 ** (b_bitsize - 1) - return {'a': a, 'b': int(math.fmod(a + b, N))} + N = 2**b_bitsize + if unsigned: + return {'a': a, 'b': int((a+b)%N)} + hN = N >> 1 + return {'a': a, 'b': (a+b+hN)%N + hN} def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: wire_symbols = ["In(x)"] * int(self.a_dtype.bitsize) diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index bd23848e3..097649186 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -33,8 +33,8 @@ SoquetT, ) from qualtran.bloqs.arithmetic.addition import Add -from qualtran.bloqs.basic_gates import OnEach, XGate -from qualtran.bloqs.bookkeeping import Allocate, Free +from qualtran.bloqs.basic_gates import OnEach, XGate, CNOT +from qualtran.bloqs.bookkeeping import Allocate, Free, Cast from qualtran.drawing import Text if TYPE_CHECKING: @@ -47,10 +47,9 @@ class Subtract(Bloq): r"""An n-bit subtraction gate. - Implements $U|a\rangle|b\rangle \rightarrow |a\rangle|a-b\rangle$ using $4n - 4 T$ gates. + Implements $U|a\rangle|b\rangle \rightarrow |a\rangle|a-b\rangle$ using $4n - 4$ T gates. - This construction uses `XGate` and `AddK` to compute the twos-compliment of `b` before - doing a standard `Add`. + This construction uses the relation `a - b = ~(~a + b)` to turn the operation into addition. Args: a_dtype: Quantum datatype used to represent the integer a. @@ -63,9 +62,7 @@ class Subtract(Bloq): b: A b_dtype.bitsize-sized input/output register (register b above). """ - a_dtype: Union[QInt, QUInt, QMontgomeryUInt] = field( - converter=lambda k: QUInt(k) if isinstance(k, int) else k - ) + a_dtype: Union[QInt, QUInt, QMontgomeryUInt] = field() b_dtype: Union[QInt, QUInt, QMontgomeryUInt] = field() @b_dtype.default @@ -104,8 +101,11 @@ def on_classical_vals( ) -> Dict[str, 'ClassicalValT']: unsigned = isinstance(self.a_dtype, (QUInt, QMontgomeryUInt)) b_bitsize = self.b_dtype.bitsize - N = 2**b_bitsize if unsigned else 2 ** (b_bitsize - 1) - return {'a': a, 'b': int(math.fmod(a - b, N))} + N = 2**b_bitsize + if unsigned: + return {'a': a, 'b': int((a - b)% N)} + hN = N >> 1 + return {'a': a, 'b': int((a - b + hN)%N) - hN} def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() @@ -126,13 +126,10 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: return { (OnEach(self.b_dtype.bitsize, XGate()), 3), (Add(QUInt(self.b_dtype.bitsize), QUInt(self.b_dtype.bitsize)), 1), + (Allocate(QAny(delta)), 1), + (Free(QAny(delta)), 1), }.union( - [ - (Allocate(QAny(self.b_dtype.bitsize - self.a_dtype.bitsize)), 1), - (Free(QAny(self.b_dtype.bitsize - self.a_dtype.bitsize)), 1), - ] - if delta - else [] + [(CNOT(), 2*delta)] if isinstance(self.a_dtype, QInt) else [] ) def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[str, 'SoquetT']: @@ -140,19 +137,23 @@ def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[ n_bits = self.b_dtype.bitsize a = bb.split(a) b = bb.split(b) - if delta: - # Add a zero prefix to `a` - a = np.concatenate([bb.split(bb.allocate(delta)), a]) + prefix = bb.split(bb.allocate(delta)) + if isinstance(self.a_dtype, QInt): + for i in range(delta): + a[0], prefix[i] = bb.add(CNOT(), ctrl=a[0], target=prefix[i]) + a = np.concatenate([prefix, a]) a = bb.join(a, QUInt(n_bits)) b = bb.join(b, QUInt(n_bits)) a = bb.add(OnEach(n_bits, XGate()), q=a) - a, b = bb.add(Add(QUInt(n_bits), QUInt(n_bits)), a=a, b=b) # a - b + a, b = bb.add(Add(QUInt(n_bits)), a=a, b=b) b = bb.add(OnEach(n_bits, XGate()), q=b) a = bb.add(OnEach(n_bits, XGate()), q=a) - b = bb.join(bb.split(b), self.b_dtype) + b = bb.add(Cast(QUInt(n_bits), QInt(n_bits)), reg=b) a = bb.split(a) - if delta: - bb.free(bb.join(a[:delta])) + if isinstance(self.a_dtype, QInt): + for i in range(delta): + a[delta], a[i] = bb.add(CNOT(), ctrl=a[delta], target=a[i]) + bb.free(bb.join(a[:delta])) a = bb.join(a[delta:], self.a_dtype) return {'a': a, 'b': b} diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index c396fbd50..5112f67d2 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import itertools import numpy as np import pytest @@ -24,8 +25,8 @@ @pytest.mark.parametrize( ['a_bits', 'b_bits'], [(a, b) for a in range(1, 6) for b in range(a, 6) if a + b <= 10] ) -def test_subtract_bloq_decomposition(a_bits, b_bits): - gate = Subtract(QInt(a_bits), QInt(b_bits)) +def test_subtract_bloq_decomposition_unsigned(a_bits, b_bits): + gate = Subtract(QUInt(a_bits), QUInt(b_bits)) qlt_testing.assert_valid_bloq_decomposition(gate) tot = 1 << (a_bits + b_bits) @@ -39,6 +40,30 @@ def test_subtract_bloq_decomposition(a_bits, b_bits): np.testing.assert_equal(got, want) +def _to_signed_binary(x: int, bits: int): + if x >= 0: + return x + return (~(-x) + 1)%(2 << bits) + +@pytest.mark.parametrize( + ['a_bits', 'b_bits'], [(a, b) for a in range(1, 6) for b in range(a, 6) if a + b <= 10] +) +def test_subtract_bloq_decomposition_signed(a_bits, b_bits): + gate = Subtract(QInt(a_bits + 1), QInt(b_bits + 1)) + qlt_testing.assert_valid_bloq_decomposition(gate) + + tot = 1 << (a_bits + b_bits + 2) + want = np.zeros((tot, tot)) + for a in range(-(1 << a_bits), (1 << a_bits)): + for b in range(-(1 << b_bits), (1 << b_bits)): + a_binary = _to_signed_binary(a, a_bits) + b_binary = _to_signed_binary(b, b_bits) + c_binary = _to_signed_binary(a - b, b_bits) + want[(a_binary << b_bits << 1) | c_binary, (a_binary << b_bits << 1) | b_binary] = 1 + got = gate.tensor_contract() + np.testing.assert_equal(got, want) + + def test_subtract_bloq_validation(): assert Subtract(QUInt(3)) == Subtract(QUInt(3), QUInt(3)) with pytest.raises(ValueError, match='bitsize must be less'): @@ -54,6 +79,22 @@ def test_subtract_bloq_consitant_counts(): @pytest.mark.parametrize('n_bits', range(1, 10)) def test_t_complexity(n_bits): - complexity = Subtract(n_bits).t_complexity() + complexity = Subtract(QUInt(n_bits)).t_complexity() assert complexity.t == 4 * (n_bits - 1) assert complexity.rotations == 0 + + +@pytest.mark.parametrize('dtype', [QUInt, QInt]) +def test_against_classical_values(dtype): + subtract = Subtract(dtype(5), dtype(5)) + cbloq = subtract.decompose_bloq() + if isinstance(dtype, QInt): + R = range(-16, 16) + else: + R = range(32) + for (a, b) in itertools.product(R, R): + print(f'{a=} {b=}') + ref = subtract.call_classically(a=a, b=b) + comp = cbloq.call_classically(a=a, b=b) + print(f'{ref=} {comp=}') + assert ref == comp From 25b2b3611ce7278b18a2c22a951645e8f730432b Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Wed, 26 Jun 2024 16:42:55 -0700 Subject: [PATCH 03/17] pin --- qualtran/bloqs/arithmetic/subtraction.py | 3 ++- qualtran/bloqs/arithmetic/subtraction_test.py | 6 ++++-- qualtran/bloqs/bookkeeping/cast.py | 3 +-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index 097649186..e59024501 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -154,7 +154,8 @@ def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[ for i in range(delta): a[delta], a[i] = bb.add(CNOT(), ctrl=a[delta], target=a[i]) bb.free(bb.join(a[:delta])) - a = bb.join(a[delta:], self.a_dtype) + a = bb.join(a[delta:], QUInt(self.a_dtype.bitsize)) + a = bb.add(Cast(QUInt(self.a_dtype.bitsize), self.a_dtype), reg=a) return {'a': a, 'b': b} diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index 5112f67d2..b32c9952c 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -84,17 +84,19 @@ def test_t_complexity(n_bits): assert complexity.rotations == 0 -@pytest.mark.parametrize('dtype', [QUInt, QInt]) +@pytest.mark.parametrize('dtype', [QInt]) def test_against_classical_values(dtype): subtract = Subtract(dtype(5), dtype(5)) cbloq = subtract.decompose_bloq() - if isinstance(dtype, QInt): + if dtype is QInt: R = range(-16, 16) else: R = range(32) + print(R) for (a, b) in itertools.product(R, R): print(f'{a=} {b=}') ref = subtract.call_classically(a=a, b=b) + print(f'{ref=}') comp = cbloq.call_classically(a=a, b=b) print(f'{ref=} {comp=}') assert ref == comp diff --git a/qualtran/bloqs/bookkeeping/cast.py b/qualtran/bloqs/bookkeeping/cast.py index 92c5fe62f..d4731b886 100644 --- a/qualtran/bloqs/bookkeeping/cast.py +++ b/qualtran/bloqs/bookkeeping/cast.py @@ -100,8 +100,7 @@ def add_my_tensors( ) def on_classical_vals(self, reg: int) -> Dict[str, 'ClassicalValT']: - # TODO: Actually cast the values https://github.com/quantumlib/Qualtran/issues/734 - return {'reg': reg} + return {'reg': self.out_dtype.from_bits(self.inp_dtype.to_bits(reg))} def as_cirq_op(self, qubit_manager, reg: 'CirqQuregT') -> Tuple[None, Dict[str, 'CirqQuregT']]: return None, {'reg': reg} From 0e41f59140f11e70b4fea422fad3f89f2bee2aca Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Wed, 26 Jun 2024 17:16:27 -0700 Subject: [PATCH 04/17] working state --- qualtran/bloqs/arithmetic/subtraction_test.py | 14 ++++++-------- qualtran/simulation/classical_sim.py | 15 +++++++++++---- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index b32c9952c..1054958be 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -86,17 +86,15 @@ def test_t_complexity(n_bits): @pytest.mark.parametrize('dtype', [QInt]) def test_against_classical_values(dtype): - subtract = Subtract(dtype(5), dtype(5)) + subtract = Subtract(dtype(3), dtype(5)) cbloq = subtract.decompose_bloq() if dtype is QInt: - R = range(-16, 16) + R1 = range(-4, 4) + R2 = range(-16, 16) else: - R = range(32) - print(R) - for (a, b) in itertools.product(R, R): - print(f'{a=} {b=}') + R1 = range(8) + R2 = range(32) + for (a, b) in itertools.product(R1, R2): ref = subtract.call_classically(a=a, b=b) - print(f'{ref=}') comp = cbloq.call_classically(a=a, b=b) - print(f'{ref=} {comp=}') assert ref == comp diff --git a/qualtran/simulation/classical_sim.py b/qualtran/simulation/classical_sim.py index e2b5014ff..fa4618ee9 100644 --- a/qualtran/simulation/classical_sim.py +++ b/qualtran/simulation/classical_sim.py @@ -62,14 +62,21 @@ def ints_to_bits( w: The bit width of the returned bitstrings. """ x = np.atleast_1d(x) + signed = [False]*len(x) if not np.issubdtype(x.dtype, np.uint): - assert np.all(x >= 0) assert np.iinfo(x.dtype).bits <= 64 - x = x.astype(np.uint64) + signed = [v < 0 for v in x] + x = np.abs(x) assert w <= np.iinfo(x.dtype).bits mask = 2 ** np.arange(w - 1, 0 - 1, -1, dtype=x.dtype).reshape((w, 1)) - return (x & mask).astype(bool).astype(np.uint8).T - + bits = (x & mask).astype(bool).astype(np.uint8).T + for j in range(len(signed)): + if not signed[j]: continue + bits[j] = 1 - bits[j] + for i in range(w-1, -1, -1): + bits[j, i] ^= 1 + if bits[j, i] == 1: break + return bits def _get_in_vals( binst: Union[DanglingT, BloqInstance], reg: Register, soq_assign: Dict[Soquet, ClassicalValT] From 6b0058a6d6875062861ec3118ca11637988aef57 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Wed, 26 Jun 2024 17:17:24 -0700 Subject: [PATCH 05/17] lint --- qualtran/bloqs/arithmetic/addition.py | 4 ++-- qualtran/bloqs/arithmetic/subtraction.py | 14 ++++++-------- qualtran/bloqs/arithmetic/subtraction_test.py | 6 ++++-- qualtran/simulation/classical_sim.py | 11 +++++++---- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 89b94ce28..1649a1451 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -159,9 +159,9 @@ def on_classical_vals( b_bitsize = self.b_dtype.bitsize N = 2**b_bitsize if unsigned: - return {'a': a, 'b': int((a+b)%N)} + return {'a': a, 'b': int((a + b) % N)} hN = N >> 1 - return {'a': a, 'b': (a+b+hN)%N + hN} + return {'a': a, 'b': (a + b + hN) % N + hN} def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: wire_symbols = ["In(x)"] * int(self.a_dtype.bitsize) diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index e59024501..907081afa 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.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. -import math + from typing import Dict, Optional, Set, Tuple, TYPE_CHECKING, Union import numpy as np @@ -33,8 +33,8 @@ SoquetT, ) from qualtran.bloqs.arithmetic.addition import Add -from qualtran.bloqs.basic_gates import OnEach, XGate, CNOT -from qualtran.bloqs.bookkeeping import Allocate, Free, Cast +from qualtran.bloqs.basic_gates import CNOT, OnEach, XGate +from qualtran.bloqs.bookkeeping import Allocate, Cast, Free from qualtran.drawing import Text if TYPE_CHECKING: @@ -103,9 +103,9 @@ def on_classical_vals( b_bitsize = self.b_dtype.bitsize N = 2**b_bitsize if unsigned: - return {'a': a, 'b': int((a - b)% N)} + return {'a': a, 'b': int((a - b) % N)} hN = N >> 1 - return {'a': a, 'b': int((a - b + hN)%N) - hN} + return {'a': a, 'b': int((a - b + hN) % N) - hN} def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() @@ -128,9 +128,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: (Add(QUInt(self.b_dtype.bitsize), QUInt(self.b_dtype.bitsize)), 1), (Allocate(QAny(delta)), 1), (Free(QAny(delta)), 1), - }.union( - [(CNOT(), 2*delta)] if isinstance(self.a_dtype, QInt) else [] - ) + }.union([(CNOT(), 2 * delta)] if isinstance(self.a_dtype, QInt) else []) def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[str, 'SoquetT']: delta = self.b_dtype.bitsize - self.a_dtype.bitsize diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index 1054958be..26fc31ef2 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -13,6 +13,7 @@ # limitations under the License. import itertools + import numpy as np import pytest @@ -41,9 +42,10 @@ def test_subtract_bloq_decomposition_unsigned(a_bits, b_bits): def _to_signed_binary(x: int, bits: int): - if x >= 0: + if x >= 0: return x - return (~(-x) + 1)%(2 << bits) + return (~(-x) + 1) % (2 << bits) + @pytest.mark.parametrize( ['a_bits', 'b_bits'], [(a, b) for a in range(1, 6) for b in range(a, 6) if a + b <= 10] diff --git a/qualtran/simulation/classical_sim.py b/qualtran/simulation/classical_sim.py index fa4618ee9..1c625b36c 100644 --- a/qualtran/simulation/classical_sim.py +++ b/qualtran/simulation/classical_sim.py @@ -62,7 +62,7 @@ def ints_to_bits( w: The bit width of the returned bitstrings. """ x = np.atleast_1d(x) - signed = [False]*len(x) + signed = [False] * len(x) if not np.issubdtype(x.dtype, np.uint): assert np.iinfo(x.dtype).bits <= 64 signed = [v < 0 for v in x] @@ -71,13 +71,16 @@ def ints_to_bits( mask = 2 ** np.arange(w - 1, 0 - 1, -1, dtype=x.dtype).reshape((w, 1)) bits = (x & mask).astype(bool).astype(np.uint8).T for j in range(len(signed)): - if not signed[j]: continue + if not signed[j]: + continue bits[j] = 1 - bits[j] - for i in range(w-1, -1, -1): + for i in range(w - 1, -1, -1): bits[j, i] ^= 1 - if bits[j, i] == 1: break + if bits[j, i] == 1: + break return bits + def _get_in_vals( binst: Union[DanglingT, BloqInstance], reg: Register, soq_assign: Dict[Soquet, ClassicalValT] ) -> ClassicalValT: From 29ba9d75cbdaf03d9e47f98e5fb7983828ebd131 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Wed, 26 Jun 2024 17:20:41 -0700 Subject: [PATCH 06/17] nit --- qualtran/bloqs/arithmetic/addition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 9fcabd342..1d62c5c51 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -159,7 +159,7 @@ def on_classical_vals( if unsigned: return {'a': a, 'b': int((a + b) % N)} hN = N >> 1 - return {'a': a, 'b': (a + b + hN) % N + hN} + return {'a': a, 'b': (a + b + hN) % N - hN} def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: wire_symbols = ["In(x)"] * int(self.a_dtype.bitsize) From c50f7fd6e0784da28ccce7d166602951958032f9 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Thu, 27 Jun 2024 13:17:51 -0700 Subject: [PATCH 07/17] pin point --- qualtran/bloqs/bookkeeping/cast.py | 4 ++++ qualtran/simulation/classical_sim_test.py | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/qualtran/bloqs/bookkeeping/cast.py b/qualtran/bloqs/bookkeeping/cast.py index d4731b886..322529d3b 100644 --- a/qualtran/bloqs/bookkeeping/cast.py +++ b/qualtran/bloqs/bookkeeping/cast.py @@ -29,6 +29,7 @@ Side, Signature, SoquetT, + QFxp, ) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq @@ -100,6 +101,9 @@ def add_my_tensors( ) def on_classical_vals(self, reg: int) -> Dict[str, 'ClassicalValT']: + if isinstance(self.inp_dtype, QFxp) or isinstance(self.out_dtype, QFxp): + # TODO: Actually cast the values https://github.com/quantumlib/Qualtran/issues/734 + return {'reg': reg} return {'reg': self.out_dtype.from_bits(self.inp_dtype.to_bits(reg))} def as_cirq_op(self, qubit_manager, reg: 'CirqQuregT') -> Tuple[None, Dict[str, 'CirqQuregT']]: diff --git a/qualtran/simulation/classical_sim_test.py b/qualtran/simulation/classical_sim_test.py index 8a018335c..1800022a5 100644 --- a/qualtran/simulation/classical_sim_test.py +++ b/qualtran/simulation/classical_sim_test.py @@ -62,11 +62,6 @@ def test_int_to_bits(): (bitstring,) = ints_to_bits(2, w=8) assert bitstring.tolist() == [0, 0, 0, 0, 0, 0, 1, 0] - # check bounds - with pytest.raises(AssertionError): - ints_to_bits([4, -2], w=8) - - def test_dtype_validation(): # set up mocks for `_update_assign_from_vals` soq_assign: Dict[Soquet, ClassicalValT] = {} # gets assigned to; we discard in this test. From 9d8e0bdecc32add027aacd62e50c49b0b4eae549 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Thu, 27 Jun 2024 14:34:49 -0700 Subject: [PATCH 08/17] fixes --- qualtran/bloqs/arithmetic/addition.py | 2 +- qualtran/bloqs/bookkeeping/cast.py | 1 - qualtran/simulation/classical_sim.py | 18 +----------------- qualtran/simulation/classical_sim_test.py | 8 ++++++++ 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 1d62c5c51..f90752959 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -159,7 +159,7 @@ def on_classical_vals( if unsigned: return {'a': a, 'b': int((a + b) % N)} hN = N >> 1 - return {'a': a, 'b': (a + b + hN) % N - hN} + return {'a': a, 'b': int(a + b + hN) % N - hN} def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: wire_symbols = ["In(x)"] * int(self.a_dtype.bitsize) diff --git a/qualtran/bloqs/bookkeeping/cast.py b/qualtran/bloqs/bookkeeping/cast.py index 39933677d..f7a4f3409 100644 --- a/qualtran/bloqs/bookkeeping/cast.py +++ b/qualtran/bloqs/bookkeeping/cast.py @@ -30,7 +30,6 @@ Side, Signature, SoquetT, - QFxp, ) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq diff --git a/qualtran/simulation/classical_sim.py b/qualtran/simulation/classical_sim.py index 1c625b36c..1e8b10a2c 100644 --- a/qualtran/simulation/classical_sim.py +++ b/qualtran/simulation/classical_sim.py @@ -62,23 +62,7 @@ def ints_to_bits( w: The bit width of the returned bitstrings. """ x = np.atleast_1d(x) - signed = [False] * len(x) - if not np.issubdtype(x.dtype, np.uint): - assert np.iinfo(x.dtype).bits <= 64 - signed = [v < 0 for v in x] - x = np.abs(x) - assert w <= np.iinfo(x.dtype).bits - mask = 2 ** np.arange(w - 1, 0 - 1, -1, dtype=x.dtype).reshape((w, 1)) - bits = (x & mask).astype(bool).astype(np.uint8).T - for j in range(len(signed)): - if not signed[j]: - continue - bits[j] = 1 - bits[j] - for i in range(w - 1, -1, -1): - bits[j, i] ^= 1 - if bits[j, i] == 1: - break - return bits + return np.array([list(map(int, np.binary_repr(v, width=w))) for v in x], dtype=np.uint8) def _get_in_vals( diff --git a/qualtran/simulation/classical_sim_test.py b/qualtran/simulation/classical_sim_test.py index 1800022a5..59bd70e6f 100644 --- a/qualtran/simulation/classical_sim_test.py +++ b/qualtran/simulation/classical_sim_test.py @@ -54,6 +54,10 @@ def test_int_to_bits(): bitstrings = ints_to_bits(nums, w=23) assert bitstrings.shape == (100, 23) + nums = rs.randint(-(2**22), 2**22, size=(100,), dtype=np.int64) + bitstrings = ints_to_bits(nums, w=23) + assert bitstrings.shape == (100, 23) + for num, bs in zip(nums, bitstrings): ref_bs = cirq.big_endian_int_to_bits(int(num), bit_count=23) np.testing.assert_array_equal(ref_bs, bs) @@ -62,6 +66,10 @@ def test_int_to_bits(): (bitstring,) = ints_to_bits(2, w=8) assert bitstring.tolist() == [0, 0, 0, 0, 0, 0, 1, 0] + bitstring = ints_to_bits([31, -1], w=6) + assert bitstring.tolist() == [[0, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1]] + + def test_dtype_validation(): # set up mocks for `_update_assign_from_vals` soq_assign: Dict[Soquet, ClassicalValT] = {} # gets assigned to; we discard in this test. From 9f62138b4af4dcacd0c9abc7c1d651067306bb11 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Mon, 8 Jul 2024 13:49:16 -0700 Subject: [PATCH 09/17] address comments --- qualtran/bloqs/arithmetic/addition.py | 12 +++- qualtran/bloqs/arithmetic/addition_test.py | 7 +++ qualtran/bloqs/arithmetic/subtraction.py | 55 ++++++++++++------- qualtran/bloqs/arithmetic/subtraction_test.py | 11 +++- 4 files changed, 63 insertions(+), 22 deletions(-) diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index f90752959..ba4fa3cb1 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -158,8 +158,16 @@ def on_classical_vals( N = 2**b_bitsize if unsigned: return {'a': a, 'b': int((a + b) % N)} - hN = N >> 1 - return {'a': a, 'b': int(a + b + hN) % N - hN} + + # Addition of signed integers can result in overflow. In most classical programming languages (e.g. C++) + # what happens when an overflow happens is left as an implementation detail for compiler designers. + # However for quantum subtraction the operation shoudl be unitary and that means that the unitary of + # the bloq should be a permutation matrix. + # If we hold `a` constant then the valid range of values of `b` [-N/2, N/2) gets shifted forward or backwards + # by `a`. to keep the operation unitary overflowing values wrap around. this is the same as moving the range [0, N) + # by the same amount modulu $N$. that is add N/2 before addition and then remove it. + half_n = N >> 1 + return {'a': a, 'b': int(a + b + half_n) % N - half_n} def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: wire_symbols = ["In(x)"] * int(self.a_dtype.bitsize) diff --git a/qualtran/bloqs/arithmetic/addition_test.py b/qualtran/bloqs/arithmetic/addition_test.py index 039008cfb..16979ea33 100644 --- a/qualtran/bloqs/arithmetic/addition_test.py +++ b/qualtran/bloqs/arithmetic/addition_test.py @@ -316,6 +316,13 @@ def test_classical_add_k_unsigned(bitsize, k, x, cvs, ctrls, result): assert bloq_classical[-1] == result +@pytest.mark.parametrize('bitsize', range(2, 5)) +def test_classical_add_signed_overflow(bitsize): + bloq = Add(QInt(bitsize)) + mx = 2 ** (bitsize - 1) - 1 + assert bloq.call_classically(a=mx, b=mx) == (mx, -2) + + # TODO: write tests for signed integer addition (subtraction) # https://github.com/quantumlib/Qualtran/issues/606 @pytest.mark.parametrize('bitsize,k,x,cvs,ctrls,result', [(5, 2, 0, (1, 0), (1, 0), 2)]) diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index 907081afa..d406867eb 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -49,7 +49,10 @@ class Subtract(Bloq): Implements $U|a\rangle|b\rangle \rightarrow |a\rangle|a-b\rangle$ using $4n - 4$ T gates. - This construction uses the relation `a - b = ~(~a + b)` to turn the operation into addition. + This construction uses the relation `a - b = ~(~a + b)` to turn the operation into addition. This relation is used in + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/pdf/2007.07391) + to turn addition into subtraction conditioned on a qubit. + Args: a_dtype: Quantum datatype used to represent the integer a. @@ -60,6 +63,9 @@ class Subtract(Bloq): Registers: a: A a_dtype.bitsize-sized input register (register a above). b: A b_dtype.bitsize-sized input/output register (register b above). + + References: + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization, page 9](https://arxiv.org/pdf/2007.07391) """ a_dtype: Union[QInt, QUInt, QMontgomeryUInt] = field() @@ -104,8 +110,15 @@ def on_classical_vals( N = 2**b_bitsize if unsigned: return {'a': a, 'b': int((a - b) % N)} - hN = N >> 1 - return {'a': a, 'b': int((a - b + hN) % N) - hN} + # Subtraction of signed integers can result in overflow. In most classical programming languages (e.g. C++) + # what happens when an overflow happens is left as an implementation detail for compiler designers. + # However for quantum subtraction the operation shoudl be unitary and that means that the unitary of + # the bloq should be a permutation matrix. + # If we hold `a` constant then the valid range of values of `b` [-N/2, N/2) gets shifted forward or backwards + # by `a`. to keep the operation unitary overflowing values wrap around. this is the same as moving the range [0, N) + # by the same amount modulu $N$. that is add N/2 before subtraction and then remove it. + half_n = N >> 1 + return {'a': a, 'b': int((a - b + half_n) % N) - half_n} def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() @@ -123,35 +136,39 @@ def wire_symbol( def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: delta = self.b_dtype.bitsize - self.a_dtype.bitsize - return { - (OnEach(self.b_dtype.bitsize, XGate()), 3), - (Add(QUInt(self.b_dtype.bitsize), QUInt(self.b_dtype.bitsize)), 1), - (Allocate(QAny(delta)), 1), - (Free(QAny(delta)), 1), - }.union([(CNOT(), 2 * delta)] if isinstance(self.a_dtype, QInt) else []) + return ( + { + (OnEach(self.b_dtype.bitsize, XGate()), 3), + (Add(QUInt(self.b_dtype.bitsize), QUInt(self.b_dtype.bitsize)), 1), + } + .union([(CNOT(), 2 * delta)] if isinstance(self.a_dtype, QInt) else []) + .union([(Allocate(QAny(delta)), 1), (Free(QAny(delta)), 1)] if delta else []) + ) def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[str, 'SoquetT']: delta = self.b_dtype.bitsize - self.a_dtype.bitsize n_bits = self.b_dtype.bitsize a = bb.split(a) b = bb.split(b) - prefix = bb.split(bb.allocate(delta)) - if isinstance(self.a_dtype, QInt): - for i in range(delta): - a[0], prefix[i] = bb.add(CNOT(), ctrl=a[0], target=prefix[i]) - a = np.concatenate([prefix, a]) + if delta: + prefix = bb.split(bb.allocate(delta)) + if isinstance(self.a_dtype, QInt): + for i in range(delta): + a[0], prefix[i] = bb.add(CNOT(), ctrl=a[0], target=prefix[i]) + a = np.concatenate([prefix, a]) a = bb.join(a, QUInt(n_bits)) b = bb.join(b, QUInt(n_bits)) a = bb.add(OnEach(n_bits, XGate()), q=a) a, b = bb.add(Add(QUInt(n_bits)), a=a, b=b) b = bb.add(OnEach(n_bits, XGate()), q=b) a = bb.add(OnEach(n_bits, XGate()), q=a) - b = bb.add(Cast(QUInt(n_bits), QInt(n_bits)), reg=b) + b = bb.add(Cast(QUInt(n_bits), self.b_dtype), reg=b) a = bb.split(a) - if isinstance(self.a_dtype, QInt): - for i in range(delta): - a[delta], a[i] = bb.add(CNOT(), ctrl=a[delta], target=a[i]) - bb.free(bb.join(a[:delta])) + if delta: + if isinstance(self.a_dtype, QInt): + for i in range(delta): + a[delta], a[i] = bb.add(CNOT(), ctrl=a[delta], target=a[i]) + bb.free(bb.join(a[:delta])) a = bb.join(a[delta:], QUInt(self.a_dtype.bitsize)) a = bb.add(Cast(QUInt(self.a_dtype.bitsize), self.a_dtype), reg=a) return {'a': a, 'b': b} diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index 26fc31ef2..fcd6da308 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -86,7 +86,7 @@ def test_t_complexity(n_bits): assert complexity.rotations == 0 -@pytest.mark.parametrize('dtype', [QInt]) +@pytest.mark.parametrize('dtype', [QInt, QUInt]) def test_against_classical_values(dtype): subtract = Subtract(dtype(3), dtype(5)) cbloq = subtract.decompose_bloq() @@ -100,3 +100,12 @@ def test_against_classical_values(dtype): ref = subtract.call_classically(a=a, b=b) comp = cbloq.call_classically(a=a, b=b) assert ref == comp + + +@pytest.mark.parametrize('bitsize', range(2, 5)) +def test_classical_add_signed_overflow(bitsize): + bloq = Subtract(QInt(bitsize)) + cbloq = bloq.decompose_bloq() + mn = -(2 ** (bitsize - 1)) + assert bloq.call_classically(a=0, b=mn) == (0, mn) + assert cbloq.call_classically(a=0, b=mn) == (0, mn) From 4880c3b29b50b357860ddb4c0f9062c1cfe386dd Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Mon, 8 Jul 2024 15:28:57 -0700 Subject: [PATCH 10/17] support symbolic decom --- qualtran/bloqs/arithmetic/subtraction.py | 41 +++++++++++++++--------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index d406867eb..f8dd27913 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -33,8 +33,9 @@ SoquetT, ) from qualtran.bloqs.arithmetic.addition import Add -from qualtran.bloqs.basic_gates import CNOT, OnEach, XGate +from qualtran.bloqs.basic_gates import OnEach, XGate from qualtran.bloqs.bookkeeping import Allocate, Cast, Free +from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiTargetCNOT from qualtran.drawing import Text if TYPE_CHECKING: @@ -141,35 +142,45 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: (OnEach(self.b_dtype.bitsize, XGate()), 3), (Add(QUInt(self.b_dtype.bitsize), QUInt(self.b_dtype.bitsize)), 1), } - .union([(CNOT(), 2 * delta)] if isinstance(self.a_dtype, QInt) else []) + .union([(MultiTargetCNOT(delta), 2)] if isinstance(self.a_dtype, QInt) else []) .union([(Allocate(QAny(delta)), 1), (Free(QAny(delta)), 1)] if delta else []) ) def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[str, 'SoquetT']: delta = self.b_dtype.bitsize - self.a_dtype.bitsize n_bits = self.b_dtype.bitsize - a = bb.split(a) - b = bb.split(b) if delta: - prefix = bb.split(bb.allocate(delta)) + if not isinstance(delta, int): + raise ValueError( + 'Symbolic decomposition not supported when a_dtype != b_dtype' + ) # pragma: no cover + a_split = bb.split(a) + prefix = bb.allocate(delta) if isinstance(self.a_dtype, QInt): - for i in range(delta): - a[0], prefix[i] = bb.add(CNOT(), ctrl=a[0], target=prefix[i]) - a = np.concatenate([prefix, a]) - a = bb.join(a, QUInt(n_bits)) - b = bb.join(b, QUInt(n_bits)) + a_split[0], prefix = bb.add( + MultiTargetCNOT(delta), control=a_split[0], targets=prefix + ) + prefix_split = bb.split(prefix) + a_split = np.concatenate([prefix_split, a_split]) + a = bb.join(a_split, QUInt(n_bits)) + else: + a = bb.add(Cast(self.a_dtype, QUInt(n_bits)), reg=a) + b = bb.add(Cast(self.b_dtype, QUInt(n_bits)), reg=b) a = bb.add(OnEach(n_bits, XGate()), q=a) a, b = bb.add(Add(QUInt(n_bits)), a=a, b=b) b = bb.add(OnEach(n_bits, XGate()), q=b) a = bb.add(OnEach(n_bits, XGate()), q=a) b = bb.add(Cast(QUInt(n_bits), self.b_dtype), reg=b) - a = bb.split(a) if delta: + a_split = bb.split(a) + prefix = bb.join(a_split[:delta]) if isinstance(self.a_dtype, QInt): - for i in range(delta): - a[delta], a[i] = bb.add(CNOT(), ctrl=a[delta], target=a[i]) - bb.free(bb.join(a[:delta])) - a = bb.join(a[delta:], QUInt(self.a_dtype.bitsize)) + a_split[delta], prefix = bb.add( + MultiTargetCNOT(delta), control=a_split[delta], targets=prefix + ) + prefix = bb.add(Cast(prefix.reg.dtype, QAny(delta)), reg=prefix) + bb.free(prefix) + a = bb.join(a_split[delta:], QUInt(self.a_dtype.bitsize)) a = bb.add(Cast(QUInt(self.a_dtype.bitsize), self.a_dtype), reg=a) return {'a': a, 'b': b} From 43095879956436c941b1ca2e4dcfaed1c0bde373 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Mon, 8 Jul 2024 15:46:04 -0700 Subject: [PATCH 11/17] add symp example --- qualtran/bloqs/arithmetic/subtraction.ipynb | 51 ++++++++++++++++++--- qualtran/bloqs/arithmetic/subtraction.py | 11 ++++- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/qualtran/bloqs/arithmetic/subtraction.ipynb b/qualtran/bloqs/arithmetic/subtraction.ipynb index 7a77515b3..f34136a14 100644 --- a/qualtran/bloqs/arithmetic/subtraction.ipynb +++ b/qualtran/bloqs/arithmetic/subtraction.ipynb @@ -38,10 +38,12 @@ "## `Subtract`\n", "An n-bit subtraction gate.\n", "\n", - "Implements $U|a\\rangle|b\\rangle \\rightarrow |a\\rangle|a-b\\rangle$ using $4n - 4 T$ gates.\n", + "Implements $U|a\\rangle|b\\rangle \\rightarrow |a\\rangle|a-b\\rangle$ using $4n - 4$ T gates.\n", + "\n", + "This construction uses the relation `a - b = ~(~a + b)` to turn the operation into addition. This relation is used in\n", + "[Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/pdf/2007.07391)\n", + "to turn addition into subtraction conditioned on a qubit.\n", "\n", - "This construction uses `XGate` and `AddK` to compute the twos-compliment of `b` before\n", - "doing a standard `Add`.\n", "\n", "#### Parameters\n", " - `a_dtype`: Quantum datatype used to represent the integer a.\n", @@ -49,7 +51,10 @@ "\n", "#### Registers\n", " - `a`: A a_dtype.bitsize-sized input register (register a above).\n", - " - `b`: A b_dtype.bitsize-sized input/output register (register b above).\n" + " - `b`: A b_dtype.bitsize-sized input/output register (register b above). \n", + "\n", + "#### References\n", + " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization, page 9](https://arxiv.org/pdf/2007.07391). \n" ] }, { @@ -123,6 +128,16 @@ "sub_diff_size_regs = Subtract(QInt(bitsize=4), QInt(bitsize=16))" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "de8ff586", + "metadata": {}, + "outputs": [], + "source": [ + "sub_symp_decomposition = Subtract(QInt(bitsize=n)).decompose_bloq()" + ] + }, { "cell_type": "markdown", "id": "82c580af", @@ -143,8 +158,8 @@ "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([sub_symb, sub_small, sub_large, sub_diff_size_regs],\n", - " ['`sub_symb`', '`sub_small`', '`sub_large`', '`sub_diff_size_regs`'])" + "show_bloqs([sub_symb, sub_small, sub_large, sub_diff_size_regs, sub_symp_decomposition],\n", + " ['`sub_symb`', '`sub_small`', '`sub_large`', '`sub_diff_size_regs`', '`sub_symp_decomposition`'])" ] }, { @@ -171,6 +186,19 @@ "show_call_graph(sub_symb_g)\n", "show_counts_sigma(sub_symb_sigma)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "141881c3", + "metadata": { + "cq.autogen": "Subtract.sub_symp_decomposition" + }, + "outputs": [], + "source": [ + "n = sympy.Symbol('n')\n", + "sub_symp_decomposition = Subtract(QInt(bitsize=n)).decompose_bloq()" + ] } ], "metadata": { @@ -180,7 +208,16 @@ "name": "python3" }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" } }, "nbformat": 4, diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index f8dd27913..c02b5a379 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -32,6 +32,7 @@ Soquet, SoquetT, ) +from qualtran._infra.composite_bloq import CompositeBloq from qualtran.bloqs.arithmetic.addition import Add from qualtran.bloqs.basic_gates import OnEach, XGate from qualtran.bloqs.bookkeeping import Allocate, Cast, Free @@ -210,6 +211,14 @@ def _sub_diff_size_regs() -> Subtract: return sub_diff_size_regs +@bloq_example +def _sub_symp_decomposition() -> CompositeBloq: + n = sympy.Symbol('n') + sub_symp_decomposition = Subtract(QInt(bitsize=n)).decompose_bloq() + return sub_symp_decomposition + + _SUB_DOC = BloqDocSpec( - bloq_cls=Subtract, examples=[_sub_symb, _sub_small, _sub_large, _sub_diff_size_regs] + bloq_cls=Subtract, + examples=[_sub_symb, _sub_small, _sub_large, _sub_diff_size_regs, _sub_symp_decomposition], ) From 3ece9ce23433e33c6922e43e546862008c04ad47 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Mon, 8 Jul 2024 16:04:41 -0700 Subject: [PATCH 12/17] fix Add --- qualtran/bloqs/arithmetic/addition.py | 3 +++ qualtran/bloqs/arithmetic/subtraction_test.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index a83c63afb..0c92fbce8 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -199,6 +199,9 @@ def decompose_from_registers( # reverse the order of qubits for big endian-ness. input_bits = quregs['a'][::-1] output_bits = quregs['b'][::-1] + if self.b_dtype.bitsize == 1: + yield CNOT().on(input_bits[0], output_bits[0]) + return ancillas = context.qubit_manager.qalloc(self.b_dtype.bitsize - 1)[::-1] # Start off the addition by anding into the ancilla yield And().on(input_bits[0], output_bits[0], ancillas[0]) diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index 5c73801c9..279699450 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -38,7 +38,7 @@ def test_subtract_bloq_decomposition_unsigned(a_bits, b_bits): c = (a - b) % max_b want[(a << b_bits) | c][a_b] = 1 got = gate.tensor_contract() - np.testing.assert_equal(got, want) + np.testing.assert_allclose(got, want) def _to_signed_binary(x: int, bits: int): From 5e26556414c75f4756abf3649744a571823434d6 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Fri, 19 Jul 2024 11:18:48 -0700 Subject: [PATCH 13/17] fix conflicts --- .../bloqs/arithmetic/multiplication.ipynb | 3 +- qualtran/bloqs/arithmetic/subtraction.ipynb | 183 ++++++++++++++++-- qualtran/bloqs/arithmetic/subtraction.py | 3 + qualtran/bloqs/arithmetic/subtraction_test.py | 17 +- 4 files changed, 186 insertions(+), 20 deletions(-) diff --git a/qualtran/bloqs/arithmetic/multiplication.ipynb b/qualtran/bloqs/arithmetic/multiplication.ipynb index c5beeeda8..c7326b061 100644 --- a/qualtran/bloqs/arithmetic/multiplication.ipynb +++ b/qualtran/bloqs/arithmetic/multiplication.ipynb @@ -813,8 +813,7 @@ " - `a`: `bitsize`-sized input register.\n", " - `result`: `bitsize`-sized output register. \n", " - `References`: \n", - " - `[Quantum Algorithms and Circuits for Scientific Computing](`: \n", - " - `https`: //arxiv.org/pdf/1511.08253). Section 2.1.\n" + " - `[Quantum Algorithms and Circuits for Scientific Computing](https`: //arxiv.org/pdf/1511.08253). Section 2.1.\n" ] }, { diff --git a/qualtran/bloqs/arithmetic/subtraction.ipynb b/qualtran/bloqs/arithmetic/subtraction.ipynb index ae856c1b2..584a0dce4 100644 --- a/qualtran/bloqs/arithmetic/subtraction.ipynb +++ b/qualtran/bloqs/arithmetic/subtraction.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "5e1ac363", + "id": "3f62c768", "metadata": { "cq.autogen": "title_cell" }, @@ -13,7 +13,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3a2de513", + "id": "1e50eeb1", "metadata": { "cq.autogen": "top_imports" }, @@ -30,7 +30,7 @@ }, { "cell_type": "markdown", - "id": "b91ab938", + "id": "9c974476", "metadata": { "cq.autogen": "Subtract.bloq_doc.md" }, @@ -44,7 +44,6 @@ "[Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/pdf/2007.07391)\n", "to turn addition into subtraction conditioned on a qubit.\n", "\n", - "\n", "#### Parameters\n", " - `a_dtype`: Quantum datatype used to represent the integer a.\n", " - `b_dtype`: Quantum datatype used to represent the integer b. Must be large enough to hold the result in the output register of a - b, or else it simply drops the most significant bits. If not specified, b_dtype is set to a_dtype. \n", @@ -60,7 +59,7 @@ { "cell_type": "code", "execution_count": null, - "id": "64dd238f", + "id": "0af034cf", "metadata": { "cq.autogen": "Subtract.bloq_doc.py" }, @@ -71,7 +70,7 @@ }, { "cell_type": "markdown", - "id": "4f77ad26", + "id": "9156f4e7", "metadata": { "cq.autogen": "Subtract.example_instances.md" }, @@ -82,7 +81,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2cd50aa6", + "id": "135553b1", "metadata": { "cq.autogen": "Subtract.sub_symb" }, @@ -95,7 +94,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e2862687", + "id": "84c7207a", "metadata": { "cq.autogen": "Subtract.sub_small" }, @@ -107,7 +106,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8b6584fc", + "id": "e98b3206", "metadata": { "cq.autogen": "Subtract.sub_large" }, @@ -119,7 +118,7 @@ { "cell_type": "code", "execution_count": null, - "id": "27312995", + "id": "e003f5ad", "metadata": { "cq.autogen": "Subtract.sub_diff_size_regs" }, @@ -131,16 +130,19 @@ { "cell_type": "code", "execution_count": null, - "id": "de8ff586", - "metadata": {}, + "id": "4aabfa98", + "metadata": { + "cq.autogen": "Subtract.sub_symp_decomposition" + }, "outputs": [], "source": [ + "n = sympy.Symbol('n')\n", "sub_symp_decomposition = Subtract(QInt(bitsize=n)).decompose_bloq()" ] }, { "cell_type": "markdown", - "id": "82c580af", + "id": "9b580c5b", "metadata": { "cq.autogen": "Subtract.graphical_signature.md" }, @@ -151,7 +153,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4e508f72", + "id": "aa412d69", "metadata": { "cq.autogen": "Subtract.graphical_signature.py" }, @@ -164,7 +166,7 @@ }, { "cell_type": "markdown", - "id": "a282a369", + "id": "6fad45f3", "metadata": { "cq.autogen": "Subtract.call_graph.md" }, @@ -175,7 +177,7 @@ { "cell_type": "code", "execution_count": null, - "id": "43f1884d", + "id": "6a3d1dbc", "metadata": { "cq.autogen": "Subtract.call_graph.py" }, @@ -187,4 +189,151 @@ "show_counts_sigma(sub_symb_sigma)" ] }, - { \ No newline at end of file + { + "cell_type": "markdown", + "id": "c4da55d6", + "metadata": { + "cq.autogen": "SubtractFrom.bloq_doc.md" + }, + "source": [ + "## `SubtractFrom`\n", + "A version of `Subtract` that subtracts the first register from the second in place.\n", + "\n", + "Implements $U|a\n", + "angle|b\n", + "angle\n", + "ightarrow |a\n", + "angle|b - a\n", + "angle$, essentially equivalent to\n", + "the statement `b -= a`.\n", + "\n", + "#### Parameters\n", + " - `dtype`: Quantum datatype used to represent the integers a, b, and b - a. \n", + "\n", + "#### Registers\n", + " - `a`: A dtype.bitsize-sized input register (register a above).\n", + " - `b`: A dtype.bitsize-sized input/output register (register b above).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ccc21662", + "metadata": { + "cq.autogen": "SubtractFrom.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import SubtractFrom" + ] + }, + { + "cell_type": "markdown", + "id": "b8182c9f", + "metadata": { + "cq.autogen": "SubtractFrom.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b56cde9", + "metadata": { + "cq.autogen": "SubtractFrom.sub_from_symb" + }, + "outputs": [], + "source": [ + "n = sympy.Symbol('n')\n", + "sub_from_symb = SubtractFrom(QInt(bitsize=n))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc45ae49", + "metadata": { + "cq.autogen": "SubtractFrom.sub_from_small" + }, + "outputs": [], + "source": [ + "sub_from_small = SubtractFrom(QInt(bitsize=4))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e40b0fb", + "metadata": { + "cq.autogen": "SubtractFrom.sub_from_large" + }, + "outputs": [], + "source": [ + "sub_from_large = SubtractFrom(QInt(bitsize=64))" + ] + }, + { + "cell_type": "markdown", + "id": "6d965b01", + "metadata": { + "cq.autogen": "SubtractFrom.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "093d2a0d", + "metadata": { + "cq.autogen": "SubtractFrom.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([sub_from_symb, sub_from_small, sub_from_large],\n", + " ['`sub_from_symb`', '`sub_from_small`', '`sub_from_large`'])" + ] + }, + { + "cell_type": "markdown", + "id": "420fd950", + "metadata": { + "cq.autogen": "SubtractFrom.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d1b9b0e", + "metadata": { + "cq.autogen": "SubtractFrom.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "sub_from_symb_g, sub_from_symb_sigma = sub_from_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(sub_from_symb_g)\n", + "show_counts_sigma(sub_from_symb_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index 77f2fe273..047879a3d 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +import math from typing import Dict, Optional, Set, Tuple, TYPE_CHECKING, Union +import numpy as np import sympy from attrs import field, frozen @@ -33,6 +35,7 @@ ) from qualtran._infra.composite_bloq import CompositeBloq from qualtran.bloqs.arithmetic.addition import Add +from qualtran.bloqs.arithmetic.negate import Negate from qualtran.bloqs.basic_gates import OnEach, XGate from qualtran.bloqs.bookkeeping import Allocate, Cast, Free from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiTargetCNOT diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index a6e36e42b..5aa3c488b 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -48,6 +48,7 @@ def test_sub_large(bloq_autotester): def test_sub_diff_size_regs(bloq_autotester): bloq_autotester(_sub_diff_size_regs) + @pytest.mark.parametrize( ['a_bits', 'b_bits'], [(a, b) for a in range(1, 6) for b in range(a, 6) if a + b <= 10] ) @@ -135,6 +136,7 @@ def test_classical_add_signed_overflow(bitsize): assert bloq.call_classically(a=0, b=mn) == (0, mn) assert cbloq.call_classically(a=0, b=mn) == (0, mn) + def test_sub_from_symb(bloq_autotester): bloq_autotester(_sub_from_symb) @@ -144,4 +146,17 @@ def test_sub_from_small(bloq_autotester): def test_sub_from_large(bloq_autotester): - bloq_autotester(_sub_from_large) \ No newline at end of file + bloq_autotester(_sub_from_large) + + +def test_subtract_from_bloq_decomposition(): + gate = SubtractFrom(QInt(4)) + qlt_testing.assert_valid_bloq_decomposition(gate) + + want = np.zeros((256, 256)) + for a_b in range(256): + a, b = a_b >> 4, a_b & 15 + c = (b - a) % 16 + want[(a << 4) | c][a_b] = 1 + got = gate.tensor_contract() + np.testing.assert_allclose(got, want) From 8d3473537faf2754b44a055150b02120ba97b0e9 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Fri, 19 Jul 2024 11:24:07 -0700 Subject: [PATCH 14/17] remove unrelated changes --- qualtran/bloqs/arithmetic/multiplication.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qualtran/bloqs/arithmetic/multiplication.ipynb b/qualtran/bloqs/arithmetic/multiplication.ipynb index c7326b061..c5beeeda8 100644 --- a/qualtran/bloqs/arithmetic/multiplication.ipynb +++ b/qualtran/bloqs/arithmetic/multiplication.ipynb @@ -813,7 +813,8 @@ " - `a`: `bitsize`-sized input register.\n", " - `result`: `bitsize`-sized output register. \n", " - `References`: \n", - " - `[Quantum Algorithms and Circuits for Scientific Computing](https`: //arxiv.org/pdf/1511.08253). Section 2.1.\n" + " - `[Quantum Algorithms and Circuits for Scientific Computing](`: \n", + " - `https`: //arxiv.org/pdf/1511.08253). Section 2.1.\n" ] }, { From 2fbd6ea5ac2fc1e72a8e7dd0bb8c2fa7540c5dfc Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Fri, 19 Jul 2024 11:58:49 -0700 Subject: [PATCH 15/17] fix --- qualtran/bloqs/arithmetic/subtraction.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index 047879a3d..8058ee605 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -157,7 +157,9 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: (OnEach(self.b_dtype.bitsize, XGate()), 3), (Add(QUInt(self.b_dtype.bitsize), QUInt(self.b_dtype.bitsize)), 1), } - .union([(MultiTargetCNOT(delta), 2)] if isinstance(self.a_dtype, QInt) else []) + .union( + [(MultiTargetCNOT(delta), 2)] if delta and isinstance(self.a_dtype, QInt) else [] + ) .union([(Allocate(QAny(delta)), 1), (Free(QAny(delta)), 1)] if delta else []) ) From 99ad3de7977b7871bec3128a2f8e0464b62a3547 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Fri, 19 Jul 2024 12:04:27 -0700 Subject: [PATCH 16/17] lint --- qualtran/bloqs/arithmetic/subtraction.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index 7fabd5a01..c0b81a7ac 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -36,7 +36,6 @@ from qualtran._infra.composite_bloq import CompositeBloq from qualtran.bloqs.arithmetic.addition import Add from qualtran.bloqs.arithmetic.bitwise import BitwiseNot -from qualtran.bloqs.arithmetic.negate import Negate from qualtran.bloqs.basic_gates import OnEach, XGate from qualtran.bloqs.bookkeeping import Allocate, Cast, Free from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiTargetCNOT From 0a80ea8b366c9333ea45a4cbb43eace0e0f0d933 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Fri, 19 Jul 2024 14:17:48 -0700 Subject: [PATCH 17/17] fix typo --- qualtran/bloqs/arithmetic/addition.py | 2 +- qualtran/bloqs/arithmetic/subtraction.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 0c92fbce8..47e2e1fd1 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -135,7 +135,7 @@ def on_classical_vals( # Addition of signed integers can result in overflow. In most classical programming languages (e.g. C++) # what happens when an overflow happens is left as an implementation detail for compiler designers. - # However for quantum subtraction the operation shoudl be unitary and that means that the unitary of + # However for quantum subtraction the operation should be unitary and that means that the unitary of # the bloq should be a permutation matrix. # If we hold `a` constant then the valid range of values of `b` [-N/2, N/2) gets shifted forward or backwards # by `a`. to keep the operation unitary overflowing values wrap around. this is the same as moving the range [0, N) diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index c0b81a7ac..0aada6426 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -128,7 +128,7 @@ def on_classical_vals( return {'a': a, 'b': int((a - b) % N)} # Subtraction of signed integers can result in overflow. In most classical programming languages (e.g. C++) # what happens when an overflow happens is left as an implementation detail for compiler designers. - # However for quantum subtraction the operation shoudl be unitary and that means that the unitary of + # However for quantum subtraction the operation should be unitary and that means that the unitary of # the bloq should be a permutation matrix. # If we hold `a` constant then the valid range of values of `b` [-N/2, N/2) gets shifted forward or backwards # by `a`. to keep the operation unitary overflowing values wrap around. this is the same as moving the range [0, N)