Skip to content

Commit 609b076

Browse files
authored
Merge pull request #4 from fedimser/CG2019_windowmod
Add windowed modexp from Gidney 2019
2 parents e115307 + b35dee6 commit 609b076

3 files changed

Lines changed: 128 additions & 6 deletions

File tree

lib/src/Main.qs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import QuantumArithmetic.Yuan2022.Divide;
1+
import QuantumArithmetic.CG20192.ModExpWindow;
22
import TestUtils.*;
33
import QuantumArithmetic.Utils;
44

55
// For debugging, run with Ctrl+F5,
66
operation Main() : Unit {
77
let n = 6;
8-
let m = 2;
9-
let a = 37;
10-
let b = 1;
11-
let ans = Test_Divide_Unequal(n, a, m, b, Divide);
8+
let a = 2L;
9+
let x = 42L;
10+
let N = 11L;
11+
let ans = TestModExp(n, a, x, N, ModExpWindow(_, _, _, _, 2, 2));
1212
Message($"ans={ans}");
1313
}

lib/src/QuantumArithmetic/CG20192.qs

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ import QuantumArithmetic.WindowedArithmeticUtils.Util.ForceMeasureResetBigInt;
1818
import QuantumArithmetic.WindowedArithmeticUtils.Util.BitLength;
1919
import QuantumArithmetic.WindowedArithmeticUtils.Xor.XorEqualConst;
2020
import QuantumArithmetic.WindowedArithmeticUtils.MulAdd_Window.PlusEqualConstTimesLEWindowed;
21+
import QuantumArithmetic.Utils;
22+
import Std.Arrays;
23+
import Std.Convert;
24+
import Std.Math;
25+
import Std.TableLookup.*;
26+
import Std.Arithmetic.RippleCarryCGIncByLE;
27+
import QuantumArithmetic.LYY2021.ModAdd;
2128

