From 072f9e3ce79d990145be5c369c0581e47b980a96 Mon Sep 17 00:00:00 2001 From: Sanskriti Joshi Date: Sun, 11 Jan 2026 01:27:20 +0000 Subject: [PATCH 1/3] updates to cg2019 in python --- qmath/mult/cg2019.py | 111 +++++++++++++++++++++++++++++++++ qmath/mult/multipliers_test.py | 27 +++++++- 2 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 qmath/mult/cg2019.py diff --git a/qmath/mult/cg2019.py b/qmath/mult/cg2019.py new file mode 100644 index 0000000..32511b3 --- /dev/null +++ b/qmath/mult/cg2019.py @@ -0,0 +1,111 @@ +# Implementation of multiplier presented in paper: +# Asymptotically Efficient Quantum Karatsuba Multiplication +# Craig Gidney, 2019. +# https://arxiv.org/abs/1904.07356 +# All numbers are integer, Little Endian. +# Based off q# implementation from (https://github.com/fedimser/quant-arith-re/blob/main/lib/src/QuantumArithmetic/CG2019.qs) + +from psiqworkbench import Qubits, QUInt, qubricks +from psiqworkbench.interoperability import implements +from psiqworkbench.qubricks import Qubrick, GidneyAdd + +from ..utils.gates import ccnot, cnot + +# from ..add.cdkm2004 import CDKMAdder + +class schoolbook_multiplication(Qubrick): + """ + Implementation of the schoolbook multiplication: + "Asymptotically Efficient Quantum Karatsuba Multiplication", + Craig Gidney, 2019. + https://arxiv.org/abs/1904.07356 + """ + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.name = "schoolbook multiplier" + self.description = "Asymptotically efficient multiplier by Craig Gidney, 2019." + self.gadder = qubricks.GidneyAdd() + + def _plusEqual(self, lvalue: Qubits, offset: Qubits): + #trimmedOffset = offset[ : len(offset) - min([len(lvalue), len(offset)]) ] #Python doesnt include the last element -> could be a edian issue + trimmedOffset = offset[ len(offset) - min([len(lvalue), len(offset)]) :] #to fix edian issue? + pad_num_qubits = len(lvalue)-len(trimmedOffset) + if len(trimmedOffset) > 0: + pad = 0 + if pad_num_qubits > 0: + pad: Qubits = self.alloc_temp_qreg(len(lvalue) - len(trimmedOffset), "pad") + paddedOffset = trimmedOffset | pad #+ should be replaced with | + self.gadder.compute(lvalue, paddedOffset) #use gidney adder (try making it work with this first) + + def _compute(self, a: Qubits, b: Qubits, c: Qubits): #parent class of all subtypes for Qubits vs QuInt? + n1 = len(a) + n2 = len(b) + w: Qubits = self.alloc_temp_qreg(n2, "w") + for k in range(n1): + for i in range(n1): + ccnot(b[i], a[k], w[i]) + print(f"QUInt = {c.read()}") + self._plusEqual(c[k: len(c) -1], w) + print(f"QUInt = {c.read()}") + for i in range(n2): + ccnot(b[i], a[k], w[i]) + +class karatsub_multiplication(Qubrick): + """ + Implementation of the multiplier presented in paper: + "Asymptotically Efficient Quantum Karatsuba Multiplication", + Craig Gidney, 2019. + https://arxiv.org/abs/1904.07356 + """ + def __init__(self): + super().__init__() + self.name = "CG2019 Multiplier" + self.description = "Asymptotically efficient multiplier by Craig Gidney, 2019." + self.schoolbook_multiplier = schoolbook_multiplication() + + def _splitPadBuffer(self, buf: Qubits, pad: Qubits, basePieceSize: int, desiredPieceSize: int, pieceCount: int) -> list[Qubits]: + """ + Splits buf into pieces of base_piece_size, pads each to desired_piece_size using pad. + + Args: + buf: Buffer of qubits to split + pad: Padding qubits (should have enough for all missing pieces) + base_piece_size: Initial size of each piece from buf + desired_piece_size: Target size after padding + piece_count: Number of pieces to create + + Returns: + List of Qubits objects, each of size desired_piece_size + """ + result = [] #how to convert "mutable result : Qubit[][] = [];" from Q# to python? + k_pad = 0 + + for i in range(pieceCount): + k_buf = i * basePieceSize + res_i = [] + + #extract from buffer if needed + if(k_buf < len(buf)): + res_i = buf[k_buf:min(k_buf + basePieceSize, len(buf))] + + #calculate how much padding is needed + missing = desiredPieceSize - len(res_i) + result += [res_i + pad[k_pad : k_pad + missing]] + k_pad += missing + + return result + + def _mergeBufferRanges(work_registers: list[Qubits], start: int, length: int): + result = [] #how to convert "mutable result : Qubit[] = [];" from Q# to python? + for i in range(len(work_registers)): + for j in range(length): + result += [work_registers[i][start+j]] + return result + + def _ceillg2(n:int) -> int: + if (n <= 1): + return 0 +# else: + + +# def _compute(self, a: Qubits, b: Qubits, c: Qubits) -> None: \ No newline at end of file diff --git a/qmath/mult/multipliers_test.py b/qmath/mult/multipliers_test.py index 9a83389..2522fc7 100644 --- a/qmath/mult/multipliers_test.py +++ b/qmath/mult/multipliers_test.py @@ -4,11 +4,12 @@ from psiqworkbench import QPU, QUInt from qmath.mult import JHHAMultipler, MCTMultipler, Multiplier +from qmath.mult.cg2019 import schoolbook_multiplication def _check_multiplier(multiplier: Multiplier, num_bits, num_trials=5): qc = QPU(filters=[">>64bit>>", ">>bit-sim>>"]) - qc.reset(4 * num_bits + 1) + qc.reset(1000)#4 * num_bits + 1) a = QUInt(num_bits, "a", qc) b = QUInt(num_bits, "b", qc) @@ -24,6 +25,23 @@ def _check_multiplier(multiplier: Multiplier, num_bits, num_trials=5): assert c.read() == x * y +def _check_multiplier_set(multiplier: Multiplier, num_bits, x_in : int, y_in: int): + qc = QPU(filters=[">>64bit>>", ">>bit-sim>>"]) + qc.reset(1000)#4 * num_bits + 1) + + a = QUInt(num_bits, "a", qc) + b = QUInt(num_bits, "b", qc) + c = QUInt(2 * num_bits, "c", qc) + + x = x_in + y = y_in + a.write(x) + b.write(y) + c.write(0) + multiplier.compute(a, b, c) + assert c.read() == x * y + + @pytest.mark.parametrize("num_bits", [1, 2, 5, 10]) def test_mct_multiplier(num_bits: int): _check_multiplier(MCTMultipler(), num_bits) @@ -32,3 +50,10 @@ def test_mct_multiplier(num_bits: int): @pytest.mark.parametrize("num_bits", [1, 2, 5, 10]) def test_jhha_multiplier(num_bits: int): _check_multiplier(JHHAMultipler(num_bits), 10) + +#workbench is little endian -> LE for some q# functions +#q# is big endian + +@pytest.mark.parametrize("num_bits", [1, 2, 5, 8, 10]) +def test_sb_multiplier(num_bits: int): + _check_multiplier(schoolbook_multiplication(), num_bits, num_trials=1) From 731a7cbbbd7aa029bdf8fa118b13d96f9d4f1fd7 Mon Sep 17 00:00:00 2001 From: Sanskriti Joshi Date: Sun, 11 Jan 2026 02:25:51 +0000 Subject: [PATCH 2/3] updates for implementing cg2019 --- qmath/add/__init__.py | 2 +- qmath/add/adders_test.py | 3 ++- qmath/mult/__init__.py | 1 + qmath/utils/math.py | 7 +++++++ 4 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 qmath/utils/math.py diff --git a/qmath/add/__init__.py b/qmath/add/__init__.py index 97cbf04..dbf14d3 100644 --- a/qmath/add/__init__.py +++ b/qmath/add/__init__.py @@ -1,4 +1,4 @@ """Quantum addition algorithms.""" from .cdkm2004 import CDKMAdder -from .ttk2009 import TTKAdder +from .ttk2009 import TTKAdder \ No newline at end of file diff --git a/qmath/add/adders_test.py b/qmath/add/adders_test.py index 0bd31b3..76eb88c 100644 --- a/qmath/add/adders_test.py +++ b/qmath/add/adders_test.py @@ -9,6 +9,7 @@ from qmath.add import CDKMAdder, TTKAdder + # Tests in-place adder: # * Initializes two registers: x of size n1 and y of size n2. # * Fills them with random numbers. @@ -108,4 +109,4 @@ def test_adder_ttk(num_bits: tuple[int, int]): def test_adder_ttk_controlled(): _check_controlled_adder(TTKAdder(), 5, 5) - _check_controlled_adder(TTKAdder(), 5, 4, num_trials=10) + _check_controlled_adder(TTKAdder(), 5, 4, num_trials=10) \ No newline at end of file diff --git a/qmath/mult/__init__.py b/qmath/mult/__init__.py index 7eb6dae..de93598 100644 --- a/qmath/mult/__init__.py +++ b/qmath/mult/__init__.py @@ -3,3 +3,4 @@ from .jhaa2016 import JHHAMultipler from .mct2017 import MCTMultipler from .multiplier import Multiplier +from .cg2019 import schoolbook_multiplication diff --git a/qmath/utils/math.py b/qmath/utils/math.py new file mode 100644 index 0000000..6a8ba44 --- /dev/null +++ b/qmath/utils/math.py @@ -0,0 +1,7 @@ +#Random math functions that might be useful in multiple places + +def floorlog2(n: int) -> int: + """Return floor(log2(n)) for positive integers using Python's bit_length.""" + if n < 1: + raise ValueError("n must be >= 1") + return n.bit_length() - 1 \ No newline at end of file From 1afcdca982d4bb254763a560a7aa3bca7c1066a1 Mon Sep 17 00:00:00 2001 From: Sanskriti Joshi Date: Sun, 11 Jan 2026 04:32:00 +0000 Subject: [PATCH 3/3] fixed schoolbook --- qmath/mult/cg2019.py | 8 ++++---- qmath/mult/multipliers_test.py | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/qmath/mult/cg2019.py b/qmath/mult/cg2019.py index 32511b3..d47a9ce 100644 --- a/qmath/mult/cg2019.py +++ b/qmath/mult/cg2019.py @@ -42,11 +42,11 @@ def _compute(self, a: Qubits, b: Qubits, c: Qubits): #parent class of all subtyp n2 = len(b) w: Qubits = self.alloc_temp_qreg(n2, "w") for k in range(n1): - for i in range(n1): + for i in range(n2): ccnot(b[i], a[k], w[i]) - print(f"QUInt = {c.read()}") - self._plusEqual(c[k: len(c) -1], w) - print(f"QUInt = {c.read()}") + #print(f"QUInt = {c.read()}") + self._plusEqual(c[k: len(c) ], w) + #print(f"QUInt = {c.read()}") for i in range(n2): ccnot(b[i], a[k], w[i]) diff --git a/qmath/mult/multipliers_test.py b/qmath/mult/multipliers_test.py index 2522fc7..e5abdfd 100644 --- a/qmath/mult/multipliers_test.py +++ b/qmath/mult/multipliers_test.py @@ -54,6 +54,10 @@ def test_jhha_multiplier(num_bits: int): #workbench is little endian -> LE for some q# functions #q# is big endian -@pytest.mark.parametrize("num_bits", [1, 2, 5, 8, 10]) +@pytest.mark.parametrize("num_bits", [2, 5, 8, 10]) def test_sb_multiplier(num_bits: int): - _check_multiplier(schoolbook_multiplication(), num_bits, num_trials=1) + _check_multiplier(schoolbook_multiplication(), num_bits, num_trials=5) + +#@pytest.mark.parametrize("num_bits", [2]) +#def test_sb_multiplier(num_bits: int): +# _check_multiplier_set(schoolbook_multiplication(), num_bits, 3, 3)