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 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/EstimateUtils.qs b/lib/src/EstimateUtils.qs index cfbe922..0f2b204 100644 --- a/lib/src/EstimateUtils.qs +++ b/lib/src/EstimateUtils.qs @@ -1,3 +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]; @@ -53,14 +66,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/lib/src/QuantumArithmetic/LAInc.qs b/lib/src/QuantumArithmetic/LAInc.qs new file mode 100644 index 0000000..67f2bd1 --- /dev/null +++ b/lib/src/QuantumArithmetic/LAInc.qs @@ -0,0 +1,154 @@ +/// 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 { + 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 { + 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])); + } + } + } +} + +/// 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 { + 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(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 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 { + ApplyPauliFromBitString(PauliX, false, y_bits, x); + } apply { + Controlled X(x, (target)); + } +} + +/// Computes x = (x+1)%(2^n), where n=Length(x). +operation IncrementByFlip(x : Qubit[]) : Unit is Adj { + 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). + let x_len = Std.Convert.IntAsBigInt(Length(x)); + Controlled FlipIfEqual(ctrl, (ctr, x_len, carry)); + + // Uncompute ctr. + within { + Controlled ApplyToEachCA(ctrl, (X, x)); + } apply { + Adjoint CountTrailingOnes(x, ctr); + } + } +} diff --git a/lib/src/TestUtils.qs b/lib/src/TestUtils.qs index 4e86e07..61ea5b5 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. diff --git a/research/Incrementer.ipynb b/research/Incrementer.ipynb new file mode 100644 index 0000000..19a0ac6 --- /dev/null +++ b/research/Incrementer.ipynb @@ -0,0 +1,418 @@ +{ + "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.\n", + "\n", + "The table below compares ancilla count and CCZ coutn between the baseline and proposed incrementer." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \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
2301517
3412522
4523526
5634533
6745747
7856754
8967758
91078765
101189774
1112910782
12131011788
13141112795
141512139113
151613149122
161714159126
171815169133
181916179142
192017189150
203027289229
2140373811330
2250474811419
2360575811502
2470676813611
2580777813710
2690878813789
27100979813900
28256253254172554
\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 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": 1, + "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", + "\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.RunUnaryOp({n},{op1})\")\n", + " op2 = \"QuantumArithmetic.LAInc.IncrementByFlip\"\n", + " re2 = ctx.logical_counts(f\"EstimateUtils.RunUnaryOp({n},{op2})\") \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", + "pd.DataFrame(table, columns=headers)" + ] + } + ], + "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 +} diff --git a/test/LAInc_test.py b/test/LAInc_test.py new file mode 100644 index 0000000..c245619 --- /dev/null +++ b/test/LAInc_test.py @@ -0,0 +1,79 @@ +import math +import random + +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]) +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_random(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] + + +@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, 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)] 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)