2229
operation Multiply (nx : Int, ny : Int, result_t : BigInt,
2330
classical_factor_x : BigInt, quantum_factor_y: BigInt) : BigInt {
@@ -52,4 +59,108 @@ operation MultiplyWindow (nx : Int, ny : Int, result_t : BigInt,
5259
return result;
5360
}
5461

55-
export Multiply, MultiplyWindow;
62+
internal function Skip2Data(
63+
generator: BigInt, period: BigInt, num_exponents: Int, num_bits: Int
64+
) : Bool[][] {
65+
mutable total = 1L;
66+
let num_entries = 1 <<< num_exponents;
67+
mutable table = [[false, size = num_bits], size = num_entries];
68+
69+
for k2 in 0..num_entries-1 {
70+
// Convert total to bits (little-endian)
71+
set table w/= k2 <- Convert.BigIntAsBoolArray(total, num_bits);
72+
set total *= generator;
73+
set total %= period;
74+
}
75+
76+
return table;
77+
}
78+
79+
/// Computes ans=(base^exponent)%modulus.
80+
/// ans must be prepared in zero state.
81+
/// base must be coprime with modulus.
82+
/// Doesn't change exponent.
83+
/// Fig. 7 in the paper
84+
operation ModExpWindow(exponent : Qubit[], ans : Qubit[], base : BigInt, modulus : BigInt,
85+
expWindowLen : Int, mulWindowLen : Int
86+
) : Unit is Adj + Ctl {
87+
let n1 = Length(exponent);
88+
let n2 = Length(ans);
89+
90+
let expWindows = Arrays.Chunks(expWindowLen, exponent);
91+
92+
// skip the first two expWindows with a direct lookup
93+
// based on [Gidney 2025](https://arxiv.org/abs/2505.15917)
94+
let skipNum =
95+
if (expWindowLen * 2 < n1) {
96+
expWindowLen * 2
97+
} else {
98+
n1
99+
};
100+
let data = Skip2Data(base, modulus, skipNum, n2);
101+
use output = Qubit[n2];
102+
within {
103+
Select(data, expWindows[0] + expWindows[1], output);
104+
} apply {
105+
Utils.ParallelCNOT(output, ans);
106+
}
107+
108+
for i in 2..Length(expWindows)-1 {
109+
let adjustedBase = Math.ExpModL(base, 1L <<< (i * expWindowLen), modulus);
110+
if (i % 2 == 1) {
111+
AddExpModWindowed(adjustedBase, modulus, 1, mulWindowLen, expWindows[i], output, ans);
112+
AddExpModWindowed(Math.InverseModL(adjustedBase, modulus), modulus, -1, mulWindowLen, expWindows[i], ans, output);
113+
} else{
114+
AddExpModWindowed(adjustedBase, modulus, 1, mulWindowLen, expWindows[i], ans, output);
115+
AddExpModWindowed(Math.InverseModL(adjustedBase, modulus), modulus, -1, mulWindowLen, expWindows[i], output, ans);
116+
}
117+
}
118+
if (Length(expWindows) % 2 == 1) {
119+
// Handle the case where there are more than 2 exponent windows
120+
Utils.ParallelSWAP(ans, output);
121+
}
122+
}
123+
124+
internal function ModExpData(factor : BigInt, expLength : Int, mulLength : Int, base : BigInt, mod : BigInt, sign : Int, numBits : Int) : Bool[][] {
125+
mutable data = [[false, size = numBits], size = 2^(expLength + mulLength)];
126+
for b in 0..2^mulLength - 1 {
127+
for a in 0..2^expLength - 1 {
128+
let idx = b * 2^expLength + a;
129+
let value = Math.ModulusL(factor * Convert.IntAsBigInt(b) * Convert.IntAsBigInt(sign) * (base^a), mod);
130+
set data w/= idx <- Convert.BigIntAsBoolArray(value, numBits);
131+
}
132+
}
133+
134+
data
135+
}
136+
137+
/// Computes zs += ys * (base ^ xs) % mod (for small registers xs and ys)
138+
/// based on Fig. 5 in [Gidney 2019](https://arxiv.org/abs/1905.07682)
139+
internal operation AddExpModWindowed(
140+
base : BigInt,
141+
mod : BigInt,
142+
sign : Int,
143+
mulWindowLen : Int,
144+
xs : Qubit[],
145+
ys : Qubit[],
146+
zs : Qubit[]
147+
) : Unit is Adj + Ctl {
148+
// split factor into parts
149+
let factorWindows = Arrays.Chunks(mulWindowLen, ys);
150+
151+
for i in 0..Length(factorWindows)-1 {
152+
// compute data for table lookup
153+
let factorValue = Math.ExpModL(2L, Convert.IntAsBigInt(i * mulWindowLen), mod);
154+
let data = ModExpData(factorValue, Length(xs), Length(factorWindows[i]), base, mod, sign, Length(zs));
155+
156+
use output = Qubit[Length(data[0])];
157+
158+
within {
159+
Select(data, xs + factorWindows[i], output);
160+
} apply {
161+
ModAdd(output, zs, mod);
162+
}
163+
}
164+
}
165+
166+
export Multiply, MultiplyWindow, ModExpWindow;

test/CG20192_test.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from qsharp import init, eval
33
import random
44

5+
import test_utils
56

67
@pytest.fixture(scope="session", autouse=True)
78
def setup():
@@ -14,3 +15,13 @@ def test_Multiply(n: int):
1415
x, y, t = random.randint(0, 2**n - 1), random.randint(0, 2**n - 1), random.randint(0, 2**n - 1)
1516
ans = eval(f"{op}({n},{n},{t}L,{x}L,{y}L)")
1617
assert ans == x * y + t
18+
19+
20+
@pytest.mark.parametrize("n", [3, 4, 8, 16, 32, 64, 80])
21+
def test_ModExp(n: int):
22+
op = "QuantumArithmetic.CG20192.ModExpWindow(_,_,_,_,2,2)"
23+
N = 1+2*random.randint(1, 2**(n-1)-1)
24+
a = test_utils.random_coprime(N)
25+
x = random.randint(0, 2**n-1)
26+
ans = eval(f"TestUtils.TestModExp({n},{a}L,{x}L,{N}L,{op})")
27+
assert ans == test_utils.pow_mod(a, x, N)

0 commit comments

Comments
 (0)