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 Wolfram's primitive polynomials #464

Open
wants to merge 21 commits into
base: release/0.3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
91d58e8
Fix typo in `pollard_p1()` docstring
mhostetter Dec 28, 2022
6195d39
Update copyright
mhostetter Jan 2, 2023
cadc24f
Upgrade to Sphinx Immaterial 0.11.2
mhostetter Jan 11, 2023
0fdd5e8
Add `pytest-benchmark` back to `[dev]` dependencies
mhostetter Jan 11, 2023
3839488
Use Google docstrings instead of NumPy docstrings
mhostetter Jan 11, 2023
6f8d78f
Change max line length to 120
mhostetter Jan 16, 2023
08eb3f9
Clean up docstrings
mhostetter Jan 17, 2023
5e45744
Parse Google docstring "See Also" sections as if they were NumPy docs…
mhostetter Jan 17, 2023
1af404f
Add `terms` kwarg to irreducible poly functions
mhostetter Jan 17, 2023
e2b5513
Add `terms` kwarg to primitive poly functions
mhostetter Jan 17, 2023
c34cfe3
Make search for polynomials of fixed-term much more efficient
mhostetter Jan 20, 2023
9b07c84
Add a `terms="min"` option to irreducible/primitive poly functions
mhostetter Jan 20, 2023
537ae90
Move common irreducible/primitive poly functions to `_poly.py`
mhostetter Jan 22, 2023
6191415
Make search for a random irreducible/primitive polynomial much more e…
mhostetter Jan 22, 2023
0c4386b
Move polynomial deterministic search into `_poly.py`
mhostetter Jan 22, 2023
521171d
Add Wolfram's primitive polynomials
iyanmv Jan 18, 2023
6d2a2bd
Use PrimitivePolyDatabase with terms="min"
iyanmv Jan 22, 2023
41486bf
Add unit tests
iyanmv Jan 22, 2023
06d0ab9
Replace pytest-timeout with a simple tick-tock
iyanmv Jan 22, 2023
bad87d2
Fix order when LookupError
iyanmv Jan 23, 2023
d3c6f6d
Fix case terms == "min" and method != "min"
iyanmv Jan 23, 2023
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
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}
},
"editor.rulers": [
120
]
}
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2020-2022 Matt Hostetter <[email protected]>
Copyright (c) 2020-2023 Matt Hostetter <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
43 changes: 39 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@
setattr(builtins, "__sphinx_build__", True)

import numpy
import sphinx

import galois

# -- Project information -----------------------------------------------------

project = "galois"
copyright = "2020-2022, Matt Hostetter"
copyright = "2020-2023, Matt Hostetter"
author = "Matt Hostetter"
version = galois.__version__

Expand Down Expand Up @@ -310,7 +311,11 @@ def autodoc_skip_member(app, what, name, obj, skip, options):
elif getattr(obj, "__qualname__", None) in ["FunctionMixin.dot", "Array.astype"]:
# NumPy methods that were overridden, don't include docs
return True
elif hasattr(obj, "__qualname__") and getattr(obj, "__qualname__").split(".")[0] == "FieldArray" and hasattr(numpy.ndarray, name):
elif (
hasattr(obj, "__qualname__")
and getattr(obj, "__qualname__").split(".")[0] == "FieldArray"
and hasattr(numpy.ndarray, name)
):
if name in ["__repr__", "__str__"]:
# Specifically allow these methods to be documented
return False
Expand Down Expand Up @@ -356,7 +361,9 @@ def classproperty(obj):
return ret


ArrayMeta_properties = [member for member in dir(galois.Array) if inspect.isdatadescriptor(getattr(type(galois.Array), member, None))]
ArrayMeta_properties = [
member for member in dir(galois.Array) if inspect.isdatadescriptor(getattr(type(galois.Array), member, None))
]
for p in ArrayMeta_properties:
# Fetch the class properties from the private metaclasses
ArrayMeta_property = getattr(galois._domains._meta.ArrayMeta, p)
Expand All @@ -372,7 +379,9 @@ def classproperty(obj):


