From d6e668c8084b50232e7b91d9931532fa2d180074 Mon Sep 17 00:00:00 2001 From: Dmytro Fedoriaka Date: Sun, 14 Jun 2026 12:10:53 -0700 Subject: [PATCH 1/9] update qdk dep --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d6649ac..ecf0fdb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,6 +19,6 @@ jobs: - name: Install requirements run: | pip install pytest - pip install qsharp==1.19.0 + pip install qdk==1.29.1 - name: Run tests run: pytest From 2c49b2a5bb6c35cb113b6b9be3f7e6d081c07be3 Mon Sep 17 00:00:00 2001 From: Dmytro Fedoriaka Date: Sun, 14 Jun 2026 13:39:54 -0700 Subject: [PATCH 2/9] Implement CountTrailingOnes --- lib/qsharp.json | 1 + lib/src/QuantumArithmetic/LAInc.qs | 35 ++++++++++++++++++++++++++++++ lib/src/TestUtils.qs | 34 +++++++++++++++++++++++++++++ test/LAInc_test.py | 26 ++++++++++++++++++++++ test/test_utils.py | 31 ++++++++++++++++++++++---- 5 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 lib/src/QuantumArithmetic/LAInc.qs create mode 100644 test/LAInc_test.py diff --git a/lib/qsharp.json b/lib/qsharp.json index c39dc81..f8e09f9 100644 --- a/lib/qsharp.json +++ b/lib/qsharp.json @@ -13,6 +13,7 @@ "src/QuantumArithmetic/DM2004.qs", "src/QuantumArithmetic/GKDKH2021.qs", "src/QuantumArithmetic/JHHA2016.qs", + "src/QuantumArithmetic/LAInc.qs", "src/QuantumArithmetic/LYTZW2013.qs", "src/QuantumArithmetic/LYY2021.qs", "src/QuantumArithmetic/MCT2017.qs", diff --git a/lib/src/QuantumArithmetic/LAInc.qs b/lib/src/QuantumArithmetic/LAInc.qs new file mode 100644 index 0000000..b1fc7f2 --- /dev/null +++ b/lib/src/QuantumArithmetic/LAInc.qs @@ -0,0 +1,35 @@ +/// Low-ancilla incrementer circuit. + + +function Log2(x : Int) : Double { + return Std.Math.Log(Std.Convert.IntAsDouble(x)) / Std.Math.LogOf2(); +} + +/// Computes ans := CTO(x), where CTO(x) is number of trailing (least +/// significant) "1" bits in register x before first zero bit. CTO(0)=0. +/// ans must be prepared in zero state. +operation CountTrailingOnes(x : Qubit[], ans : Qubit[]) : Unit is Ctl + Adj { + let x_len = Length(x); + if (x_len == 1) { + CNOT(x[0], ans[0]); + } elif (x_len == 2) { + X(x[1]); + CCNOT(x[0], x[1], ans[0]); + X(x[1]); + CCNOT(x[0], x[1], ans[1]); + } else { + let n : Int = Std.Math.Ceiling(Log2(x_len)); + let x_low = x[0..(1 <<< (n - 1))-1]; + let x_high = x[(1 <<< (n - 1))..x_len-1]; + if (x_len == 1 <<< n) { + Std.Diagnostics.Fact(Length(ans) >= n + 1, "ans too small"); + CountTrailingOnes(x_low, ans[0..n-1]); + Controlled CountTrailingOnes([ans[n-1]], (x_high, ans[0..n-2] + [ans[n]])); + CNOT(ans[n], ans[n-1]); + } else { + Std.Diagnostics.Fact(Length(ans) >= n, "ans too small"); + CountTrailingOnes(x_low, ans[0..n-1]); + Controlled CountTrailingOnes([ans[n-1]], (x_high, ans[0..n-2])); + } + } +} \ No newline at end of file diff --git a/lib/src/TestUtils.qs b/lib/src/TestUtils.qs index 4e86e07..6818c12 100644 --- a/lib/src/TestUtils.qs +++ b/lib/src/TestUtils.qs @@ -26,6 +26,39 @@ operation MeasureBigInt(reg : Qubit[]) : BigInt { return ans; } +/// Tests artihemtic operation that acts on array of qubit registers. +/// Numbers are unsigned little-endian integers. +operation TestArithmeticOp( + op : (Qubit[][]) => Unit, + sizes : Int[], + vals : BigInt[] +) : BigInt[] { + Fact(Length(sizes) == Length(vals), "sizes and vals must have the same length."); + let n = Length(sizes); + mutable total = 0; + for sz in sizes { + set total += sz; + } + use allQubits = Qubit[total]; + mutable regs : Qubit[][] = []; + mutable offset = 0; + for sz in sizes { + set regs += [allQubits[offset..offset + sz - 1]]; + set offset += sz; + } + for i in 0..n - 1 { + ApplyBigInt(vals[i], regs[i]); + } + + op(regs); + + mutable results : BigInt[] = []; + for i in 0..n - 1 { + set results += [MeasureBigInt(regs[i])]; + } + return results; +} + // Applies binary operation on quantum integers. // 1. Creates qubit register x of size n, populates it with integer x_val. // 2. Creates qubit register y of size n, populates it with integer y_val. @@ -146,6 +179,7 @@ operation UnaryOpInPlace(n : Int, x_val : BigInt, op : (Qubit[]) => Unit) : BigI return MeasureBigInt(x); } + /// Computes op(x). /// Also checks that Controlled functor is implemented correctly (at least for /// single control). diff --git a/test/LAInc_test.py b/test/LAInc_test.py new file mode 100644 index 0000000..0b876b7 --- /dev/null +++ b/test/LAInc_test.py @@ -0,0 +1,26 @@ +import math + +import pytest + +from test_utils import ArithmeticOpTester + + +def _ctz(x: int) -> int: + """Count trailing zeroes.""" + return (x & -x).bit_length() - 1 + + +def _cto(x: int) -> int: + """Count trailing ones.""" + return _ctz(x + 1) + + +@pytest.mark.parametrize("x_size", [1, 2, 3, 4, 5, 6, 7, 8]) +def test_CountLowestOnes(x_size: int): + """Tests CountLowestOnes.""" + op = "QuantumArithmetic.LAInc.CountTrailingOnes" + ans_size = math.floor(math.log2(x_size)) + 1 + tester = ArithmeticOpTester(op, [x_size, ans_size]) + for x in range(2**x_size): + result = tester.run([x, 0]) + assert result == [x, _cto(x)] diff --git a/test/test_utils.py b/test/test_utils.py index c4dae28..572fa60 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,12 +1,16 @@ -import random import math +import random + +import qdk + +CONTEXT = qdk.Context(project_root="./lib/") def pow_mod(x, y, p): """Computes (x**y)%p.""" a, x = 1, x % p - while (y > 0): - if (y & 1): + while y > 0: + if y & 1: a = (a * x) % p y = y >> 1 x = (x * x) % p @@ -15,7 +19,26 @@ def pow_mod(x, y, p): def random_coprime(N): for _ in range(100): - ans = random.randint(2, N-1) + ans = random.randint(2, N - 1) if math.gcd(ans, N) == 1: return ans raise ValueError(f"No coprime for {N}") + + +class ArithmeticOpTester: + """Tests arithmetic operation with fixed register sizes on many inputs.""" + + def __init__(self, op: str, arg_sizes: int): + self.arity = len(arg_sizes) + args_expanded = ",".join(f"r[{i}]" for i in range(self.arity)) + op1 = f"r=>{op}({args_expanded})" + + CONTEXT.eval(f""" + operation _RunOpOnInputs(inputs: BigInt[]) : BigInt[] {{ + return TestUtils.TestArithmeticOp({op1},{arg_sizes},inputs); + }} + """) + self.test_callable = CONTEXT.code._RunOpOnInputs + + def run(self, args: list[int]) -> list[int]: + return self.test_callable(args) From 6d8edea87840903f1151a30f28ebcd4c9cf4f615 Mon Sep 17 00:00:00 2001 From: Dmytro Fedoriaka Date: Sun, 14 Jun 2026 14:26:54 -0700 Subject: [PATCH 3/9] Implement FlipFirst --- lib/src/QuantumArithmetic/LAInc.qs | 40 +++++++++++++++++++++++++++++- test/LAInc_test.py | 34 ++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/lib/src/QuantumArithmetic/LAInc.qs b/lib/src/QuantumArithmetic/LAInc.qs index b1fc7f2..288b974 100644 --- a/lib/src/QuantumArithmetic/LAInc.qs +++ b/lib/src/QuantumArithmetic/LAInc.qs @@ -32,4 +32,42 @@ operation CountTrailingOnes(x : Qubit[], ans : Qubit[]) : Unit is Ctl + Adj { Controlled CountTrailingOnes([ans[n-1]], (x_high, ans[0..n-2])); } } -} \ No newline at end of file +} + +/// Flips first `ctr` bits in `target`. +/// If `ctr==0`, does nothing. +/// If `ctr==Length(target)`, flips all bits. +/// If `ctr>Length(target)`, behavior is undefined. +operation FlipFirst(target : Qubit[], ctr : Qubit[]) : Unit is Ctl + Adj { + let target_len = Length(target); + let ctr_len = Length(ctr); + let n = Std.Math.Floor(Log2(target_len)) + 1; + if (ctr_len > n) { + // Counter too large, ignore highest qubits. + FlipFirst(target, ctr[0..n-1]); + } elif (ctr_len < n) { + // Counter too small, can only affect prefix of target. + FlipFirst(target[0..(1 <<< ctr_len)-2], ctr); + } elif (target_len == 1) { + CNOT(ctr[0], target[0]); + } elif (target_len == 2) { + CNOT(ctr[0], target[0]); + CNOT(ctr[1], target[0]); + CNOT(ctr[1], target[1]); + } else { + Std.Diagnostics.Fact(ctr_len == n, ""); + Std.Diagnostics.Fact(target_len >= (1 <<< (n - 1)), ""); + let target_low : Qubit[] = target[0..(1 <<< (n - 1))-1]; + let target_high : Qubit[] = target[(1 <<< (n - 1))..target_len-1]; + + Controlled ApplyToEachCA([ctr[n-1]], (X, target_low)); + if (Length(target_high) > 0) { + Controlled FlipFirst([ctr[n-1]], (target_high, ctr[0..n-2])); + } + if (Length(target_low) > 1) { + X(ctr[n-1]); + Controlled FlipFirst([ctr[n-1]], (target_low[0..Length(target_low)-2], ctr[0..n-2])); + X(ctr[n-1]); + } + } +} \ No newline at end of file diff --git a/test/LAInc_test.py b/test/LAInc_test.py index 0b876b7..a895dd9 100644 --- a/test/LAInc_test.py +++ b/test/LAInc_test.py @@ -1,8 +1,9 @@ import math +import random import pytest -from test_utils import ArithmeticOpTester +from test_utils import ArithmeticOpTester def _ctz(x: int) -> int: @@ -15,12 +16,37 @@ def _cto(x: int) -> int: return _ctz(x + 1) -@pytest.mark.parametrize("x_size", [1, 2, 3, 4, 5, 6, 7, 8]) -def test_CountLowestOnes(x_size: int): - """Tests CountLowestOnes.""" +@pytest.mark.parametrize("x_size", [1, 2, 3, 4, 5, 6]) +def test_CountTrailingOnes_exhaustive(x_size: int): op = "QuantumArithmetic.LAInc.CountTrailingOnes" ans_size = math.floor(math.log2(x_size)) + 1 tester = ArithmeticOpTester(op, [x_size, ans_size]) for x in range(2**x_size): result = tester.run([x, 0]) assert result == [x, _cto(x)] + + +@pytest.mark.parametrize("x_size", [10, 20, 30]) +def test_CountTrailingOnes(x_size: int): + op = "QuantumArithmetic.LAInc.CountTrailingOnes" + ans_size = math.floor(math.log2(x_size)) + 1 + tester = ArithmeticOpTester(op, [x_size, ans_size]) + for ones_count in range(0, x_size + 1): + x = 2**ones_count - 1 + r = x_size - (ones_count + 1) + if r > 0: + x += (random.randint(0, 2**r - 1)) << (ones_count + 1) + result = tester.run([x, 0]) + assert result == [x, ones_count] + + +@pytest.mark.parametrize("target_size", [1, 2, 3, 4, 5, 6, 7, 8, 10, 20, 30]) +def test_FlipFirst(target_size: int): + op = "QuantumArithmetic.LAInc.FlipFirst" + ctr_size = math.floor(math.log2(target_size)) + 1 + tester = ArithmeticOpTester(op, [target_size, ctr_size]) + for flip_count in range(target_size + 1): + assert flip_count < 2**ctr_size + target_init = random.randint(0, 2**target_size - 1) + result = tester.run([target_init, flip_count]) + assert result == [target_init ^ ((1 << flip_count) - 1), flip_count] From 9e054dfabd043f444ddcc0171798d1d0d64fe89d Mon Sep 17 00:00:00 2001 From: Dmytro Fedoriaka Date: Sun, 14 Jun 2026 16:08:14 -0700 Subject: [PATCH 4/9] implement incrementer --- lib/src/QuantumArithmetic/LAInc.qs | 29 +++++++++++++++++++++++++++++ test/LAInc_test.py | 19 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/src/QuantumArithmetic/LAInc.qs b/lib/src/QuantumArithmetic/LAInc.qs index 288b974..b008513 100644 --- a/lib/src/QuantumArithmetic/LAInc.qs +++ b/lib/src/QuantumArithmetic/LAInc.qs @@ -70,4 +70,33 @@ operation FlipFirst(target : Qubit[], ctr : Qubit[]) : Unit is Ctl + Adj { X(ctr[n-1]); } } +} + +// Flips target if x==y. +operation FlipIfEqual(x : Qubit[], y : BigInt, target : Qubit) : Unit is Ctl + Adj { + let y_bits = Std.Convert.BigIntAsBoolArray(y, Length(x)); + within { + ApplyPauliFromBitString(PauliX, false, y_bits, x); + } apply { + Controlled X(x, (target)); + } +} + +// Increments register x. +operation IncrementByFlip(x : Qubit[]) : Unit is Adj { + use ctr = Qubit[Std.Math.Floor(Log2(Length(x) + 1)) + 1]; + use carry = Qubit(); + CountTrailingOnes(x, ctr); + QuantumArithmetic.ConstAdder.AddConstant(1L, ctr); + FlipFirst(x + [carry], ctr); + QuantumArithmetic.ConstAdder.AddConstant(-1L, ctr); + + // Uncompute carry. + // We know that carry=1 iff ctr=Length(x). + FlipIfEqual(ctr, Std.Convert.IntAsBigInt(Length(x)), carry); + + // Uncompute ctr. + ApplyToEachCA(X, x); + Adjoint CountTrailingOnes(x, ctr); + ApplyToEachCA(X, x); } \ No newline at end of file diff --git a/test/LAInc_test.py b/test/LAInc_test.py index a895dd9..3a7b23b 100644 --- a/test/LAInc_test.py +++ b/test/LAInc_test.py @@ -27,7 +27,7 @@ def test_CountTrailingOnes_exhaustive(x_size: int): @pytest.mark.parametrize("x_size", [10, 20, 30]) -def test_CountTrailingOnes(x_size: int): +def test_CountTrailingOnes_random(x_size: int): op = "QuantumArithmetic.LAInc.CountTrailingOnes" ans_size = math.floor(math.log2(x_size)) + 1 tester = ArithmeticOpTester(op, [x_size, ans_size]) @@ -50,3 +50,20 @@ def test_FlipFirst(target_size: int): target_init = random.randint(0, 2**target_size - 1) result = tester.run([target_init, flip_count]) assert result == [target_init ^ ((1 << flip_count) - 1), flip_count] + + +@pytest.mark.parametrize("n", [1, 2, 3, 4, 5, 6]) +def test_IncrementByFlip_exhaustive(n: int): + op = "QuantumArithmetic.LAInc.IncrementByFlip" + tester = ArithmeticOpTester(op, [n]) + for x in range(2**n): + assert tester.run([x]) == [(x + 1) % (2**n)] + + +@pytest.mark.parametrize("n", [10, 20, 15, 16, 100]) +def test_IncrementByFlip_random(n: int): + op = "QuantumArithmetic.LAInc.IncrementByFlip" + tester = ArithmeticOpTester(op, [n]) + xs = [0, 1, 2**n - 2, 2**n - 1] + [random.randint(0, 2**n - 1) for _ in range(20)] + for x in xs: + assert tester.run([x]) == [(x + 1) % (2**n)] From 863274f9e57d35a214ffd0a82692215fef21ec57 Mon Sep 17 00:00:00 2001 From: Dmytro Fedoriaka Date: Sun, 14 Jun 2026 16:13:21 -0700 Subject: [PATCH 5/9] initial re --- lib/src/EstimateUtils.qs | 9 +- research/Incrementer.ipynb | 280 +++++++++++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 research/Incrementer.ipynb diff --git a/lib/src/EstimateUtils.qs b/lib/src/EstimateUtils.qs index cfbe922..2c36f86 100644 --- a/lib/src/EstimateUtils.qs +++ b/lib/src/EstimateUtils.qs @@ -1,3 +1,8 @@ +operation RunUnaryOp(n : Int, op : (Qubit[]) => Unit) : Unit { + use a = Qubit[n]; + op(a); +} + operation BinaryOpExtraOut(n : Int, x_val : Int, y_val : Int, op : (Qubit[], Qubit[], Qubit[], Qubit) => Unit) : Int { use x = Qubit[n]; use y = Qubit[n]; @@ -53,14 +58,14 @@ operation RunModExp(n : Int, op : (Qubit[], Qubit[], BigInt, BigInt) => Unit) : op(x_qubits, ans, a, N); } -operation RunRadix(n: Int, radix: Int, op : (Qubit[], Qubit[], Qubit[], Int, (Qubit[], Qubit[], Qubit[]) => Unit is Adj) => Unit is Adj, adder_op: (Qubit[], Qubit[], Qubit[]) => Unit is Adj) : Unit { +operation RunRadix(n : Int, radix : Int, op : (Qubit[], Qubit[], Qubit[], Int, (Qubit[], Qubit[], Qubit[]) => Unit is Adj) => Unit is Adj, adder_op : (Qubit[], Qubit[], Qubit[]) => Unit is Adj) : Unit { use a = Qubit[n]; use b = Qubit[n]; use c = Qubit[n]; op(a, b, c, radix, adder_op); } -operation RunRadixCarry(n: Int, radix: Int, op : (Qubit[], Qubit[], Qubit[], Int, (Qubit[], Qubit[], Qubit[], Qubit) => Unit is Adj) => Unit is Adj, adder_op: (Qubit[], Qubit[], Qubit[], Qubit) => Unit is Adj) : Unit { +operation RunRadixCarry(n : Int, radix : Int, op : (Qubit[], Qubit[], Qubit[], Int, (Qubit[], Qubit[], Qubit[], Qubit) => Unit is Adj) => Unit is Adj, adder_op : (Qubit[], Qubit[], Qubit[], Qubit) => Unit is Adj) : Unit { use a = Qubit[n]; use b = Qubit[n]; use c = Qubit[n]; diff --git a/research/Incrementer.ipynb b/research/Incrementer.ipynb new file mode 100644 index 0000000..b58c3e9 --- /dev/null +++ b/research/Incrementer.ipynb @@ -0,0 +1,280 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 41, + "id": "7895ea6e-12c7-4101-9f66-677cf74c340b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nAncilla (base)CCZ (base)Ancilla (new)CCZ (new)
010031
120037
2301519
3412530
4523534
5634549
6745783
78567104
89677108
910787123
102017189396
113027289709
12403738111178
13504748111581
14605758112038
15706768132807
16807778133264
17908788133649
181009798134324
192562532541717766
\n", + "
" + ], + "text/plain": [ + " n Ancilla (base) CCZ (base) Ancilla (new) CCZ (new)\n", + "0 1 0 0 3 1\n", + "1 2 0 0 3 7\n", + "2 3 0 1 5 19\n", + "3 4 1 2 5 30\n", + "4 5 2 3 5 34\n", + "5 6 3 4 5 49\n", + "6 7 4 5 7 83\n", + "7 8 5 6 7 104\n", + "8 9 6 7 7 108\n", + "9 10 7 8 7 123\n", + "10 20 17 18 9 396\n", + "11 30 27 28 9 709\n", + "12 40 37 38 11 1178\n", + "13 50 47 48 11 1581\n", + "14 60 57 58 11 2038\n", + "15 70 67 68 13 2807\n", + "16 80 77 78 13 3264\n", + "17 90 87 88 13 3649\n", + "18 100 97 98 13 4324\n", + "19 256 253 254 17 17766" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import qdk\n", + "ctx = qdk.Context(project_root=\"../lib/\")\n", + "\n", + "headers=[\"n\", \"Ancilla (base)\", \"CCZ (base)\", \"Ancilla (new)\", \"CCZ (new)\"]\n", + "\n", + "table = []\n", + "for n in list(range(1,10))+list(range(10, 110, 10)) + [256]:\n", + " op1 = \"QuantumArithmetic.ConstAdder.AddConstant(1L,_)\"\n", + " re1 = ctx.logical_counts(f\"EstimateUtils.RunUnaryOp({n},{op1})\")\n", + " op2 = \"QuantumArithmetic.LAInc.IncrementByFlip\"\n", + " re2 = ctx.logical_counts(f\"EstimateUtils.RunUnaryOp({n},{op2})\") \n", + " table.append([n, re1[\"numQubits\"]-n, re1[\"cczCount\"], re2[\"numQubits\"]-n, re2[\"cczCount\"]])\n", + "\n", + "\n", + "import pandas as pd\n", + "pd.DataFrame(table, columns=headers)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21305a68-8861-48f7-972f-755ffa59c608", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 8ec946c53f514c47e16dd53d62665663e81aee4d Mon Sep 17 00:00:00 2001 From: Dmytro Fedoriaka Date: Sun, 14 Jun 2026 16:34:34 -0700 Subject: [PATCH 6/9] optimize controls in Count>TrailingOnes --- lib/src/QuantumArithmetic/LAInc.qs | 65 ++++++++++++++++++--------- research/Incrementer.ipynb | 72 +++++++++++++++--------------- 2 files changed, 81 insertions(+), 56 deletions(-) diff --git a/lib/src/QuantumArithmetic/LAInc.qs b/lib/src/QuantumArithmetic/LAInc.qs index b008513..51d0ed6 100644 --- a/lib/src/QuantumArithmetic/LAInc.qs +++ b/lib/src/QuantumArithmetic/LAInc.qs @@ -9,27 +9,52 @@ function Log2(x : Int) : Double { /// significant) "1" bits in register x before first zero bit. CTO(0)=0. /// ans must be prepared in zero state. operation CountTrailingOnes(x : Qubit[], ans : Qubit[]) : Unit is Ctl + Adj { - let x_len = Length(x); - if (x_len == 1) { - CNOT(x[0], ans[0]); - } elif (x_len == 2) { - X(x[1]); - CCNOT(x[0], x[1], ans[0]); - X(x[1]); - CCNOT(x[0], x[1], ans[1]); - } else { - let n : Int = Std.Math.Ceiling(Log2(x_len)); - let x_low = x[0..(1 <<< (n - 1))-1]; - let x_high = x[(1 <<< (n - 1))..x_len-1]; - if (x_len == 1 <<< n) { - Std.Diagnostics.Fact(Length(ans) >= n + 1, "ans too small"); - CountTrailingOnes(x_low, ans[0..n-1]); - Controlled CountTrailingOnes([ans[n-1]], (x_high, ans[0..n-2] + [ans[n]])); - CNOT(ans[n], ans[n-1]); + body (...) { + Controlled CountTrailingOnes([], (x, ans)); + } + controlled (ctrl, ...) { + let x_len = Length(x); + let ctrl_len = Length(ctrl); + if ctrl_len >= 2 { + use anc = Qubit(); + within { + AND(ctrl[0], ctrl[1], anc); + } apply { + Controlled CountTrailingOnes([anc] + ctrl[2..ctrl_len-1], (x, ans)); + } + } elif (x_len == 1 and ctrl_len == 0) { + CNOT(x[0], ans[0]); + } elif (x_len == 1 and ctrl_len == 1) { + CCNOT(ctrl[0], x[0], ans[0]); + } elif (x_len == 2 and ctrl_len == 0) { + X(x[1]); + CCNOT(x[0], x[1], ans[0]); + X(x[1]); + CCNOT(x[0], x[1], ans[1]); + } elif (x_len == 2 and ctrl_len == 1) { + use x0 = Qubit(); + within { + AND(ctrl[0], x[0], x0); + } apply { + X(x[1]); + CCNOT(x0, x[1], ans[0]); + X(x[1]); + CCNOT(x0, x[1], ans[1]); + } } else { - Std.Diagnostics.Fact(Length(ans) >= n, "ans too small"); - CountTrailingOnes(x_low, ans[0..n-1]); - Controlled CountTrailingOnes([ans[n-1]], (x_high, ans[0..n-2])); + let n : Int = Std.Math.Ceiling(Log2(x_len)); + let x_low = x[0..(1 <<< (n - 1))-1]; + let x_high = x[(1 <<< (n - 1))..x_len-1]; + if (x_len == 1 <<< n) { + Std.Diagnostics.Fact(Length(ans) >= n + 1, "ans too small"); + Controlled CountTrailingOnes(ctrl, (x_low, ans[0..n-1])); + Controlled CountTrailingOnes(ctrl + [ans[n-1]], (x_high, ans[0..n-2] + [ans[n]])); + Controlled CNOT(ctrl, (ans[n], ans[n-1])); + } else { + Std.Diagnostics.Fact(Length(ans) >= n, "ans too small"); + Controlled CountTrailingOnes(ctrl, (x_low, ans[0..n-1])); + Controlled CountTrailingOnes(ctrl + [ans[n-1]], (x_high, ans[0..n-2])); + } } } } diff --git a/research/Incrementer.ipynb b/research/Incrementer.ipynb index b58c3e9..f88d370 100644 --- a/research/Incrementer.ipynb +++ b/research/Incrementer.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 41, + "execution_count": 44, "id": "7895ea6e-12c7-4101-9f66-677cf74c340b", "metadata": {}, "outputs": [ @@ -65,7 +65,7 @@ " 1\n", " 2\n", " 5\n", - " 30\n", + " 24\n", " \n", " \n", " 4\n", @@ -73,7 +73,7 @@ " 2\n", " 3\n", " 5\n", - " 34\n", + " 28\n", " \n", " \n", " 5\n", @@ -81,7 +81,7 @@ " 3\n", " 4\n", " 5\n", - " 49\n", + " 37\n", " \n", " \n", " 6\n", @@ -89,7 +89,7 @@ " 4\n", " 5\n", " 7\n", - " 83\n", + " 69\n", " \n", " \n", " 7\n", @@ -97,7 +97,7 @@ " 5\n", " 6\n", " 7\n", - " 104\n", + " 76\n", " \n", " \n", " 8\n", @@ -105,7 +105,7 @@ " 6\n", " 7\n", " 7\n", - " 108\n", + " 80\n", " \n", " \n", " 9\n", @@ -113,7 +113,7 @@ " 7\n", " 8\n", " 7\n", - " 123\n", + " 89\n", " \n", " \n", " 10\n", @@ -121,7 +121,7 @@ " 17\n", " 18\n", " 9\n", - " 396\n", + " 272\n", " \n", " \n", " 11\n", @@ -129,7 +129,7 @@ " 27\n", " 28\n", " 9\n", - " 709\n", + " 449\n", " \n", " \n", " 12\n", @@ -137,7 +137,7 @@ " 37\n", " 38\n", " 11\n", - " 1178\n", + " 784\n", " \n", " \n", " 13\n", @@ -145,7 +145,7 @@ " 47\n", " 48\n", " 11\n", - " 1581\n", + " 1027\n", " \n", " \n", " 14\n", @@ -153,7 +153,7 @@ " 57\n", " 58\n", " 11\n", - " 2038\n", + " 1268\n", " \n", " \n", " 15\n", @@ -161,7 +161,7 @@ " 67\n", " 68\n", " 13\n", - " 2807\n", + " 1863\n", " \n", " \n", " 16\n", @@ -169,7 +169,7 @@ " 77\n", " 78\n", " 13\n", - " 3264\n", + " 2140\n", " \n", " \n", " 17\n", @@ -177,7 +177,7 @@ " 87\n", " 88\n", " 13\n", - " 3649\n", + " 2349\n", " \n", " \n", " 18\n", @@ -185,7 +185,7 @@ " 97\n", " 98\n", " 13\n", - " 4324\n", + " 2780\n", " \n", " \n", " 19\n", @@ -193,7 +193,7 @@ " 253\n", " 254\n", " 17\n", - " 17766\n", + " 11784\n", " \n", " \n", "\n", @@ -204,26 +204,26 @@ "0 1 0 0 3 1\n", "1 2 0 0 3 7\n", "2 3 0 1 5 19\n", - "3 4 1 2 5 30\n", - "4 5 2 3 5 34\n", - "5 6 3 4 5 49\n", - "6 7 4 5 7 83\n", - "7 8 5 6 7 104\n", - "8 9 6 7 7 108\n", - "9 10 7 8 7 123\n", - "10 20 17 18 9 396\n", - "11 30 27 28 9 709\n", - "12 40 37 38 11 1178\n", - "13 50 47 48 11 1581\n", - "14 60 57 58 11 2038\n", - "15 70 67 68 13 2807\n", - "16 80 77 78 13 3264\n", - "17 90 87 88 13 3649\n", - "18 100 97 98 13 4324\n", - "19 256 253 254 17 17766" + "3 4 1 2 5 24\n", + "4 5 2 3 5 28\n", + "5 6 3 4 5 37\n", + "6 7 4 5 7 69\n", + "7 8 5 6 7 76\n", + "8 9 6 7 7 80\n", + "9 10 7 8 7 89\n", + "10 20 17 18 9 272\n", + "11 30 27 28 9 449\n", + "12 40 37 38 11 784\n", + "13 50 47 48 11 1027\n", + "14 60 57 58 11 1268\n", + "15 70 67 68 13 1863\n", + "16 80 77 78 13 2140\n", + "17 90 87 88 13 2349\n", + "18 100 97 98 13 2780\n", + "19 256 253 254 17 11784" ] }, - "execution_count": 41, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } From 1d88429b7bef8339b1daadd413dd0420c4783029 Mon Sep 17 00:00:00 2001 From: Dmytro Fedoriaka Date: Sun, 14 Jun 2026 17:08:13 -0700 Subject: [PATCH 7/9] Controlled version --- lib/src/EstimateUtils.qs | 8 + lib/src/QuantumArithmetic/LAInc.qs | 113 ++++--- research/Incrementer.ipynb | 504 ++++++++++++++++++++++++++--- test/LAInc_test.py | 12 +- 4 files changed, 544 insertions(+), 93 deletions(-) diff --git a/lib/src/EstimateUtils.qs b/lib/src/EstimateUtils.qs index 2c36f86..0f2b204 100644 --- a/lib/src/EstimateUtils.qs +++ b/lib/src/EstimateUtils.qs @@ -1,8 +1,16 @@ +/// Runs operation on the given number of qubits. operation RunUnaryOp(n : Int, op : (Qubit[]) => Unit) : Unit { use a = Qubit[n]; op(a); } +/// Runs controlled operation on the given number of qubits. +operation RunUnaryOpCtl(n : Int, op : (Qubit[]) => Unit is Ctl) : Unit { + use ctrl = Qubit[1]; + use a = Qubit[n]; + Controlled op(ctrl, (a)); +} + operation BinaryOpExtraOut(n : Int, x_val : Int, y_val : Int, op : (Qubit[], Qubit[], Qubit[], Qubit) => Unit) : Int { use x = Qubit[n]; use y = Qubit[n]; diff --git a/lib/src/QuantumArithmetic/LAInc.qs b/lib/src/QuantumArithmetic/LAInc.qs index 51d0ed6..9a45ae2 100644 --- a/lib/src/QuantumArithmetic/LAInc.qs +++ b/lib/src/QuantumArithmetic/LAInc.qs @@ -64,40 +64,59 @@ operation CountTrailingOnes(x : Qubit[], ans : Qubit[]) : Unit is Ctl + Adj { /// If `ctr==Length(target)`, flips all bits. /// If `ctr>Length(target)`, behavior is undefined. operation FlipFirst(target : Qubit[], ctr : Qubit[]) : Unit is Ctl + Adj { - let target_len = Length(target); - let ctr_len = Length(ctr); - let n = Std.Math.Floor(Log2(target_len)) + 1; - if (ctr_len > n) { - // Counter too large, ignore highest qubits. - FlipFirst(target, ctr[0..n-1]); - } elif (ctr_len < n) { - // Counter too small, can only affect prefix of target. - FlipFirst(target[0..(1 <<< ctr_len)-2], ctr); - } elif (target_len == 1) { - CNOT(ctr[0], target[0]); - } elif (target_len == 2) { - CNOT(ctr[0], target[0]); - CNOT(ctr[1], target[0]); - CNOT(ctr[1], target[1]); - } else { - Std.Diagnostics.Fact(ctr_len == n, ""); - Std.Diagnostics.Fact(target_len >= (1 <<< (n - 1)), ""); - let target_low : Qubit[] = target[0..(1 <<< (n - 1))-1]; - let target_high : Qubit[] = target[(1 <<< (n - 1))..target_len-1]; + body (...) { + Controlled FlipFirst([], (target, ctr)); + } + controlled (ctrl, ...) { + let target_len = Length(target); + let ctr_len = Length(ctr); + let n = Std.Math.Floor(Log2(target_len)) + 1; + let ctrl_len = Length(ctrl); + if ctrl_len >= 2 { + use anc = Qubit(); + within { + AND(ctrl[0], ctrl[1], anc); + } apply { + Controlled FlipFirst([anc] + ctrl[2..ctrl_len-1], (target, ctr)); + } + } elif (ctr_len > n) { + // Counter too large, ignore highest qubits. + Controlled FlipFirst(ctrl, (target, ctr[0..n-1])); + } elif (ctr_len < n) { + // Counter too small, can only affect prefix of target. + Controlled FlipFirst(ctrl, (target[0..(1 <<< ctr_len)-2], ctr)); + } elif (target_len == 1 and ctrl_len == 0) { + CNOT(ctr[0], target[0]); + } elif (target_len == 1 and ctrl_len == 1) { + CCNOT(ctrl[0], ctr[0], target[0]); + } elif (target_len == 2 and ctrl_len == 0) { + CNOT(ctr[0], target[0]); + CNOT(ctr[1], target[0]); + CNOT(ctr[1], target[1]); + } elif (target_len == 2 and ctrl_len == 1) { + CCNOT(ctrl[0], ctr[0], target[0]); + CCNOT(ctrl[0], ctr[1], target[0]); + CCNOT(ctrl[0], ctr[1], target[1]); + } else { + Std.Diagnostics.Fact(ctr_len == n, ""); + Std.Diagnostics.Fact(target_len >= (1 <<< (n - 1)), ""); + let target_low : Qubit[] = target[0..(1 <<< (n - 1))-1]; + let target_high : Qubit[] = target[(1 <<< (n - 1))..target_len-1]; - Controlled ApplyToEachCA([ctr[n-1]], (X, target_low)); - if (Length(target_high) > 0) { - Controlled FlipFirst([ctr[n-1]], (target_high, ctr[0..n-2])); - } - if (Length(target_low) > 1) { - X(ctr[n-1]); - Controlled FlipFirst([ctr[n-1]], (target_low[0..Length(target_low)-2], ctr[0..n-2])); - X(ctr[n-1]); + Controlled ApplyToEachCA(ctrl + [ctr[n-1]], (X, target_low)); + if (Length(target_high) > 0) { + Controlled FlipFirst(ctrl + [ctr[n-1]], (target_high, ctr[0..n-2])); + } + if (Length(target_low) > 1) { + Controlled X(ctrl, (ctr[n-1])); + Controlled FlipFirst(ctrl + [ctr[n-1]], (target_low[0..Length(target_low)-2], ctr[0..n-2])); + Controlled X(ctrl, (ctr[n-1])); + } } } } -// Flips target if x==y. +/// Flips target iff x==y. operation FlipIfEqual(x : Qubit[], y : BigInt, target : Qubit) : Unit is Ctl + Adj { let y_bits = Std.Convert.BigIntAsBoolArray(y, Length(x)); within { @@ -107,21 +126,29 @@ operation FlipIfEqual(x : Qubit[], y : BigInt, target : Qubit) : Unit is Ctl + A } } -// Increments register x. +/// Computes x = (x+1)%(2^n), where n=Length(x). operation IncrementByFlip(x : Qubit[]) : Unit is Adj { - use ctr = Qubit[Std.Math.Floor(Log2(Length(x) + 1)) + 1]; - use carry = Qubit(); - CountTrailingOnes(x, ctr); - QuantumArithmetic.ConstAdder.AddConstant(1L, ctr); - FlipFirst(x + [carry], ctr); - QuantumArithmetic.ConstAdder.AddConstant(-1L, ctr); + body (...) { + Controlled IncrementByFlip([], (x)); + } + controlled (ctrl, ...) { + use ctr = Qubit[Std.Math.Floor(Log2(Length(x) + 1)) + 1]; + use carry = Qubit(); + CountTrailingOnes(x, ctr); + QuantumArithmetic.ConstAdder.AddConstant(1L, ctr); + Controlled FlipFirst(ctrl, (x + [carry], ctr)); + QuantumArithmetic.ConstAdder.AddConstant(-1L, ctr); - // Uncompute carry. - // We know that carry=1 iff ctr=Length(x). - FlipIfEqual(ctr, Std.Convert.IntAsBigInt(Length(x)), carry); + // Uncompute carry. + // We know that carry=1 iff ctr=Length(x). + let x_len = Std.Convert.IntAsBigInt(Length(x)); + Controlled FlipIfEqual(ctrl, (ctr, x_len, carry)); - // Uncompute ctr. - ApplyToEachCA(X, x); - Adjoint CountTrailingOnes(x, ctr); - ApplyToEachCA(X, x); + // Uncompute ctr. + within { + Controlled ApplyToEachCA(ctrl, (X, x)); + } apply { + Adjoint CountTrailingOnes(x, ctr); + } + } } \ No newline at end of file diff --git a/research/Incrementer.ipynb b/research/Incrementer.ipynb index f88d370..1567bd6 100644 --- a/research/Incrementer.ipynb +++ b/research/Incrementer.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 44, + "execution_count": 4, "id": "7895ea6e-12c7-4101-9f66-677cf74c340b", "metadata": {}, "outputs": [ @@ -57,7 +57,7 @@ " 0\n", " 1\n", " 5\n", - " 19\n", + " 17\n", " \n", " \n", " 3\n", @@ -65,7 +65,7 @@ " 1\n", " 2\n", " 5\n", - " 24\n", + " 22\n", " \n", " \n", " 4\n", @@ -73,7 +73,7 @@ " 2\n", " 3\n", " 5\n", - " 28\n", + " 26\n", " \n", " \n", " 5\n", @@ -81,7 +81,7 @@ " 3\n", " 4\n", " 5\n", - " 37\n", + " 33\n", " \n", " \n", " 6\n", @@ -89,7 +89,7 @@ " 4\n", " 5\n", " 7\n", - " 69\n", + " 47\n", " \n", " \n", " 7\n", @@ -97,7 +97,7 @@ " 5\n", " 6\n", " 7\n", - " 76\n", + " 54\n", " \n", " \n", " 8\n", @@ -105,7 +105,7 @@ " 6\n", " 7\n", " 7\n", - " 80\n", + " 58\n", " \n", " \n", " 9\n", @@ -113,87 +113,159 @@ " 7\n", " 8\n", " 7\n", - " 89\n", + " 65\n", " \n", " \n", " 10\n", + " 11\n", + " 8\n", + " 9\n", + " 7\n", + " 74\n", + " \n", + " \n", + " 11\n", + " 12\n", + " 9\n", + " 10\n", + " 7\n", + " 82\n", + " \n", + " \n", + " 12\n", + " 13\n", + " 10\n", + " 11\n", + " 7\n", + " 88\n", + " \n", + " \n", + " 13\n", + " 14\n", + " 11\n", + " 12\n", + " 7\n", + " 95\n", + " \n", + " \n", + " 14\n", + " 15\n", + " 12\n", + " 13\n", + " 9\n", + " 113\n", + " \n", + " \n", + " 15\n", + " 16\n", + " 13\n", + " 14\n", + " 9\n", + " 122\n", + " \n", + " \n", + " 16\n", + " 17\n", + " 14\n", + " 15\n", + " 9\n", + " 126\n", + " \n", + " \n", + " 17\n", + " 18\n", + " 15\n", + " 16\n", + " 9\n", + " 133\n", + " \n", + " \n", + " 18\n", + " 19\n", + " 16\n", + " 17\n", + " 9\n", + " 142\n", + " \n", + " \n", + " 19\n", " 20\n", " 17\n", " 18\n", " 9\n", - " 272\n", + " 150\n", " \n", " \n", - " 11\n", + " 20\n", " 30\n", " 27\n", " 28\n", " 9\n", - " 449\n", + " 229\n", " \n", " \n", - " 12\n", + " 21\n", " 40\n", " 37\n", " 38\n", " 11\n", - " 784\n", + " 330\n", " \n", " \n", - " 13\n", + " 22\n", " 50\n", " 47\n", " 48\n", " 11\n", - " 1027\n", + " 419\n", " \n", " \n", - " 14\n", + " 23\n", " 60\n", " 57\n", " 58\n", " 11\n", - " 1268\n", + " 502\n", " \n", " \n", - " 15\n", + " 24\n", " 70\n", " 67\n", " 68\n", " 13\n", - " 1863\n", + " 611\n", " \n", " \n", - " 16\n", + " 25\n", " 80\n", " 77\n", " 78\n", " 13\n", - " 2140\n", + " 710\n", " \n", " \n", - " 17\n", + " 26\n", " 90\n", " 87\n", " 88\n", " 13\n", - " 2349\n", + " 789\n", " \n", " \n", - " 18\n", + " 27\n", " 100\n", " 97\n", " 98\n", " 13\n", - " 2780\n", + " 900\n", " \n", " \n", - " 19\n", + " 28\n", " 256\n", " 253\n", " 254\n", " 17\n", - " 11784\n", + " 2554\n", " \n", " \n", "\n", @@ -203,27 +275,36 @@ " n Ancilla (base) CCZ (base) Ancilla (new) CCZ (new)\n", "0 1 0 0 3 1\n", "1 2 0 0 3 7\n", - "2 3 0 1 5 19\n", - "3 4 1 2 5 24\n", - "4 5 2 3 5 28\n", - "5 6 3 4 5 37\n", - "6 7 4 5 7 69\n", - "7 8 5 6 7 76\n", - "8 9 6 7 7 80\n", - "9 10 7 8 7 89\n", - "10 20 17 18 9 272\n", - "11 30 27 28 9 449\n", - "12 40 37 38 11 784\n", - "13 50 47 48 11 1027\n", - "14 60 57 58 11 1268\n", - "15 70 67 68 13 1863\n", - "16 80 77 78 13 2140\n", - "17 90 87 88 13 2349\n", - "18 100 97 98 13 2780\n", - "19 256 253 254 17 11784" + "2 3 0 1 5 17\n", + "3 4 1 2 5 22\n", + "4 5 2 3 5 26\n", + "5 6 3 4 5 33\n", + "6 7 4 5 7 47\n", + "7 8 5 6 7 54\n", + "8 9 6 7 7 58\n", + "9 10 7 8 7 65\n", + "10 11 8 9 7 74\n", + "11 12 9 10 7 82\n", + "12 13 10 11 7 88\n", + "13 14 11 12 7 95\n", + "14 15 12 13 9 113\n", + "15 16 13 14 9 122\n", + "16 17 14 15 9 126\n", + "17 18 15 16 9 133\n", + "18 19 16 17 9 142\n", + "19 20 17 18 9 150\n", + "20 30 27 28 9 229\n", + "21 40 37 38 11 330\n", + "22 50 47 48 11 419\n", + "23 60 57 58 11 502\n", + "24 70 67 68 13 611\n", + "25 80 77 78 13 710\n", + "26 90 87 88 13 789\n", + "27 100 97 98 13 900\n", + "28 256 253 254 17 2554" ] }, - "execution_count": 44, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -235,7 +316,7 @@ "headers=[\"n\", \"Ancilla (base)\", \"CCZ (base)\", \"Ancilla (new)\", \"CCZ (new)\"]\n", "\n", "table = []\n", - "for n in list(range(1,10))+list(range(10, 110, 10)) + [256]:\n", + "for n in list(range(1,20))+list(range(20, 110, 10)) + [256]:\n", " op1 = \"QuantumArithmetic.ConstAdder.AddConstant(1L,_)\"\n", " re1 = ctx.logical_counts(f\"EstimateUtils.RunUnaryOp({n},{op1})\")\n", " op2 = \"QuantumArithmetic.LAInc.IncrementByFlip\"\n", @@ -249,9 +330,334 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "21305a68-8861-48f7-972f-755ffa59c608", "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nAncilla (base)CCZ (base)Ancilla (new)CCZ (new)
010046
1201413
2313624
3425630
4537634
5649641
67511858
78613866
89715870
910817877
1011919886
11121021894
121311238100
131412258107
1415132710132
1516142910142
1617153110146
1718163310153
1819173510162
1920183710170
2030285710249
2140387712366
2250489712455
23605811712538
24706813714679
25807815714778
26908817714857
271009819714968
28256254509182814
\n", + "
" + ], + "text/plain": [ + " n Ancilla (base) CCZ (base) Ancilla (new) CCZ (new)\n", + "0 1 0 0 4 6\n", + "1 2 0 1 4 13\n", + "2 3 1 3 6 24\n", + "3 4 2 5 6 30\n", + "4 5 3 7 6 34\n", + "5 6 4 9 6 41\n", + "6 7 5 11 8 58\n", + "7 8 6 13 8 66\n", + "8 9 7 15 8 70\n", + "9 10 8 17 8 77\n", + "10 11 9 19 8 86\n", + "11 12 10 21 8 94\n", + "12 13 11 23 8 100\n", + "13 14 12 25 8 107\n", + "14 15 13 27 10 132\n", + "15 16 14 29 10 142\n", + "16 17 15 31 10 146\n", + "17 18 16 33 10 153\n", + "18 19 17 35 10 162\n", + "19 20 18 37 10 170\n", + "20 30 28 57 10 249\n", + "21 40 38 77 12 366\n", + "22 50 48 97 12 455\n", + "23 60 58 117 12 538\n", + "24 70 68 137 14 679\n", + "25 80 78 157 14 778\n", + "26 90 88 177 14 857\n", + "27 100 98 197 14 968\n", + "28 256 254 509 18 2814" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "headers=[\"n\", \"Ancilla (base)\", \"CCZ (base)\", \"Ancilla (new)\", \"CCZ (new)\"]\n", + "\n", + "table = []\n", + "for n in list(range(1,20))+list(range(20, 110, 10)) + [256]:\n", + " op1 = \"QuantumArithmetic.ConstAdder.AddConstant(1L,_)\"\n", + " re1 = ctx.logical_counts(f\"EstimateUtils.RunUnaryOpCtl({n},{op1})\")\n", + " op2 = \"QuantumArithmetic.LAInc.IncrementByFlip\"\n", + " re2 = ctx.logical_counts(f\"EstimateUtils.RunUnaryOpCtl({n},{op2})\") \n", + " table.append([n, re1[\"numQubits\"]-(n+1), re1[\"cczCount\"], re2[\"numQubits\"]-(n+1), re2[\"cczCount\"]])\n", + "\n", + "\n", + "import pandas as pd\n", + "pd.DataFrame(table, columns=headers)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c9c64b1-c2ad-4c3f-89ec-1f306619bbfb", + "metadata": {}, "outputs": [], "source": [] } diff --git a/test/LAInc_test.py b/test/LAInc_test.py index 3a7b23b..c245619 100644 --- a/test/LAInc_test.py +++ b/test/LAInc_test.py @@ -60,10 +60,20 @@ def test_IncrementByFlip_exhaustive(n: int): assert tester.run([x]) == [(x + 1) % (2**n)] -@pytest.mark.parametrize("n", [10, 20, 15, 16, 100]) +@pytest.mark.parametrize("n", [10, 15, 16, 20, 100]) def test_IncrementByFlip_random(n: int): op = "QuantumArithmetic.LAInc.IncrementByFlip" tester = ArithmeticOpTester(op, [n]) xs = [0, 1, 2**n - 2, 2**n - 1] + [random.randint(0, 2**n - 1) for _ in range(20)] for x in xs: assert tester.run([x]) == [(x + 1) % (2**n)] + + +@pytest.mark.parametrize("n", [10, 20]) +def test_IncrementByFlip_controlled(n: int): + op = "((c,x) => Controlled QuantumArithmetic.LAInc.IncrementByFlip(c,(x)))" + tester = ArithmeticOpTester(op, [1, n]) + xs = [0, 1, 2**n - 2, 2**n - 1] + [random.randint(0, 2**n - 1) for _ in range(20)] + for x in xs: + assert tester.run([0, x]) == [0, x] + assert tester.run([1, x]) == [1, (x + 1) % (2**n)] From 52d0855bf9b4f389319627664bc50b62bb14a1b6 Mon Sep 17 00:00:00 2001 From: Dmytro Fedoriaka Date: Sun, 14 Jun 2026 17:55:47 -0700 Subject: [PATCH 8/9] writeup --- research/Incrementer.ipynb | 394 +++++++------------------------------ 1 file changed, 66 insertions(+), 328 deletions(-) diff --git a/research/Incrementer.ipynb b/research/Incrementer.ipynb index 1567bd6..7aab5b0 100644 --- a/research/Incrementer.ipynb +++ b/research/Incrementer.ipynb @@ -1,8 +1,68 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "513e9852-ad37-42ea-b02e-266938bcb316", + "metadata": {}, + "source": [ + "## Low-ancilla incrementer\n", + "\n", + "*Dima Fedoriaka, June 2026*\n", + "\n", + "### Summary\n", + "\n", + "Here I present a circuit for incrementing n-qubit integer register using only O(log n) ancillas.\n", + "\n", + "### Motivation\n", + "\n", + "The practical use case for low-ancilla incrementer is constant addition when the constant is small (let's say bit size of constant is $n_c$). Then we can use constant adder circuit with carry on first $n_c$ qubits. For the rest of the register, we just need to add a carry qubit to it, which can be done by applying incrementer controlled by the carry bit.\n", + "\n", + "### Baseline\n", + "\n", + "As baseline, I am using incrementer from [this paper](https://www.worldscientific.com/doi/abs/10.1142/S0217979213501919) which I generalized to a constant adder (https://arxiv.org/pdf/2501.07060) and which is implemented in [ConstAdder.qs](../lib/src/QuantumArithmetic/ConstAdder.qs).\n", + "\n", + "### Implementation idea\n", + "\n", + "Define CTO(x) - \"count trailing ones\", i.e. number of least significant bits in x equal to 1 before first 0 bit.\n", + "\n", + "Then to increment x we need to flip first CTO(x)+1 bits in x.\n", + "\n", + "So, incrementing is reduced to implementing two operations:\n", + "* CountTrailingOnes(x, ans) - computes ans:=CTO(x)\n", + "* FlipFirst(target, ctr) - flips first `ctr` bits in `target`.\n", + "\n", + "Both CountTrailingOnes and FlipFirst can be implemented recursively by splitting input in 2 parts, first of them having length equal to a power of 2. Both of them use $O(\\log n)$ ancilla, adding one ancilla for each level of recursion.\n", + "\n", + "The incrementer works like this:\n", + "* Allocate counter register and carry qubit.\n", + "* Compute counter := CTO(x).\n", + "* Increment counter using baseline incrementer.\n", + "* Compute FlipFirst(x+carry, counter).\n", + "* Uncompute carry by applying multi-controlled X, using the fact that carry=1 if and only if counter=Length(x). Note that carry is only needed to handle overflow case when input is 2^n-1. If we can assume it's not going to happen, we don't need carry.\n", + "* Uncompute counter by flipping all bits in x, running CTO in reverse and flipping all bits in x again.\n", + "\n", + "The full implementation is in [LAInc.qs](../lib/src/QuantumArithmetic/LAInc.qs) and tests are in [LAInc_test.py](../test/LAInc_test.py).\n", + "\n", + "### Version with carry\n", + "\n", + "To turn presented incremented in incrementer with carry:\n", + "* Instead of using ancilla for carry, make it input qubit.\n", + "* Do not uncompute the carry qubit.\n", + "\n", + "### Cost\n", + "\n", + "Baseline incrementer uses $n-3$ ancillary qubits.\n", + "\n", + "The presented incrementer uses exactly $2 \\lceil \\log_2(n+2) \\rceil -1$ ancillary qubits which becomes less than base starting from n=11.\n", + "\n", + "On depth, the proposed circuit uses ~10n CCZ gates while base circuit uses ~1n CCZ gates.\n", + "\n", + "So it's much more expensive in depth, but might be worth it if it can reduce overall space requirement of an algorithm." + ] + }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 22, "id": "7895ea6e-12c7-4101-9f66-677cf74c340b", "metadata": {}, "outputs": [ @@ -304,13 +364,14 @@ "28 256 253 254 17 2554" ] }, - "execution_count": 4, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import qdk\n", + "import math\n", "ctx = qdk.Context(project_root=\"../lib/\")\n", "\n", "headers=[\"n\", \"Ancilla (base)\", \"CCZ (base)\", \"Ancilla (new)\", \"CCZ (new)\"]\n", @@ -321,332 +382,9 @@ " re1 = ctx.logical_counts(f\"EstimateUtils.RunUnaryOp({n},{op1})\")\n", " op2 = \"QuantumArithmetic.LAInc.IncrementByFlip\"\n", " re2 = ctx.logical_counts(f\"EstimateUtils.RunUnaryOp({n},{op2})\") \n", - " table.append([n, re1[\"numQubits\"]-n, re1[\"cczCount\"], re2[\"numQubits\"]-n, re2[\"cczCount\"]])\n", - "\n", - "\n", - "import pandas as pd\n", - "pd.DataFrame(table, columns=headers)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "21305a68-8861-48f7-972f-755ffa59c608", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nAncilla (base)CCZ (base)Ancilla (new)CCZ (new)
010046
1201413
2313624
3425630
4537634
5649641
67511858
78613866
89715870
910817877
1011919886
11121021894
121311238100
131412258107
1415132710132
1516142910142
1617153110146
1718163310153
1819173510162
1920183710170
2030285710249
2140387712366
2250489712455
23605811712538
24706813714679
25807815714778
26908817714857
271009819714968
28256254509182814
\n", - "
" - ], - "text/plain": [ - " n Ancilla (base) CCZ (base) Ancilla (new) CCZ (new)\n", - "0 1 0 0 4 6\n", - "1 2 0 1 4 13\n", - "2 3 1 3 6 24\n", - "3 4 2 5 6 30\n", - "4 5 3 7 6 34\n", - "5 6 4 9 6 41\n", - "6 7 5 11 8 58\n", - "7 8 6 13 8 66\n", - "8 9 7 15 8 70\n", - "9 10 8 17 8 77\n", - "10 11 9 19 8 86\n", - "11 12 10 21 8 94\n", - "12 13 11 23 8 100\n", - "13 14 12 25 8 107\n", - "14 15 13 27 10 132\n", - "15 16 14 29 10 142\n", - "16 17 15 31 10 146\n", - "17 18 16 33 10 153\n", - "18 19 17 35 10 162\n", - "19 20 18 37 10 170\n", - "20 30 28 57 10 249\n", - "21 40 38 77 12 366\n", - "22 50 48 97 12 455\n", - "23 60 58 117 12 538\n", - "24 70 68 137 14 679\n", - "25 80 78 157 14 778\n", - "26 90 88 177 14 857\n", - "27 100 98 197 14 968\n", - "28 256 254 509 18 2814" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "headers=[\"n\", \"Ancilla (base)\", \"CCZ (base)\", \"Ancilla (new)\", \"CCZ (new)\"]\n", - "\n", - "table = []\n", - "for n in list(range(1,20))+list(range(20, 110, 10)) + [256]:\n", - " op1 = \"QuantumArithmetic.ConstAdder.AddConstant(1L,_)\"\n", - " re1 = ctx.logical_counts(f\"EstimateUtils.RunUnaryOpCtl({n},{op1})\")\n", - " op2 = \"QuantumArithmetic.LAInc.IncrementByFlip\"\n", - " re2 = ctx.logical_counts(f\"EstimateUtils.RunUnaryOpCtl({n},{op2})\") \n", - " table.append([n, re1[\"numQubits\"]-(n+1), re1[\"cczCount\"], re2[\"numQubits\"]-(n+1), re2[\"cczCount\"]])\n", + " anc_new = re2[\"numQubits\"]-n\n", + " assert anc_new == 2*math.ceil(math.log2(n+2)) -1\n", + " table.append([n, re1[\"numQubits\"]-n, re1[\"cczCount\"], anc_new, re2[\"cczCount\"]])\n", "\n", "\n", "import pandas as pd\n", From ddda326b6ce9438b071ea00a900755f6482a1b6c Mon Sep 17 00:00:00 2001 From: Dmytro Fedoriaka Date: Sun, 14 Jun 2026 17:57:07 -0700 Subject: [PATCH 9/9] fix --- lib/src/QuantumArithmetic/LAInc.qs | 2 +- lib/src/TestUtils.qs | 1 - research/Incrementer.ipynb | 16 +++++----------- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/lib/src/QuantumArithmetic/LAInc.qs b/lib/src/QuantumArithmetic/LAInc.qs index 9a45ae2..67f2bd1 100644 --- a/lib/src/QuantumArithmetic/LAInc.qs +++ b/lib/src/QuantumArithmetic/LAInc.qs @@ -151,4 +151,4 @@ operation IncrementByFlip(x : Qubit[]) : Unit is Adj { Adjoint CountTrailingOnes(x, ctr); } } -} \ No newline at end of file +} diff --git a/lib/src/TestUtils.qs b/lib/src/TestUtils.qs index 6818c12..61ea5b5 100644 --- a/lib/src/TestUtils.qs +++ b/lib/src/TestUtils.qs @@ -179,7 +179,6 @@ operation UnaryOpInPlace(n : Int, x_val : BigInt, op : (Qubit[]) => Unit) : BigI return MeasureBigInt(x); } - /// Computes op(x). /// Also checks that Controlled functor is implemented correctly (at least for /// single control). diff --git a/research/Incrementer.ipynb b/research/Incrementer.ipynb index 7aab5b0..19a0ac6 100644 --- a/research/Incrementer.ipynb +++ b/research/Incrementer.ipynb @@ -57,12 +57,14 @@ "\n", "On depth, the proposed circuit uses ~10n CCZ gates while base circuit uses ~1n CCZ gates.\n", "\n", - "So it's much more expensive in depth, but might be worth it if it can reduce overall space requirement of an algorithm." + "So it's much more expensive in depth, but might be worth it if it can reduce overall space requirement of an algorithm.\n", + "\n", + "The table below compares ancilla count and CCZ coutn between the baseline and proposed incrementer." ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 1, "id": "7895ea6e-12c7-4101-9f66-677cf74c340b", "metadata": {}, "outputs": [ @@ -364,7 +366,7 @@ "28 256 253 254 17 2554" ] }, - "execution_count": 22, + "execution_count": 1, "metadata": {}, "output_type": "execute_result" } @@ -390,14 +392,6 @@ "import pandas as pd\n", "pd.DataFrame(table, columns=headers)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3c9c64b1-c2ad-4c3f-89ec-1f306619bbfb", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": {