Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bloq for constant polynomial multiplication modulu in GF(2) #1516

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
5 changes: 4 additions & 1 deletion dev_tools/qualtran_dev_tools/notebook_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,10 @@
NotebookSpecV2(
title='GF($2^m$) Multiplication',
module=qualtran.bloqs.gf_arithmetic.gf2_multiplication,
bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_multiplication._GF2_MULTIPLICATION_DOC],
bloq_specs=[
qualtran.bloqs.gf_arithmetic.gf2_multiplication._GF2_MULTIPLICATION_DOC,
qualtran.bloqs.gf_arithmetic.gf2_multiplication._MULTIPLY_BY_CONSTANT_MOD_DOC,
],
),
NotebookSpecV2(
title='GF($2^m$) Addition',
Expand Down
5 changes: 4 additions & 1 deletion qualtran/bloqs/gf_arithmetic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@
from qualtran.bloqs.gf_arithmetic.gf2_add_k import GF2AddK
from qualtran.bloqs.gf_arithmetic.gf2_addition import GF2Addition
from qualtran.bloqs.gf_arithmetic.gf2_inverse import GF2Inverse
from qualtran.bloqs.gf_arithmetic.gf2_multiplication import GF2Multiplication
from qualtran.bloqs.gf_arithmetic.gf2_multiplication import (
GF2Multiplication,
MultiplyPolyByConstantMod,
)
from qualtran.bloqs.gf_arithmetic.gf2_square import GF2Square
128 changes: 117 additions & 11 deletions qualtran/bloqs/gf_arithmetic/gf2_multiplication.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "markdown",
"id": "87c95c4a",
"id": "f2e9ace3",
"metadata": {
"cq.autogen": "title_cell"
},
Expand All @@ -13,7 +13,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "31c1f087",
"id": "84523966",
"metadata": {
"cq.autogen": "top_imports"
},
Expand All @@ -30,7 +30,7 @@
},
{
"cell_type": "markdown",
"id": "307679ec",
"id": "d77b58bc",
"metadata": {
"cq.autogen": "GF2Multiplication.bloq_doc.md"
},
Expand Down Expand Up @@ -72,7 +72,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "872a44d1",
"id": "40013f0c",
"metadata": {
"cq.autogen": "GF2Multiplication.bloq_doc.py"
},
Expand All @@ -83,7 +83,7 @@
},
{
"cell_type": "markdown",
"id": "d0f0db7d",
"id": "c5bcc7f0",
"metadata": {
"cq.autogen": "GF2Multiplication.example_instances.md"
},
Expand All @@ -94,7 +94,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "131bc962",
"id": "75114297",
"metadata": {
"cq.autogen": "GF2Multiplication.gf16_multiplication"
},
Expand All @@ -106,7 +106,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "69f564d8",
"id": "7bdc2588",
"metadata": {
"cq.autogen": "GF2Multiplication.gf2_multiplication_symbolic"
},
Expand All @@ -120,7 +120,7 @@
},
{
"cell_type": "markdown",
"id": "2a62c2b8",
"id": "9866335a",
"metadata": {
"cq.autogen": "GF2Multiplication.graphical_signature.md"
},
Expand All @@ -131,7 +131,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "cf003e98",
"id": "f09a13f8",
"metadata": {
"cq.autogen": "GF2Multiplication.graphical_signature.py"
},
Expand All @@ -144,7 +144,7 @@
},
{
"cell_type": "markdown",
"id": "f14ef0c5",
"id": "b26814e0",
"metadata": {
"cq.autogen": "GF2Multiplication.call_graph.md"
},
Expand All @@ -155,7 +155,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "f4b7bf2c",
"id": "7219fea5",
"metadata": {
"cq.autogen": "GF2Multiplication.call_graph.py"
},
Expand All @@ -166,6 +166,112 @@
"show_call_graph(gf16_multiplication_g)\n",
"show_counts_sigma(gf16_multiplication_sigma)"
]
},
{
"cell_type": "markdown",
"id": "a9729be2",
"metadata": {
"cq.autogen": "MultiplyPolyByConstantMod.bloq_doc.md"
},
"source": [
"## `MultiplyPolyByConstantMod`\n",
"Multiply a polynomial by $f(x)$ modulu $m(x)$. Both $f(x)$ and $m(x)$ are constants.\n",
"\n",
"#### Parameters\n",
" - `f_x`: The polynomial to mulitply with, given either a galois.Poly or as a sequence degrees.\n",
" - `m_x`: The modulus polynomial, given either a galois.Poly or as a sequence degrees. \n",
"\n",
"#### Registers\n",
" - `g`: The polynomial coefficients (in GF(2)). \n",
"\n",
"#### References\n",
" - [Space-efficient quantum multiplication of polynomials for binary finite fields with sub-quadratic Toffoli gate count](https://arxiv.org/abs/1910.02849v2). Algorithm 1\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a25c2e49",
"metadata": {
"cq.autogen": "MultiplyPolyByConstantMod.bloq_doc.py"
},
"outputs": [],
"source": [
"from qualtran.bloqs.gf_arithmetic import MultiplyPolyByConstantMod"
]
},
{
"cell_type": "markdown",
"id": "3690dcdb",
"metadata": {
"cq.autogen": "MultiplyPolyByConstantMod.example_instances.md"
},
"source": [
"### Example Instances"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dffa638f",
"metadata": {
"cq.autogen": "MultiplyPolyByConstantMod.gf2_multiply_by_constant_modulu"
},
"outputs": [],
"source": [
"fx = [2, 0] # x^2 + 1\n",
"mx = [0, 1, 3] # x^3 + x + 1\n",
"gf2_multiply_by_constant_modulu = MultiplyPolyByConstantMod(fx, mx)"
]
},
{
"cell_type": "markdown",
"id": "e140ad12",
"metadata": {
"cq.autogen": "MultiplyPolyByConstantMod.graphical_signature.md"
},
"source": [
"#### Graphical Signature"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "501a0781",
"metadata": {
"cq.autogen": "MultiplyPolyByConstantMod.graphical_signature.py"
},
"outputs": [],
"source": [
"from qualtran.drawing import show_bloqs\n",
"show_bloqs([gf2_multiply_by_constant_modulu],\n",
" ['`gf2_multiply_by_constant_modulu`'])"
]
},
{
"cell_type": "markdown",
"id": "292b96b9",
"metadata": {
"cq.autogen": "MultiplyPolyByConstantMod.call_graph.md"
},
"source": [
"### Call Graph"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fad6337a",
"metadata": {
"cq.autogen": "MultiplyPolyByConstantMod.call_graph.py"
},
"outputs": [],
"source": [
"from qualtran.resource_counting.generalizers import ignore_split_join\n",
"gf2_multiply_by_constant_modulu_g, gf2_multiply_by_constant_modulu_sigma = gf2_multiply_by_constant_modulu.call_graph(max_depth=1, generalizer=ignore_split_join)\n",
"show_call_graph(gf2_multiply_by_constant_modulu_g)\n",
"show_counts_sigma(gf2_multiply_by_constant_modulu_sigma)"
]
}
],
"metadata": {
Expand Down
106 changes: 105 additions & 1 deletion qualtran/bloqs/gf_arithmetic/gf2_multiplication.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from qualtran.symbolics import ceil, is_symbolic, log2, Shaped, SymbolicInt

if TYPE_CHECKING:
from qualtran import BloqBuilder, Soquet
from qualtran import BloqBuilder, Soquet, SoquetT
from qualtran.resource_counting import BloqCountDictT, BloqCountT, SympySymbolAllocator
from qualtran.simulation.classical_sim import ClassicalValT

Expand Down Expand Up @@ -247,3 +247,107 @@ def _gf2_multiplication_symbolic() -> GF2Multiplication:
_GF2_MULTIPLICATION_DOC = BloqDocSpec(
bloq_cls=GF2Multiplication, examples=(_gf16_multiplication, _gf2_multiplication_symbolic)
)


@attrs.frozen
class MultiplyPolyByConstantMod(Bloq):
r"""Multiply a polynomial by $f(x)$ modulu $m(x)$. Both $f(x)$ and $m(x)$ are constants.

Args:
f_x: The polynomial to mulitply with, given either a galois.Poly or as
a sequence degrees.
m_x: The modulus polynomial, given either a galois.Poly or as
a sequence degrees.

Registers:
g: The polynomial coefficients (in GF(2)).

References:
[Space-efficient quantum multiplication of polynomials for binary finite fields with
sub-quadratic Toffoli gate count](https://arxiv.org/abs/1910.02849v2) Algorithm 1
"""

f_x: Poly = attrs.field(converter=lambda x: x if isinstance(x, Poly) else Poly.Degrees(x))
m_x: Poly = attrs.field(converter=lambda x: x if isinstance(x, Poly) else Poly.Degrees(x))

def __attrs_post_init__(self):
assert self.m_x.is_irreducible()
assert self.f_x.degrees.max() < self.m_x.degrees.max()

@cached_property
def n(self):
return self.m_x.degrees.max()

@cached_property
def lup(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Returns the LUP decomposition of the matrix representing the operation.

If m_x is irreducible, then the operation y := (y*f_x)%m_x can be represented
by a full rank matrix that can be decomposed into PLU where L and U are lower
and upper traingular matricies and P is a permutation matrix.
"""
n = self.n
matrix = np.zeros((n, n), dtype=int)
for i in range(n):
p = (self.f_x * Poly.Degrees([i])) % self.m_x
for j in p.nonzero_degrees:
matrix[j, i] = 1
P, L, U = GF(2)(matrix).plu_decompose()
return np.asarray(L, dtype=int), np.asarray(U, dtype=int), np.asarray(P, dtype=int)

@cached_property
def signature(self) -> 'Signature':
return Signature([Register('g', QBit(), shape=(self.n,))])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tanujkhattar I want to have an $n-1$ degree polynomial in GF(2) instead of a number in GF($2^n$). is there a way to do that? or do we want to use GF($2^n$) for consistancy?


def on_classical_vals(self, g) -> Dict[str, 'ClassicalValT']:
p = (Poly(g[::-1], GF(2)) * self.f_x) % self.m_x
res = p.coefficients().tolist()
res = [0 for _ in range(self.n - len(res))] + res
res = res[::-1]
return {'g': res}

def build_composite_bloq(self, bb: 'BloqBuilder', g: 'SoquetT') -> Dict[str, 'SoquetT']:
L, U, P = self.lup
if is_symbolic(self.n):
raise DecomposeTypeError(f"Symbolic decomposition isn't supported for {self}")
assert isinstance(g, np.ndarray)
for i in range(self.n):
for j in range(i + 1, self.n):
if U[i, j]:
g[j], g[i] = bb.add(CNOT(), ctrl=g[j], target=g[i])

for i in reversed(range(self.n)):
for j in reversed(range(i)):
if L[i, j]:
g[j], g[i] = bb.add(CNOT(), ctrl=g[j], target=g[i])

column = [*range(self.n)]
for i in range(self.n):
for j in range(i + 1, self.n):
if P[i, column[j]]:
g[i], g[j] = g[j], g[i]
column[i], column[j] = column[j], column[i]
return {'g': g}

def build_call_graph(
self, ssa: 'SympySymbolAllocator'
) -> Union['BloqCountDictT', Set['BloqCountT']]:
L, U, _ = self.lup
# The number of cnots is the number of non zero off-diagnoal entries in L and U.
cnots = np.sum(L) + np.sum(U) - 2 * self.n
if cnots:
return {CNOT(): cnots}
return {}


@bloq_example
def _gf2_multiply_by_constant_modulu() -> MultiplyPolyByConstantMod:
fx = [2, 0] # x^2 + 1
mx = [0, 1, 3] # x^3 + x + 1
gf2_multiply_by_constant_modulu = MultiplyPolyByConstantMod(fx, mx)
return gf2_multiply_by_constant_modulu


_MULTIPLY_BY_CONSTANT_MOD_DOC = BloqDocSpec(
bloq_cls=MultiplyPolyByConstantMod, examples=(_gf2_multiply_by_constant_modulu,)
)
Loading
Loading