Skip to content

Commit f7262f8

Browse files
authored
[QGFPoly] Add GF2PolyAddK, GFPolySplit and GFPolyJoin bloqs (#1622)
* Add GF2PolyAddK, GFPolySplit and GFPolyJoin bloqs * Fix tests, mypy and pylint * Mark symbolic only when degree is symbolic * Add tests for gate counts
1 parent 3abebf0 commit f7262f8

10 files changed

Lines changed: 965 additions & 0 deletions

File tree

dev_tools/qualtran_dev_tools/notebook_specs.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@
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_k
96+
import qualtran.bloqs.gf_poly_arithmetic.gf_poly_split_and_join
9597
import qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp
9698
import qualtran.bloqs.mcmt.and_bloq
9799
import qualtran.bloqs.mcmt.controlled_via_and
@@ -604,6 +606,24 @@
604606
),
605607
]
606608

609+
GF_POLY_ARITHMETIC = [
610+
# --------------------------------------------------------------------------
611+
# ----- Polynomials defined over Galois Fields (GF) Arithmetic --------
612+
# --------------------------------------------------------------------------
613+
NotebookSpecV2(
614+
title='Polynomials over GF($p^m$) - Split and Join',
615+
module=qualtran.bloqs.gf_poly_arithmetic.gf_poly_split_and_join,
616+
bloq_specs=[
617+
qualtran.bloqs.gf_poly_arithmetic.gf_poly_split_and_join._GF_POLY_SPLIT_DOC,
618+
qualtran.bloqs.gf_poly_arithmetic.gf_poly_split_and_join._GF_POLY_JOIN_DOC,
619+
],
620+
),
621+
NotebookSpecV2(
622+
title='Polynomials over GF($2^m$) - Add Constant',
623+
module=qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add_k,
624+
bloq_specs=[qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add_k._GF2_POLY_ADD_K_DOC],
625+
),
626+
]
607627