FieldArrayMeta_properties = [
member for member in dir(galois.FieldArray) if inspect.isdatadescriptor(getattr(type(galois.FieldArray), member, None))
member
for member in dir(galois.FieldArray)
if inspect.isdatadescriptor(getattr(type(galois.FieldArray), member, None))
]
for p in FieldArrayMeta_properties:
# Fetch the class properties from the private metaclasses
Expand Down Expand Up @@ -413,7 +422,33 @@ def modify_type_hints(signature):
return signature


def monkey_patch_parse_see_also():
"""
Use the NumPy docstring parsing of See Also sections for convenience. This automatically
hyperlinks plaintext functions and methods.
"""
# Add the special parsing method from NumpyDocstring
method = sphinx.ext.napoleon.NumpyDocstring._parse_numpydoc_see_also_section
sphinx.ext.napoleon.GoogleDocstring._parse_numpydoc_see_also_section = method

def _parse_see_also_section(self, section: str):
"""Copied from NumpyDocstring._parse_see_also_section()."""
lines = self._consume_to_next_section()

# Added: strip whitespace from lines to satisfy _parse_numpydoc_see_also_section()
for i in range(len(lines)):

Choose a reason for hiding this comment

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

you can just do

for line in lines:
    line = line.strip()
...

lines[i] = lines[i].strip()

try:
return self._parse_numpydoc_see_also_section(lines)
except ValueError:
return self._format_admonition("seealso", lines)

sphinx.ext.napoleon.GoogleDocstring._parse_see_also_section = _parse_see_also_section


def setup(app):
monkey_patch_parse_see_also()
app.connect("autodoc-skip-member", autodoc_skip_member)
app.connect("autodoc-process-bases", autodoc_process_bases)
app.connect("autodoc-process-signature", autodoc_process_signature)
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
sphinx < 5.4
git+https://github.com/jbms/sphinx-immaterial@f9d9f5b4cf875ae13f94b0139dbf02ad23e5f9e9
sphinx-immaterial == 0.11.2
myst-parser
sphinx-design
sphinx-last-updated-by-git
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ dev = [
"pytest",
"pytest-cov[toml]",
"pytest-xdist",
# "pytest-benchmark", # Remove until py dependency is fixed (https://github.com/ionelmc/pytest-benchmark/issues/226)
"pytest-benchmark >= 4.0.0",
]

[project.urls]
Expand Down Expand Up @@ -98,10 +98,10 @@ disable = [
"unneeded-not",
]
min-similarity-lines = 100
max-line-length = 140
max-line-length = 120

