Skip to content

Commit

Permalink
Add GF2Inverse bloq to compute inverse over GF(2^m)
Browse files Browse the repository at this point in the history
  • Loading branch information
tanujkhattar committed Oct 6, 2024
1 parent 2e8cc39 commit 8c58ae2
Show file tree
Hide file tree
Showing 8 changed files with 398 additions and 1 deletion.
6 changes: 6 additions & 0 deletions dev_tools/autogenerate-bloqs-notebooks-v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
import qualtran.bloqs.factoring.ecc
import qualtran.bloqs.factoring.mod_exp
import qualtran.bloqs.gf_arithmetic.gf2_addition
import qualtran.bloqs.gf_arithmetic.gf2_inverse
import qualtran.bloqs.gf_arithmetic.gf2_multiplication
import qualtran.bloqs.gf_arithmetic.gf2_square
import qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp
Expand Down Expand Up @@ -573,6 +574,11 @@
module=qualtran.bloqs.gf_arithmetic.gf2_square,
bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_square._GF2_SQUARE_DOC],
),
NotebookSpecV2(
title='GF($2^m$) Inverse',
module=qualtran.bloqs.gf_arithmetic.gf2_inverse,
bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_inverse._GF2_INVERSE_DOC],
),
]


Expand Down
1 change: 1 addition & 0 deletions docs/bloqs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ Bloqs Library
gf_arithmetic/gf2_multiplication.ipynb
gf_arithmetic/gf2_addition.ipynb
gf_arithmetic/gf2_square.ipynb
gf_arithmetic/gf2_inverse.ipynb

