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", + "Ancilla (base) | \n", + "CCZ (base) | \n", + "Ancilla (new) | \n", + "CCZ (new) | \n", + "
|---|---|---|---|---|---|
| 0 | \n", + "1 | \n", + "0 | \n", + "0 | \n", + "3 | \n", + "1 | \n", + "
| 1 | \n", + "2 | \n", + "0 | \n", + "0 | \n", + "3 | \n", + "7 | \n", + "
| 2 | \n", + "3 | \n", + "0 | \n", + "1 | \n", + "5 | \n", + "17 | \n", + "
| 3 | \n", + "4 | \n", + "1 | \n", + "2 | \n", + "5 | \n", + "22 | \n", + "
| 4 | \n", + "5 | \n", + "2 | \n", + "3 | \n", + "5 | \n", + "26 | \n", + "
| 5 | \n", + "6 | \n", + "3 | \n", + "4 | \n", + "5 | \n", + "33 | \n", + "
| 6 | \n", + "7 | \n", + "4 | \n", + "5 | \n", + "7 | \n", + "47 | \n", + "
| 7 | \n", + "8 | \n", + "5 | \n", + "6 | \n", + "7 | \n", + "54 | \n", + "
| 8 | \n", + "9 | \n", + "6 | \n", + "7 | \n", + "7 | \n", + "58 | \n", + "
| 9 | \n", + "10 | \n", + "7 | \n", + "8 | \n", + "7 | \n", + "65 | \n", + "
| 10 | \n", + "11 | \n", + "8 | \n", + "9 | \n", + "7 | \n", + "74 | \n", + "
| 11 | \n", + "12 | \n", + "9 | \n", + "10 | \n", + "7 | \n", + "82 | \n", + "
| 12 | \n", + "13 | \n", + "10 | \n", + "11 | \n", + "7 | \n", + "88 | \n", + "
| 13 | \n", + "14 | \n", + "11 | \n", + "12 | \n", + "7 | \n", + "95 | \n", + "
| 14 | \n", + "15 | \n", + "12 | \n", + "13 | \n", + "9 | \n", + "113 | \n", + "
| 15 | \n", + "16 | \n", + "13 | \n", + "14 | \n", + "9 | \n", + "122 | \n", + "
| 16 | \n", + "17 | \n", + "14 | \n", + "15 | \n", + "9 | \n", + "126 | \n", + "
| 17 | \n", + "18 | \n", + "15 | \n", + "16 | \n", + "9 | \n", + "133 | \n", + "
| 18 | \n", + "19 | \n", + "16 | \n", + "17 | \n", + "9 | \n", + "142 | \n", + "
| 19 | \n", + "20 | \n", + "17 | \n", + "18 | \n", + "9 | \n", + "150 | \n", + "
| 20 | \n", + "30 | \n", + "27 | \n", + "28 | \n", + "9 | \n", + "229 | \n", + "
| 21 | \n", + "40 | \n", + "37 | \n", + "38 | \n", + "11 | \n", + "330 | \n", + "
| 22 | \n", + "50 | \n", + "47 | \n", + "48 | \n", + "11 | \n", + "419 | \n", + "
| 23 | \n", + "60 | \n", + "57 | \n", + "58 | \n", + "11 | \n", + "502 | \n", + "
| 24 | \n", + "70 | \n", + "67 | \n", + "68 | \n", + "13 | \n", + "611 | \n", + "
| 25 | \n", + "80 | \n", + "77 | \n", + "78 | \n", + "13 | \n", + "710 | \n", + "
| 26 | \n", + "90 | \n", + "87 | \n", + "88 | \n", + "13 | \n", + "789 | \n", + "
| 27 | \n", + "100 | \n", + "97 | \n", + "98 | \n", + "13 | \n", + "900 | \n", + "
| 28 | \n", + "256 | \n", + "253 | \n", + "254 | \n", + "17 | \n", + "2554 | \n", + "