[tool.black]
line-length = 140
line-length = 120
exclude = '''
/(
build
Expand Down
12 changes: 9 additions & 3 deletions scripts/create_prime_factors_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,9 @@ def parse_factors_string(table: dict, rows: dict, row: dict, string: str, letter
# A special composite. Verify that it divides the remaining value.
label = prime_composite_label(table, row["n"], letter)
key = (label, factor)
assert row["composite"] % table["composites"][key] == 0, f"{row['composite']} is not divisible by {table['composites'][key]}"
assert (
row["composite"] % table["composites"][key] == 0
), f"{row['composite']} is not divisible by {table['composites'][key]}"
else:
# Must be a regular integer
factor = int(factor)
Expand Down Expand Up @@ -443,7 +445,9 @@ def add_to_database(
)


def test_factorization(base: int, exponent: int, offset: int, value: int, factors: list[int], multiplicities: list[int], composite: int):
def test_factorization(
base: int, exponent: int, offset: int, value: int, factors: list[int], multiplicities: list[int], composite: int
):
"""
Tests that all the factorization parameters are consistent.
"""
Expand Down Expand Up @@ -494,7 +498,9 @@ def create_even_negative_offset_table(conn: sqlite3.Connection, cursor: sqlite3.

row = select_two_factorizations_from_database(cursor, A_value, B_value)
if row is None:
assert k == k_fail, f"The {base}^(2k) - 1 table generation failed at k = {k}, but should have failed at k = {k_fail}"
assert (
k == k_fail
), f"The {base}^(2k) - 1 table generation failed at k = {k}, but should have failed at k = {k_fail}"
break

exponent = 2 * k
Expand Down
123 changes: 123 additions & 0 deletions scripts/create_primitive_polys_database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""
A script to create a database of primitive polynomials

Sources:
- Wolfram Research, "Primitive Polynomials" from the Wolfram Data Repository (2017): https://doi.org/10.24097/wolfram.48521.data

"""
from __future__ import annotations

import os
import sqlite3
from pathlib import Path

import requests
import hashlib
import json


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

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

_add_wolfram_2017(conn, cursor)

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 primitive polynomial to the database.
"""
cursor.execute(
"""
INSERT INTO polys (characteristic, degree, nonzero_degrees, nonzero_coeffs)
VALUES (?,?,?,?)
""",
(characteristic, degree, nonzero_degrees, nonzero_coeffs),
)


def _add_wolfram_2017(conn, cursor):
"""
Add Wolfram's primitive polynomials to the database.
Up to GF(2^1_200), GF(3^660), GF(5^430) and GF(7^358)
"""
url = "https://www.wolframcloud.com/objects/8a6cda66-58d7-49cf-8a1b-5d4788ff6c6e"
data = requests.get(url, stream=True).content
sha256 = hashlib.sha256()
sha256.update(data)
assert sha256.hexdigest() == "38249bff65eb06d74b9188ccbf4c29fe2becd0716588bf78d418b31463d30703"

data = json.loads(data)

print("Parsing Wolfram's primitive polynomials (2017)...")

for entry in data[1:]:
characteristic = entry[1][1]
degree = entry[1][2]
coeffs = entry[2][1][2]
nonzero_degrees = [0]
nonzero_coeffs = [coeffs[1]]
for term in coeffs[2:]:
if term[0] == "Power":
nonzero_degrees += [term[2]]
nonzero_coeffs += [1]
elif term[0] == "Times":
if term[2][0] == "Power":
nonzero_degrees += [term[2][2]]
nonzero_coeffs += [term[1]]
else: # Case P(x) = n * x
nonzero_degrees += [1]
nonzero_coeffs += [term[1]]
else: # Case P(x) = x
nonzero_degrees += [1]
nonzero_coeffs += [1]
nonzero_degrees = str(nonzero_degrees[::-1]).replace(" ", "")[1:-1]
nonzero_coeffs = str(nonzero_coeffs[::-1]).replace(" ", "")[1:-1]
print(f"Irreducible polynomial for GF({characteristic}^{degree})")
add_to_database(cursor, characteristic, degree, nonzero_degrees, nonzero_coeffs)

conn.commit()


if __name__ == "__main__":
main()
36 changes: 34 additions & 2 deletions scripts/generate_field_test_vectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,23 @@ def make_luts(field, sub_folder, seed, sparse=False):
save_pickle(d, folder, "matrix_inverse.pkl")

set_seed(seed + 206)
shapes = [(2, 2), (2, 2), (2, 2), (3, 3), (3, 3), (3, 3), (4, 4), (4, 4), (4, 4), (5, 5), (5, 5), (5, 5), (6, 6), (6, 6), (6, 6)]
shapes = [
(2, 2),
(2, 2),
(2, 2),
(3, 3),
(3, 3),
(3, 3),
(4, 4),
(4, 4),
(4, 4),
(5, 5),
(5, 5),
(5, 5),
(6, 6),
(6, 6),
(6, 6),
]
X = []
Z = []
for i in range(len(shapes)):
Expand All @@ -488,7 +504,23 @@ def make_luts(field, sub_folder, seed, sparse=False):
save_pickle(d, folder, "matrix_determinant.pkl")

set_seed(seed + 207)
shapes = [(2, 2), (2, 2), (2, 2), (3, 3), (3, 3), (3, 3), (4, 4), (4, 4), (4, 4), (5, 5), (5, 5), (5, 5), (6, 6), (6, 6), (6, 6)]
shapes = [
(2, 2),
(2, 2),
(2, 2),
(3, 3),
(3, 3),
(3, 3),
(4, 4),
(4, 4),
(4, 4),
(5, 5),
(5, 5),
(5, 5),
(6, 6),
(6, 6),
(6, 6),
]
X = []
Y = []
Z = []
Expand Down
3 changes: 2 additions & 1 deletion src/galois/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
__version__ = "0.0.0"
__version_tuple__ = (0, 0, 0)
warnings.warn(
"An error occurred during package install where setuptools_scm failed to create a _version.py file. Defaulting version to 0.0.0."
"An error occurred during package install where setuptools_scm failed to create a _version.py file."
"Defaulting version to 0.0.0."
)

# Import class/functions from nested private modules
Expand Down
Loading