608628
ROT_QFT_PE = [
609629
# --------------------------------------------------------------------------
@@ -935,6 +955,7 @@
935955
('Arithmetic', ARITHMETIC),
936956
('Modular Arithmetic', MOD_ARITHMETIC),
937957
('GF Arithmetic', GF_ARITHMETIC),
958+
('Polynomials over Galois Fields', GF_POLY_ARITHMETIC),
938959
('Rotations', ROT_QFT_PE),
939960
('Block Encoding', BLOCK_ENCODING),
940961
('Optimization', OPTIMIZATION),

docs/bloqs/index.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ Bloqs Library
105105
gf_arithmetic/gf2_square.ipynb
106106
gf_arithmetic/gf2_inverse.ipynb
107107

108+
.. toctree::
109+
:maxdepth: 2
110+
:caption: Polynomials over Galois Fields:
111+
112+
gf_poly_arithmetic/gf_poly_split_and_join.ipynb
113+
gf_poly_arithmetic/gf2_poly_add_k.ipynb
114+
108115
.. toctree::
109116
:maxdepth: 2
110117
:caption: Rotations:
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
15+
16+
from qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add_k import GF2PolyAddK
17+
from qualtran.bloqs.gf_poly_arithmetic.gf_poly_split_and_join import GFPolyJoin, GFPolySplit
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "71e0dbd8",
6+
"metadata": {
7+
"cq.autogen": "title_cell"
8+
},
9+
"source": [
10+
"# Polynomials over GF($2^m$) - Add Constant"
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"id": "c02448cd",
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": "a5f23a08",
34+
"metadata": {
35+
"cq.autogen": "GF2PolyAddK.bloq_doc.md"
36+
},
37+
"source": [
38+
"## `GF2PolyAddK`\n",
39+
"In place addition of a constant polynomial defined over GF($2^m$).\n",
40+
"\n",
41+
"The bloq implements in place addition of a classical constant polynomial $g(x)$ and\n",
42+
"a quantum register $|f(x)\\rangle$ storing coefficients of a degree-n polynomial defined\n",
43+
"over GF($2^m$). Addition in GF($2^m$) simply reduces to a component wise XOR, which can\n",
44+
"be implemented via X gates.\n",
45+
"\n",
46+
"$$\n",
47+
" |f(x)\\rangle \\rightarrow |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+
" - `g_x`: An instance of `galois.Poly` that specifies that constant polynomial g(x) defined over GF($2^m$) that should be added to the input register f(x). \n",
53+
"\n",
54+
"#### Registers\n",
55+
" - `f_x`: Input THRU register that stores coefficients of polynomial defined over $GF(2^m)$.\n"
56+
]
57+
},
58+
{
59+
"cell_type": "code",
60+
"execution_count": null,
61+
"id": "d40e23be",
62+
"metadata": {
63+
"cq.autogen": "GF2PolyAddK.bloq_doc.py"
64+
},
65+
"outputs": [],
66+
"source": [
67+
"from qualtran.bloqs.gf_poly_arithmetic import GF2PolyAddK"
68+
]
69+
},
70+
{
71+
"cell_type": "markdown",
72+
"id": "101615d7",
73+
"metadata": {
74+
"cq.autogen": "GF2PolyAddK.example_instances.md"
75+
},
76+
"source": [
77+
"### Example Instances"
78+
]
79+
},
80+
{
81+
"cell_type": "code",
82+
"execution_count": null,
83+
"id": "2cc9165f",
84+
"metadata": {
85+
"cq.autogen": "GF2PolyAddK.gf2_poly_4_8_add_k"
86+
},
87+
"outputs": [],
88+
"source": [
89+
"from galois import Poly\n",
90+
"\n",
91+
"from qualtran import QGF, QGFPoly\n",
92+
"\n",
93+
"qgf_poly = QGFPoly(4, QGF(2, 3))\n",
94+
"g_x = Poly(qgf_poly.qgf.gf_type([1, 2, 3, 4, 5]))\n",
95+
"gf2_poly_4_8_add_k = GF2PolyAddK(qgf_poly, g_x)"
96+
]
97+
},
98+
{
99+
"cell_type": "code",
100+
"execution_count": null,
101+
"id": "9081edee",
102+
"metadata": {
103+
"cq.autogen": "GF2PolyAddK.gf2_poly_add_k_symbolic"
104+
},
105+
"outputs": [],
106+
"source": [
107+
"import sympy\n",
108+
"from galois import Poly\n",
109+
"\n",
110+
"from qualtran import QGF, QGFPoly\n",
111+
"\n",
112+
"n, m = sympy.symbols('n, m', positive=True, integers=True)\n",
113+
"qgf_poly = QGFPoly(n, QGF(2, m))\n",
114+
"gf2_poly_add_k_symbolic = GF2PolyAddK(qgf_poly, Poly([0, 0, 0, 0]))"
115+
]
116+
},
117+
{
118+
"cell_type": "markdown",
119+
"id": "11a4d71f",
120+
"metadata": {
121+
"cq.autogen": "GF2PolyAddK.graphical_signature.md"
122+
},
123+
"source": [
124+
"#### Graphical Signature"
125+
]
126+
},
127+
{
128+
"cell_type": "code",
129+
"execution_count": null,
130+
"id": "66b4a211",
131+
"metadata": {
132+
"cq.autogen": "GF2PolyAddK.graphical_signature.py"
133+
},
134+
"outputs": [],
135+
"source": [
136+
"from qualtran.drawing import show_bloqs\n",
137+
"show_bloqs([gf2_poly_4_8_add_k, gf2_poly_add_k_symbolic],\n",
138+
" ['`gf2_poly_4_8_add_k`', '`gf2_poly_add_k_symbolic`'])"
139+
]
140+
},
141+
{
142+
"cell_type": "markdown",
143+
"id": "5940114d",
144+
"metadata": {
145+
"cq.autogen": "GF2PolyAddK.call_graph.md"
146+
},
147+
"source": [
148+
"### Call Graph"
149+
]
150+
},
151+
{
152+
"cell_type": "code",
153+
"execution_count": null,
154+
"id": "dffd364c",
155+
"metadata": {
156+
"cq.autogen": "GF2PolyAddK.call_graph.py"
157+
},
158+
"outputs": [],
159+
"source": [
160+
"from qualtran.resource_counting.generalizers import ignore_split_join\n",
161+
"gf2_poly_4_8_add_k_g, gf2_poly_4_8_add_k_sigma = gf2_poly_4_8_add_k.call_graph(max_depth=1, generalizer=ignore_split_join)\n",
162+
"show_call_graph(gf2_poly_4_8_add_k_g)\n",
163+
"show_counts_sigma(gf2_poly_4_8_add_k_sigma)"
164+
]
165+
}
166+
],
167+
"metadata": {
168+
"kernelspec": {
169+
"display_name": "Python 3",
170+
"language": "python",
171+
"name": "python3"
172+
},
173+
"language_info": {
174+
"name": "python"
175+
}
176+
},
177+
"nbformat": 4,
178+
"nbformat_minor": 5
179+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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+
import galois
19+
20+
from qualtran import (
21+
Bloq,
22+
bloq_example,
23+
BloqDocSpec,
24+
DecomposeTypeError,
25+
QGFPoly,
26+
Register,
27+
Signature,
28+
)
29+
from qualtran.bloqs.gf_arithmetic import GF2AddK
30+
from qualtran.bloqs.gf_poly_arithmetic.gf_poly_split_and_join import GFPolyJoin, GFPolySplit
31+
from qualtran.symbolics import is_symbolic
32+
33+
if TYPE_CHECKING:
34+
from qualtran import BloqBuilder, Soquet
35+
from qualtran.resource_counting import BloqCountDictT, BloqCountT, SympySymbolAllocator
36+
from qualtran.simulation.classical_sim import ClassicalValT
37+
38+
39+
@attrs.frozen
40+
class GF2PolyAddK(Bloq):
41+
r"""In place addition of a constant polynomial defined over GF($2^m$).
42+
43+
The bloq implements in place addition of a classical constant polynomial $g(x)$ and
44+
a quantum register $|f(x)\rangle$ storing coefficients of a degree-n polynomial defined
45+
over GF($2^m$). Addition in GF($2^m$) simply reduces to a component wise XOR, which can
46+
be implemented via X gates.
47+
48+
$$
49+
|f(x)\rangle \rightarrow |f(x) + g(x)\rangle
50+
$$
51+
52+
Args:
53+
qgf_poly: An instance of `QGFPoly` type that defines the data type for quantum
54+
register $|f(x)\rangle$ storing coefficients of a degree-n polynomial defined
55+
over GF($2^m$).
56+
g_x: An instance of `galois.Poly` that specifies that constant polynomial g(x)
57+
defined over GF($2^m$) that should be added to the input register f(x).
58+
59+
Registers:
60+
f_x: Input THRU register that stores coefficients of polynomial defined over $GF(2^m)$.
61+
"""
62+
63+
qgf_poly: QGFPoly
64+
g_x: galois.Poly = attrs.field()
65+
66+
@cached_property
67+
def signature(self) -> 'Signature':
68+
return Signature([Register('f_x', dtype=self.qgf_poly)])
69+
70+
@g_x.validator
71+
def _validate_g_x(self, attribute, value):
72+
if not is_symbolic(self.qgf_poly.degree):
73+
if value.degree > self.qgf_poly.degree:
74+
raise ValueError(f"Degree of constant polynomial must be <= {self.qgf_poly.degree}")
75+
if not is_symbolic(self.qgf_poly.degree, self.qgf_poly.qgf):
76+
if not value.field is self.qgf_poly.qgf.gf_type:
77+
raise ValueError(
78+
f"Constant polynomial must be defined over galois field {self.qgf_poly.qgf.gf_type}"
79+
)
80+
81+
def is_symbolic(self):
82+
return is_symbolic(self.qgf_poly.degree)
83+
84+
def build_composite_bloq(self, bb: 'BloqBuilder', *, f_x: 'Soquet') -> Dict[str, 'Soquet']:
85+
if self.is_symbolic():
86+
raise DecomposeTypeError(f"Cannot decompose symbolic {self}")
87+
f_x = bb.add(GFPolySplit(self.qgf_poly), reg=f_x)
88+
g_x = self.qgf_poly.to_gf_coefficients(self.g_x)
89+
for i in range(self.qgf_poly.degree + 1):
90+
f_x[i] = bb.add(GF2AddK(self.qgf_poly.qgf.bitsize, int(g_x[i])), x=f_x[i])
91+
92+
f_x = bb.add(GFPolyJoin(self.qgf_poly), reg=f_x)
93+
return {'f_x': f_x}
94+
95+
def build_call_graph(
96+
self, ssa: 'SympySymbolAllocator'
97+
) -> Union['BloqCountDictT', Set['BloqCountT']]:
98+
if self.is_symbolic():
99+
k = ssa.new_symbol('g_x')
100+
return {GF2AddK(self.qgf_poly.qgf.bitsize, k): self.qgf_poly.degree + 1}
101+
return super().build_call_graph(ssa)
102+
103+
def on_classical_vals(self, *, f_x) -> Dict[str, 'ClassicalValT']:
104+
return {'f_x': f_x + self.g_x}
105+
106+
107+
@bloq_example
108+
def _gf2_poly_4_8_add_k() -> GF2PolyAddK:
109+
from galois import Poly
110+
111+
from qualtran import QGF, QGFPoly
112+
113+
qgf_poly = QGFPoly(4, QGF(2, 3))
114+
g_x = Poly(qgf_poly.qgf.gf_type([1, 2, 3, 4, 5]))
115+
gf2_poly_4_8_add_k = GF2PolyAddK(qgf_poly, g_x)
116+
return gf2_poly_4_8_add_k
117+
118+
119+
@bloq_example
120+
def _gf2_poly_add_k_symbolic() -> GF2PolyAddK:
121+
import sympy
122+
from galois import Poly
123+
124+
from qualtran import QGF, QGFPoly
125+
126+
n, m = sympy.symbols('n, m', positive=True, integers=True)
127+
qgf_poly = QGFPoly(n, QGF(2, m))
128+
gf2_poly_add_k_symbolic = GF2PolyAddK(qgf_poly, Poly([0, 0, 0, 0]))
129+
return gf2_poly_add_k_symbolic
130+
131+
132+
_GF2_POLY_ADD_K_DOC = BloqDocSpec(
133+
bloq_cls=GF2PolyAddK, examples=(_gf2_poly_4_8_add_k, _gf2_poly_add_k_symbolic)
134+
)

0 commit comments

Comments
 (0)