Skip to content

Commit 9e5cae2

Browse files
committed
Add GF2PolyAdd bloq for quantum-quantum addition of polynomials over GF(2^m)
1 parent c371fc4 commit 9e5cae2

7 files changed

Lines changed: 346 additions & 1 deletion

File tree

dev_tools/qualtran_dev_tools/notebook_specs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
import qualtran.bloqs.gf_arithmetic.gf2_inverse
9393
import qualtran.bloqs.gf_arithmetic.gf2_multiplication
9494
import qualtran.bloqs.gf_arithmetic.gf2_square
95+
import qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add
9596
import qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add_k
9697
import qualtran.bloqs.gf_poly_arithmetic.gf_poly_split_and_join
9798
import qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp
@@ -623,6 +624,11 @@
623624
module=qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add_k,
624625
bloq_specs=[qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add_k._GF2_POLY_ADD_K_DOC],
625626
),
627+
NotebookSpecV2(
628+
title='Polynomials over GF($2^m$) - Addition',
629+
module=qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add,
630+
bloq_specs=[qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add._GF2_POLY_ADD_DOC],
631+
),
626632
]
627633

628634
ROT_QFT_PE = [

docs/bloqs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ Bloqs Library
111111

112112
gf_poly_arithmetic/gf_poly_split_and_join.ipynb
113113
gf_poly_arithmetic/gf2_poly_add_k.ipynb
114+
gf_poly_arithmetic/gf2_poly_add.ipynb
114115

115116
.. toctree::
116117
:maxdepth: 2

qualtran/bloqs/gf_poly_arithmetic/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
15+
from qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add import GF2PolyAdd
1616
from qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add_k import GF2PolyAddK
1717
from qualtran.bloqs.gf_poly_arithmetic.gf_poly_split_and_join import GFPolyJoin, GFPolySplit
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "846e63df",
6+
"metadata": {
7+
"cq.autogen": "title_cell"
8+
},
9+
"source": [
10+
"# Polynomials over GF($2^m$) - Addition"
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"id": "5dd599ca",
17+
"metadata": {
18+
"cq.autogen": "top_imports"
19+
},
20+
"outputs": [],
21+
"source": [
22+
"from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n",
23+
"from qualtran import QBit, QInt, QUInt, QAny\n",
24+
"from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n",
25+
"from typing import *\n",
26+
"import numpy as np\n",
27+
"import sympy\n",
28+
"import cirq"
29+
]
30+
},
31+
{
32+
"cell_type": "markdown",
33+
"id": "458cf4fa",
34+
"metadata": {
35+
"cq.autogen": "GF2PolyAdd.bloq_doc.md"
36+
},
37+
"source": [
38+
"## `GF2PolyAdd`\n",
39+
"In place quantum-quantum addition of two polynomials defined over GF($2^m$).\n",
40+
"\n",
41+
"The bloq implements in place addition of quantum registers $|f(x)\\rangle$ and $|g(x)\\rangle$\n",
42+
"storing coefficients of two degree-n polynomials defined over GF($2^m$).\n",
43+
"Addition in GF($2^m$) simply reduces to a component wise XOR, which can be implemented via\n",
44+
"CNOT gates.\n",
45+
"\n",
46+
"$$\n",
47+
" |f(x)\\rangle |g(x)\\rangle \\rightarrow |f(x)\\rangle |f(x) + g(x)\\rangle\n",
48+
"$$\n",
49+
"\n",
50+
"#### Parameters\n",
51+
" - `qgf_poly`: An instance of `QGFPoly` type that defines the data type for quantum register $|f(x)\\rangle$ storing coefficients of a degree-n polynomial defined over GF($2^m$). \n",
52+
"\n",
53+
"#### Registers\n",
54+
" - `f_x`: THRU register that stores coefficients of first polynomial defined over $GF(2^m)$.\n",
55+
" - `g_x`: THRU register that stores coefficients of second polynomial defined over $GF(2^m)$.\n"
56+
]
57+
},
58+
{
59+
"cell_type": "code",
60+
"execution_count": null,
61+
"id": "d67b7e61",
62+
"metadata": {
63+
"cq.autogen": "GF2PolyAdd.bloq_doc.py"
64+
},
65+
"outputs": [],
66+
"source": [
67+
"from qualtran.bloqs.gf_poly_arithmetic import GF2PolyAdd"
68+
]
69+
},
70+
{
71+
"cell_type": "markdown",
72+
"id": "0a0a575b",
73+
"metadata": {
74+
"cq.autogen": "GF2PolyAdd.example_instances.md"
75+
},
76+
"source": [
77+
"### Example Instances"
78+
]
79+
},
80+
{
81+
"cell_type": "code",
82+
"execution_count": null,
83+
"id": "f77964d0",
84+
"metadata": {
85+
"cq.autogen": "GF2PolyAdd.gf2_poly_4_8_add"
86+
},
87+
"outputs": [],
88+
"source": [
89+
"from qualtran import QGF, QGFPoly\n",
90+
"\n",
91+
"qgf_poly = QGFPoly(4, QGF(2, 3))\n",
92+
"gf2_poly_4_8_add = GF2PolyAdd(qgf_poly)"
93+
]
94+
},
95+
{
96+
"cell_type": "code",
97+
"execution_count": null,
98+
"id": "0b094507",
99+
"metadata": {
100+
"cq.autogen": "GF2PolyAdd.gf2_poly_add_symbolic"
101+
},
102+
"outputs": [],
103+
"source": [
104+
"import sympy\n",
105+
"\n",
106+
"from qualtran import QGF, QGFPoly\n",
107+
"\n",
108+
"n, m = sympy.symbols('n, m', positive=True, integers=True)\n",
109+
"qgf_poly = QGFPoly(n, QGF(2, m))\n",
110+
"gf2_poly_add_symbolic = GF2PolyAdd(qgf_poly)"
111+
]
112+
},
113+
{
114+
"cell_type": "markdown",
115+
"id": "43b66756",
116+
"metadata": {
117+
"cq.autogen": "GF2PolyAdd.graphical_signature.md"
118+
},
119+
"source": [
120+
"#### Graphical Signature"
121+
]
122+
},
123+
{
124+
"cell_type": "code",
125+
"execution_count": null,
126+
"id": "ecf1a676",
127+
"metadata": {
128+
"cq.autogen": "GF2PolyAdd.graphical_signature.py"
129+
},
130+
"outputs": [],
131+
"source": [
132+
"from qualtran.drawing import show_bloqs\n",
133+
"show_bloqs([gf2_poly_4_8_add, gf2_poly_add_symbolic],\n",
134+
" ['`gf2_poly_4_8_add`', '`gf2_poly_add_symbolic`'])"
135+
]
136+
},
137+
{
138+
"cell_type": "markdown",
139+
"id": "6d7fe836",
140+
"metadata": {
141+
"cq.autogen": "GF2PolyAdd.call_graph.md"
142+
},
143+
"source": [
144+
"### Call Graph"
145+
]
146+
},
147+
{
148+
"cell_type": "code",
149+
"execution_count": null,
150+
"id": "bc1596d3",
151+
"metadata": {
152+
"cq.autogen": "GF2PolyAdd.call_graph.py"
153+
},
154+
"outputs": [],
155+
"source": [
156+
"from qualtran.resource_counting.generalizers import ignore_split_join\n",
157+
"gf2_poly_4_8_add_g, gf2_poly_4_8_add_sigma = gf2_poly_4_8_add.call_graph(max_depth=1, generalizer=ignore_split_join)\n",
158+
"show_call_graph(gf2_poly_4_8_add_g)\n",
159+
"show_counts_sigma(gf2_poly_4_8_add_sigma)"
160+
]
161+
}
162+
],
163+
"metadata": {
164+
"kernelspec": {
165+
"display_name": "Python 3",
166+
"language": "python",
167+
"name": "python3"
168+
},
169+
"language_info": {
170+
"name": "python"
171+
}
172+
},
173+
"nbformat": 4,
174+
"nbformat_minor": 5
175+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
from functools import cached_property
15+
from typing import Dict, Set, TYPE_CHECKING, Union
16+
17+
import attrs
18+
19+
from qualtran import (
20+
Bloq,
21+
bloq_example,
22+
BloqDocSpec,
23+
DecomposeTypeError,
24+
QGFPoly,
25+
Register,
26+
Signature,
27+
)
28+
from qualtran.bloqs.gf_arithmetic import GF2Addition
29+
from qualtran.bloqs.gf_poly_arithmetic.gf_poly_split_and_join import GFPolyJoin, GFPolySplit
30+
from qualtran.symbolics import is_symbolic
31+
32+
if TYPE_CHECKING:
33+
from qualtran import BloqBuilder, Soquet
34+
from qualtran.resource_counting import BloqCountDictT, BloqCountT, SympySymbolAllocator
35+
from qualtran.simulation.classical_sim import ClassicalValT
36+
37+
38+
@attrs.frozen
39+
class GF2PolyAdd(Bloq):
40+
r"""In place quantum-quantum addition of two polynomials defined over GF($2^m$).
41+
42+
The bloq implements in place addition of quantum registers $|f(x)\rangle$ and $|g(x)\rangle$
43+
storing coefficients of two degree-n polynomials defined over GF($2^m$).
44+
Addition in GF($2^m$) simply reduces to a component wise XOR, which can be implemented via
45+
CNOT gates.
46+
47+
$$
48+
|f(x)\rangle |g(x)\rangle \rightarrow |f(x)\rangle |f(x) + g(x)\rangle
49+
$$
50+
51+
Args:
52+
qgf_poly: An instance of `QGFPoly` type that defines the data type for quantum
53+
register $|f(x)\rangle$ storing coefficients of a degree-n polynomial defined
54+
over GF($2^m$).
55+
56+
Registers:
57+
f_x: THRU register that stores coefficients of first polynomial defined over $GF(2^m)$.
58+
g_x: THRU register that stores coefficients of second polynomial defined over $GF(2^m)$.
59+
"""
60+
61+
qgf_poly: QGFPoly
62+
63+
@cached_property
64+
def signature(self) -> 'Signature':
65+
return Signature(
66+
[Register('f_x', dtype=self.qgf_poly), Register('g_x', dtype=self.qgf_poly)]
67+
)
68+
69+
def is_symbolic(self):
70+
return is_symbolic(self.qgf_poly.degree)
71+
72+
def build_composite_bloq(
73+
self, bb: 'BloqBuilder', *, f_x: 'Soquet', g_x: 'Soquet'
74+
) -> Dict[str, 'Soquet']:
75+
if self.is_symbolic():
76+
raise DecomposeTypeError(f"Cannot decompose symbolic {self}")
77+
f_x = bb.add(GFPolySplit(self.qgf_poly), reg=f_x)
78+
g_x = bb.add(GFPolySplit(self.qgf_poly), reg=g_x)
79+
for i in range(self.qgf_poly.degree + 1):
80+
f_x[i], g_x[i] = bb.add(GF2Addition(self.qgf_poly.qgf.bitsize), x=f_x[i], y=g_x[i])
81+
82+
f_x = bb.add(GFPolyJoin(self.qgf_poly), reg=f_x)
83+
g_x = bb.add(GFPolyJoin(self.qgf_poly), reg=g_x)
84+
return {'f_x': f_x, 'g_x': g_x}
85+
86+
def build_call_graph(
87+
self, ssa: 'SympySymbolAllocator'
88+
) -> Union['BloqCountDictT', Set['BloqCountT']]:
89+
return {GF2Addition(self.qgf_poly.qgf.bitsize): self.qgf_poly.degree + 1}
90+
91+
def on_classical_vals(self, *, f_x, g_x) -> Dict[str, 'ClassicalValT']:
92+
return {'f_x': f_x, 'g_x': f_x + g_x}
93+
94+
95+
@bloq_example
96+
def _gf2_poly_4_8_add() -> GF2PolyAdd:
97+
from qualtran import QGF, QGFPoly
98+
99+
qgf_poly = QGFPoly(4, QGF(2, 3))
100+
gf2_poly_4_8_add = GF2PolyAdd(qgf_poly)
101+
return gf2_poly_4_8_add
102+
103+
104+
@bloq_example
105+
def _gf2_poly_add_symbolic() -> GF2PolyAdd:
106+
import sympy
107+
108+
from qualtran import QGF, QGFPoly
109+
110+
n, m = sympy.symbols('n, m', positive=True, integers=True)
111+
qgf_poly = QGFPoly(n, QGF(2, m))
112+
gf2_poly_add_symbolic = GF2PolyAdd(qgf_poly)
113+
return gf2_poly_add_symbolic
114+
115+
116+
_GF2_POLY_ADD_DOC = BloqDocSpec(
117+
bloq_cls=GF2PolyAdd, examples=(_gf2_poly_4_8_add, _gf2_poly_add_symbolic)
118+
)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import numpy as np
15+
from galois import Poly
16+
17+
from qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add import _gf2_poly_4_8_add, _gf2_poly_add_symbolic
18+
from qualtran.testing import assert_consistent_classical_action
19+
20+
21+
def test_gf2_poly_4_8_add(bloq_autotester):
22+
bloq_autotester(_gf2_poly_4_8_add)
23+
24+
25+
def test_gf2_poly_symbolic_add_k(bloq_autotester):
26+
bloq_autotester(_gf2_poly_add_symbolic)
27+
28+
29+
def test_gf2_poly_add_classical_sim():
30+
bloq = _gf2_poly_4_8_add.make()
31+
f_x = Poly(bloq.qgf_poly.qgf.gf_type([0, 1, 2, 3, 4]))
32+
g_x = Poly(bloq.qgf_poly.qgf.gf_type([1, 2, 3, 4, 5]))
33+
assert bloq.call_classically(f_x=f_x, g_x=g_x) == (f_x, f_x + g_x) # type: ignore[arg-type]
34+
35+
f_x_range = np.asarray(
36+
[
37+
Poly(bloq.qgf_poly.qgf.gf_type([0, 0, 0, 0, 0])),
38+
Poly(bloq.qgf_poly.qgf.gf_type([7, 7, 7, 7, 7])),
39+
Poly(bloq.qgf_poly.qgf.gf_type([0, 0, 3, 5, 7])),
40+
Poly(bloq.qgf_poly.qgf.gf_type([2, 3, 5, 0, 0])),
41+
]
42+
)
43+
assert_consistent_classical_action(bloq, f_x=f_x_range, g_x=f_x_range)

qualtran/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample):
105105
'gf_poly_join', # cannot serialize QGF and QGFPoly
106106
'gf2_poly_4_8_add_k', # cannot serialize QGF and QGFPoly
107107
'gf2_poly_add_k_symbolic', # cannot serialize QGF and QGFPoly
108+
'gf2_poly_4_8_add', # cannot serialize QGF and QGFPoly
109+
'gf2_poly_add_symbolic', # cannot serialize QGF and QGFPoly
108110
'gqsp_1d_ising',
109111
'auto_partition',
110112
'unitary_block_encoding',

0 commit comments

Comments
 (0)