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 irreducible polynomials for GF(2^m) (2<=m<=10_000) #462

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ dev = [
"pytest-cov[toml]",
"pytest-xdist",
"pytest-benchmark >= 4.0.0",
"requests",
"pdfminer.six"
]

[project.urls]
Expand Down
114 changes: 114 additions & 0 deletions scripts/create_irreducible_polys_database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""
A script to create a database of irreducible polynomials

Sources:
- Gadiel Seroussi. Table of Low-Weight Binary Irreducible Polynomials (1998): https://www.hpl.hp.com/techreports/98/HPL-98-135.html

"""
from __future__ import annotations

import os
import sqlite3
from pathlib import Path

import requests
import hashlib
import io
from pdfminer.high_level import extract_text


def main():
"""
The main routine to create a database of irreducible polynomials
"""

database_file = Path(__file__).parent.parent / "src" / "galois" / "_databases" / "irreducible_polys.db"
conn, cursor = create_database(database_file)

_add_hpl_1998(conn, cursor)
mhostetter marked this conversation as resolved.
Show resolved Hide resolved

conn.close()


def create_database(file: Path) -> tuple[sqlite3.Connection, sqlite3.Cursor]:
"""
Deletes the old database, makes a new one, and returns the database connection.
"""
if file.exists():
os.remove(file)

conn = sqlite3.connect(file)
cursor = conn.cursor()
create_table(conn, cursor)

return conn, cursor


def create_table(conn: sqlite3.Connection, cursor: sqlite3.Cursor):
"""
Creates an empty 'polys' table.
"""
cursor.execute(
"""
CREATE TABLE polys (
characteristic INTEGER NOT NULL,
degree INTEGER NOT NULL,
nonzero_degrees TEXT NOT NULL,
nonzero_coeffs TEXT NOT NULL,
PRIMARY KEY (characteristic, degree)
)
"""
)
conn.commit()


def add_to_database(
cursor: sqlite3.Cursor, characteristic: int, degree: int, nonzero_degrees: str, nonzero_coeffs: str
):
"""
Adds the given irreducible polynomial to the database.
"""
cursor.execute(
"""
INSERT INTO polys (characteristic, degree, nonzero_degrees, nonzero_coeffs)
VALUES (?,?,?,?)
""",
(characteristic, degree, nonzero_degrees, nonzero_coeffs),
)


def _add_hpl_1998(conn, cursor):
"""
Add Gadiel Seroussi's table to the database.
GF(2^m) for 2 <= m <= 10_000
"""
url = "https://www.hpl.hp.com/techreports/98/HPL-98-135.pdf"
# There is an issue with the SSL certificate using CURL_CA_BUNDLE
# We don't validate https, but we do check the PDF's checksum
pdf = requests.get(url, stream=True, verify=False).content
sha256 = hashlib.sha256()
sha256.update(pdf)
assert sha256.hexdigest() == "78f02d84a0957ad261c53a0d1107adb2ff9d72f52ba5e10ea77eaa8cf766a0ee"
mhostetter marked this conversation as resolved.
Show resolved Hide resolved

coefficients = []
print("Parsing Table of Low-Weight Binary Irreducible Polynomials (1998)...")
for page in range(3, 16):
text = extract_text(io.BytesIO(pdf), page_numbers=[page]) # extract_text doesn't accept Bytes as input
# Tabs are parsed as \n\n, except when the irreducible poly is a pentanomial.
# In that case, there is only a space. First replace takes care of that.
# Second replace unifies tabs and changes of lines.
# Every page ends with the page number and the form feed \x0c, hence the [:-2].
coefficients += text.replace(" ", "\n").replace("\n\n", "\n").split("\n")[:-2]

for coeffs in coefficients:
degree = coeffs.split(",")[0]
nonzero_degrees = coeffs + ",0"
nonzero_coeffs = ("1," * len(nonzero_degrees.split(",")))[:-1]
print(f"Irreducible polynomial for GF(2^{degree})")
add_to_database(cursor, 2, degree, nonzero_degrees, nonzero_coeffs)

conn.commit()


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions src/galois/_databases/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
"""
from ._conway import ConwayPolyDatabase
from ._prime import PrimeFactorsDatabase
from ._irreducible import IrreduciblePolyDatabase
46 changes: 46 additions & 0 deletions src/galois/_databases/_irreducible.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
A module that handles accessing the database of irreducible polynomials.
"""
import os
import sqlite3

DATABASE = None # Database singleton class
DATABASE_FILE = os.path.join(os.path.dirname(__file__), "irreducible_polys.db")


class IrreduciblePolyDatabase:
"""
Class to interface with the irreducible polynomials database.
"""

def __new__(cls):
global DATABASE
if DATABASE is None:
DATABASE = super().__new__(cls)
return DATABASE

def __init__(self):
self.conn = sqlite3.connect(DATABASE_FILE)
self.cursor = self.conn.cursor()

def fetch(self, characteristic: int, degree: int):
self.cursor.execute(
"""
SELECT nonzero_degrees,nonzero_coeffs
FROM polys
WHERE characteristic=? AND degree=?""",
(characteristic, degree),
)
result = self.cursor.fetchone()

if result is None:
raise LookupError(
f"The irreducible polynomials database does not contain an entry for GF({characteristic}^{degree}).\n\n"
"Alternatively, you can construct irreducible polynomials with `galois.irreducible_poly(p, m)` "
"or primitive polynomials with `galois.primitive_poly(p, m)`."
)

nonzero_degrees = [int(_) for _ in result[0].split(",")]
nonzero_coeffs = [int(_) for _ in result[1].split(",")]

return nonzero_degrees, nonzero_coeffs
Binary file added src/galois/_databases/irreducible_polys.db
Binary file not shown.
12 changes: 12 additions & 0 deletions src/galois/_polys/_irreducible.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from typing_extensions import Literal

from .._databases import IrreduciblePolyDatabase
from .._domains import _factory
from .._helper import export, method_of, verify_isinstance
from .._prime import factors, is_prime_power
Expand Down Expand Up @@ -128,6 +129,7 @@ def irreducible_poly(
degree: int,
terms: int | str | None = None,
method: Literal["min", "max", "random"] = "min",
use_database: bool = True,
) -> Poly:
r"""
Returns a monic irreducible polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` with degree :math:`m`.
Expand Down Expand Up @@ -215,6 +217,16 @@ def irreducible_poly(
if not method in ["min", "max", "random"]:
raise ValueError(f"Argument 'method' must be in ['min', 'max', 'random'], not {method!r}.")

if terms == "min" and method == "min" and use_database:
try:
db = IrreduciblePolyDatabase()
degrees, coeffs = db.fetch(order, degree)
field = _factory.FIELD_FACTORY(order)
poly = Poly.Degrees(degrees, coeffs, field=field)
return poly
except LookupError:
pass

try:
if method == "min":
return next(irreducible_polys(order, degree, terms))
Expand Down
15 changes: 15 additions & 0 deletions tests/polys/luts/irreducible_polys_database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
A module containing LUTs for irreducible polynomials with min terms from irreducible_polys.db
"""

# LUT items are poly nonzero degrees and coefficients in degree-descending order

# Gadiel Seroussi's table (1998)
# LUT items obtained by randomly picking degrees and checking the PDF
# sorted(numpy.random.default_rng(1337).integers(size=5, low=500, high=10_000, endpoint=True))

IRREDUCIBLE_POLY_MIN_TERMS_2_2262 = [[2262, 57, 0], [1, 1, 1]]
IRREDUCIBLE_POLY_MIN_TERMS_2_5632 = [[5632, 17, 15, 5, 0], [1, 1, 1, 1, 1]]
IRREDUCIBLE_POLY_MIN_TERMS_2_5690 = [[5690, 1623, 0], [1, 1, 1]]
IRREDUCIBLE_POLY_MIN_TERMS_2_7407 = [[7407, 27, 21, 17, 0], [1, 1, 1, 1, 1]]
IRREDUCIBLE_POLY_MIN_TERMS_2_8842 = [[8842, 4143, 0], [1, 1, 1]]
29 changes: 29 additions & 0 deletions tests/polys/test_irreducible_polys.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
A pytest module to test generating irreducible polynomials over finite fields.
"""
import time
import numpy as np
import pytest

Expand Down Expand Up @@ -42,6 +43,14 @@
)
from .luts.irreducible_polys_25 import IRREDUCIBLE_POLYS_25_1, IRREDUCIBLE_POLYS_25_2

from .luts.irreducible_polys_database import (
IRREDUCIBLE_POLY_MIN_TERMS_2_2262,
IRREDUCIBLE_POLY_MIN_TERMS_2_5632,
IRREDUCIBLE_POLY_MIN_TERMS_2_5690,
IRREDUCIBLE_POLY_MIN_TERMS_2_7407,
IRREDUCIBLE_POLY_MIN_TERMS_2_8842,
)

PARAMS = [
(2, 1, IRREDUCIBLE_POLYS_2_1),
(2, 2, IRREDUCIBLE_POLYS_2_2),
Expand Down Expand Up @@ -71,6 +80,14 @@
(5**2, 2, IRREDUCIBLE_POLYS_25_2),
]

PARAMS_DB = [
(2, 2262, IRREDUCIBLE_POLY_MIN_TERMS_2_2262),
(2, 5632, IRREDUCIBLE_POLY_MIN_TERMS_2_5632),
(2, 5690, IRREDUCIBLE_POLY_MIN_TERMS_2_5690),
(2, 7407, IRREDUCIBLE_POLY_MIN_TERMS_2_7407),
(2, 8842, IRREDUCIBLE_POLY_MIN_TERMS_2_8842),
]


def test_irreducible_poly_exceptions():
with pytest.raises(TypeError):
Expand Down Expand Up @@ -160,6 +177,18 @@ def test_minimum_terms(order, degree, polys):
assert f.coeffs.tolist() in min_term_polys


@pytest.mark.parametrize("order,degree,polys", PARAMS_DB)
def test_minimum_terms_from_database(order, degree, polys):
tick = time.time()
p = galois.irreducible_poly(order, degree, terms="min")
tock = time.time()
assert tock - tick < 1.0
db_degrees = p.nonzero_degrees.tolist()
db_coeffs = p.nonzero_coeffs.tolist()
exp_degrees, exp_coeffs = polys
assert db_degrees == exp_degrees and db_coeffs == exp_coeffs


def test_large_degree():
"""
See https://github.com/mhostetter/galois/issues/360.
Expand Down