.. toctree::
:maxdepth: 2
Expand Down
1 change: 1 addition & 0 deletions qualtran/bloqs/gf_arithmetic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
# limitations under the License.

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_square import GF2Square
182 changes: 182 additions & 0 deletions qualtran/bloqs/gf_arithmetic/gf2_inverse.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "25463ba5",
"metadata": {
"cq.autogen": "title_cell"
},
"source": [
"# GF($2^m$) Inverse"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "38263a0a",
"metadata": {
"cq.autogen": "top_imports"
},
"outputs": [],
"source": [
"from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n",
"from qualtran import QBit, QInt, QUInt, QAny\n",
"from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n",
"from typing import *\n",
"import numpy as np\n",
"import sympy\n",
"import cirq"
]
},
{
"cell_type": "markdown",
"id": "9054cbbb",
"metadata": {
"cq.autogen": "GF2Inverse.bloq_doc.md"
},
"source": [
"## `GF2Inverse`\n",
"Out of place inversion for elements in GF($2^m$)\n",
"\n",
"Given a quantum register storing elements from GF($2^m$), this bloq computes the inverse\n",
"of the given element in a new output register, out-of-place. Specifically,\n",
"it implements the transformation\n",
"\n",
"$$\n",
" |a\\rangle |0\\rangle \\rightarrow |a\\rangle |a^{-1}\\rangle\n",
"$$\n",
"\n",
"Inverse is computed by using Fermat's little theorem for Finite Fields, which states that\n",
"for a finite field $\\mathbb{F}$ with $m$ elements, $\\forall a \\in \\mathbb{F}$\n",
"$$\n",
" a^{m} = a\n",
"$$\n",
"\n",
"When the finite field is GF($2^m$), Fermat's little theorem can be used to obtain the relation\n",
"\n",
"$$\n",
" a^{-1} = a^{2^m - 2}\n",
"$$\n",
"\n",
"Thus, the inverse can be obtained via $m - 1$ squaring and multiplication operations.\n",
"\n",
"#### Parameters\n",
" - `bitsize`: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of qubits in the input register whose inverse should be calculated. \n",
"\n",
"#### Registers\n",
" - `x`: Input THRU register of size $m$ that stores elements from $GF(2^m)$.\n",
" - `result`: Output RIGHT register of size $m$ that stores $x^{-1}$ from $GF(2^m)$.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3a43c81",
"metadata": {
"cq.autogen": "GF2Inverse.bloq_doc.py"
},
"outputs": [],
"source": [
"from qualtran.bloqs.gf_arithmetic import GF2Inverse"
]
},
{
"cell_type": "markdown",
"id": "b0bf14cc",
"metadata": {
"cq.autogen": "GF2Inverse.example_instances.md"
},
"source": [
"### Example Instances"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f6ce3222",
"metadata": {
"cq.autogen": "GF2Inverse.gf16_inverse"
},
"outputs": [],
"source": [
"gf16_inverse = GF2Inverse(4)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6c26f44f",
"metadata": {
"cq.autogen": "GF2Inverse.gf2_inverse_symbolic"
},
"outputs": [],
"source": [
"import sympy\n",
"\n",
"m = sympy.Symbol('m')\n",
"gf2_inverse_symbolic = GF2Inverse(m)"
]
},
{
"cell_type": "markdown",
"id": "c813ddef",
"metadata": {
"cq.autogen": "GF2Inverse.graphical_signature.md"
},
"source": [
"#### Graphical Signature"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "591221ff",
"metadata": {
"cq.autogen": "GF2Inverse.graphical_signature.py"
},
"outputs": [],
"source": [
"from qualtran.drawing import show_bloqs\n",
"show_bloqs([gf16_inverse, gf2_inverse_symbolic],\n",
" ['`gf16_inverse`', '`gf2_inverse_symbolic`'])"
]
},
{
"cell_type": "markdown",
"id": "c96fb97d",
"metadata": {
"cq.autogen": "GF2Inverse.call_graph.md"
},
"source": [
"### Call Graph"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "67aeeb6c",
"metadata": {
"cq.autogen": "GF2Inverse.call_graph.py"
},
"outputs": [],
"source": [
"from qualtran.resource_counting.generalizers import ignore_split_join\n",
"gf16_inverse_g, gf16_inverse_sigma = gf16_inverse.call_graph(max_depth=1, generalizer=ignore_split_join)\n",
"show_call_graph(gf16_inverse_g)\n",
"show_counts_sigma(gf16_inverse_sigma)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
157 changes: 157 additions & 0 deletions qualtran/bloqs/gf_arithmetic/gf2_inverse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from functools import cached_property
from typing import Dict, Set, TYPE_CHECKING, Union

import attrs
import numpy as np

from qualtran import (
Bloq,
bloq_example,
BloqDocSpec,
DecomposeTypeError,
QGF,
Register,
Side,
Signature,
)
from qualtran.bloqs.gf_arithmetic.gf2_addition import GF2Addition
from qualtran.bloqs.gf_arithmetic.gf2_multiplication import GF2Multiplication
from qualtran.bloqs.gf_arithmetic.gf2_square import GF2Square
from qualtran.symbolics import is_symbolic, SymbolicInt

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


@attrs.frozen
class GF2Inverse(Bloq):
r"""Out of place inversion for elements in GF($2^m$)
Given a quantum register storing elements from GF($2^m$), this bloq computes the inverse
of the given element in a new output register, out-of-place. Specifically,
it implements the transformation
$$
|a\rangle |0\rangle \rightarrow |a\rangle |a^{-1}\rangle
$$
Inverse is computed by using Fermat's little theorem for Finite Fields, which states that
for a finite field $\mathbb{F}$ with $m$ elements, $\forall a \in \mathbb{F}$
$$
a^{m} = a
$$
When the finite field is GF($2^m$), Fermat's little theorem can be used to obtain the relation
$$
a^{-1} = a^{2^m - 2}
$$
Thus, the inverse can be obtained via $m - 1$ squaring and multiplication operations.
Args:
bitsize: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of
qubits in the input register whose inverse should be calculated.
Registers:
x: Input THRU register of size $m$ that stores elements from $GF(2^m)$.
result: Output RIGHT register of size $m$ that stores $x^{-1}$ from $GF(2^m)$.
"""

bitsize: SymbolicInt

@cached_property
def signature(self) -> 'Signature':
junk_reg = (
[Register('junk', dtype=self.qgf, shape=(self.bitsize - 2,), side=Side.RIGHT)]
if is_symbolic(self.bitsize) or self.bitsize > 2
else []
)
return Signature(
[
Register('x', dtype=self.qgf),
Register('result', dtype=self.qgf, side=Side.RIGHT),
*junk_reg,
]
)

@cached_property
def qgf(self) -> QGF:
return QGF(characteristic=2, degree=self.bitsize)

def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'Soquet') -> Dict[str, 'Soquet']:
if is_symbolic(self.bitsize):
raise DecomposeTypeError(f"Cannot decompose symbolic {self}")
result = bb.allocate(dtype=self.qgf)
if self.bitsize == 1:
x, result = bb.add(GF2Addition(self.bitsize), x=x, y=result)
return {'x': x, 'result': result}

x = bb.add(GF2Square(self.bitsize), x=x)
x, result = bb.add(GF2Addition(self.bitsize), x=x, y=result)

junk = []
for i in range(2, self.bitsize):
x = bb.add(GF2Square(self.bitsize), x=x)
x, result, new_result = bb.add(GF2Multiplication(self.bitsize), x=x, y=result)
junk.append(result)
result = new_result
x = bb.add(GF2Square(self.bitsize), x=x)
return {'x': x, 'result': result} | ({'junk': np.array(junk)} if junk else {})

def build_call_graph(
self, ssa: 'SympySymbolAllocator'
) -> Union['BloqCountDictT', Set['BloqCountT']]:
if is_symbolic(self.bitsize) or self.bitsize > 2:
return {
GF2Addition(self.bitsize): 1,
GF2Square(self.bitsize): self.bitsize - 1,
GF2Multiplication(self.bitsize): self.bitsize - 2,
}
return {GF2Addition(self.bitsize): 1} | (
{GF2Square(self.bitsize): 1} if self.bitsize == 2 else {}
)

def on_classical_vals(self, *, x) -> Dict[str, 'ClassicalValT']:
assert isinstance(x, self.qgf.gf_type)
x_temp = x**2
result = x_temp
junk = []
for i in range(2, int(self.bitsize)):
junk.append(result)
x_temp = x_temp * x_temp
result = result * x_temp
return {'x': x, 'result': x ** (-1), 'junk': np.array(junk)}


@bloq_example
def _gf16_inverse() -> GF2Inverse:
gf16_inverse = GF2Inverse(4)
return gf16_inverse


@bloq_example
def _gf2_inverse_symbolic() -> GF2Inverse:
import sympy

m = sympy.Symbol('m')
gf2_inverse_symbolic = GF2Inverse(m)
return gf2_inverse_symbolic


_GF2_INVERSE_DOC = BloqDocSpec(bloq_cls=GF2Inverse, examples=(_gf16_inverse, _gf2_inverse_symbolic))
Loading

0 comments on commit 8c58ae2

Please sign in to comment.