Skip to content

Commit

Permalink
Make search for a random irreducible/primitive polynomial much more e…
Browse files Browse the repository at this point in the history
…fficient
  • Loading branch information
mhostetter committed Feb 1, 2023
1 parent a82c9d0 commit 534de4e
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 60 deletions.
47 changes: 18 additions & 29 deletions src/galois/_polys/_irreducible.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@
from .._domains import _factory
from .._helper import export, verify_isinstance
from .._prime import is_prime_power
from ._poly import Poly, _deterministic_search_fixed_terms, _minimum_terms
from ._poly import (
Poly,
_deterministic_search_fixed_terms,
_minimum_terms,
_random_search,
_random_search_fixed_terms,
)

if TYPE_CHECKING:
from .._fields import FieldArray
Expand Down Expand Up @@ -113,21 +119,23 @@ def irreducible_poly(

try:
if method == "min":
poly = next(irreducible_polys(order, degree, terms))
elif method == "max":
poly = next(irreducible_polys(order, degree, terms, reverse=True))
else:
if terms == "min":
terms = _minimum_terms(order, degree, "is_irreducible")
poly = _random_search(order, degree, terms)
return next(irreducible_polys(order, degree, terms))
if method == "max":
return next(irreducible_polys(order, degree, terms, reverse=True))

# Random search
if terms is None:
return next(_random_search(order, degree, "is_irreducible"))
if terms == "min":
terms = _minimum_terms(order, degree, "is_irreducible")
return next(_random_search_fixed_terms(order, degree, terms, "is_irreducible"))

except StopIteration as e:
terms_str = "any" if terms is None else str(terms)
raise RuntimeError(
f"No monic irreducible polynomial of degree {degree} over GF({order}) with {terms_str} terms exists."
) from e

return poly


@export
def irreducible_polys(
Expand Down Expand Up @@ -262,22 +270,3 @@ def _deterministic_search(
return poly

return None


def _random_search(order: int, degree: int, terms: int | None) -> Poly:
"""
Searches for a random irreducible polynomial.
"""
field = _factory.FIELD_FACTORY(order)

# Only search monic polynomials of degree m over GF(p)
start = order**degree
stop = 2 * order**degree

while True:
integer = random.randint(start, stop - 1)
poly = Poly.Int(integer, field=field)
if terms is not None and poly.nonzero_coeffs.size != terms:
continue
if poly.is_irreducible():
return poly
55 changes: 53 additions & 2 deletions src/galois/_polys/_poly.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from __future__ import annotations

import functools
import random
from typing import Callable, Iterable, Iterator, Sequence, Type, overload

import numpy as np
Expand Down Expand Up @@ -2318,7 +2319,7 @@ def _deterministic_search_fixed_terms(
reverse: bool = False,
) -> Iterator[Poly]:
"""
Iterates over all polynomials of the given degree and number of non-zero terms in lexicographical
Iterates over all monic polynomials of the given degree and number of non-zero terms in lexicographical
order, only yielding those that pass the specified test (either 'is_irreducible()' or 'is_primitive()').
"""
assert test in ["is_irreducible", "is_primitive"]
Expand All @@ -2344,7 +2345,7 @@ def _deterministic_search_fixed_terms_recursive(
direction: Callable[[Iterable[int]], Iterable[int]],
) -> Iterator[Poly]:
"""
Recursively finds all polynomials having non-zero coefficients `coeffs` with degree `degrees` with `terms`
Recursively finds all monic polynomials having non-zero coefficients `coeffs` with degree `degrees` with `terms`
additional non-zero terms. The polynomials are found in lexicographical order, only yielding those that pass
the specified test (either 'is_irreducible()' or 'is_primitive()').
"""
Expand All @@ -2371,3 +2372,53 @@ def _deterministic_search_fixed_terms_recursive(
yield from _deterministic_search_fixed_terms_recursive(
next_degrees, next_coeffs, terms - 1, test, field, direction
)


def _random_search(order: int, degree: int, test: str) -> Iterator[Poly]:
"""
Searches for a random monic polynomial of specified degree, only yielding those that pass the specified test
(either 'is_irreducible()' or 'is_primitive()').
"""
assert test in ["is_irreducible", "is_primitive"]
field = _factory.FIELD_FACTORY(order)

# Only search monic polynomials of degree m over GF(q)
start = order**degree
stop = 2 * order**degree

while True:
integer = random.randint(start, stop - 1)
poly = Poly.Int(integer, field=field)
if getattr(poly, test)():
yield poly


def _random_search_fixed_terms(
order: int,
degree: int,
terms: int,
test: str,
) -> Iterator[Poly]:
"""
Searches for a random monic polynomial of specified degree and number of non-zero terms, only yielding those that
pass the specified test (either 'is_irreducible()' or 'is_primitive()').
"""
assert test in ["is_irreducible", "is_primitive"]
field = _factory.FIELD_FACTORY(order)

if terms == 1:
# The x^m term is always 1. If there's only one term, then the x^m is the polynomial.
poly = Poly.Degrees([degree], [1], field=field)
if getattr(poly, test)():
yield poly
else:
while True:
# The x^m term is always 1 and the x^0 term is always non-zero.
mid_degrees = random.sample(range(1, degree), terms - 2)
mid_coeffs = np.random.randint(1, field.order, terms - 2)
x0_coeff = np.random.randint(1, field.order)
degrees = (degree, *mid_degrees, 0)
coeffs = (1, *mid_coeffs, x0_coeff)
poly = Poly.Degrees(degrees, coeffs, field=field)
if getattr(poly, test)():
yield poly
47 changes: 18 additions & 29 deletions src/galois/_polys/_primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
from .._domains import _factory
from .._helper import export, verify_isinstance
from .._prime import is_prime, is_prime_power
from ._poly import Poly, _deterministic_search_fixed_terms, _minimum_terms
from ._poly import (
Poly,
_deterministic_search_fixed_terms,
_minimum_terms,
_random_search,
_random_search_fixed_terms,
)

if TYPE_CHECKING:
from .._fields import FieldArray
Expand Down Expand Up @@ -132,21 +138,23 @@ def primitive_poly(

try:
if method == "min":
poly = next(primitive_polys(order, degree, terms))
elif method == "max":
poly = next(primitive_polys(order, degree, terms, reverse=True))
else:
if terms == "min":
terms = _minimum_terms(order, degree, "is_primitive")
poly = _random_search(order, degree, terms)
return next(primitive_polys(order, degree, terms))
if method == "max":
return next(primitive_polys(order, degree, terms, reverse=True))

# Random search
if terms is None:
return next(_random_search(order, degree, "is_primitive"))
if terms == "min":
terms = _minimum_terms(order, degree, "is_primitive")
return next(_random_search_fixed_terms(order, degree, terms, "is_primitive"))

except StopIteration as e:
terms_str = "any" if terms is None else str(terms)
raise RuntimeError(
f"No monic primitive polynomial of degree {degree} over GF({order}) with {terms_str} terms exists."
) from e

return poly


@export
def primitive_polys(
Expand Down Expand Up @@ -285,25 +293,6 @@ def _deterministic_search(
return None


def _random_search(order: int, degree: int, terms: int | None) -> Poly:
"""
Searches for a random primitive polynomial.
"""
field = _factory.FIELD_FACTORY(order)

# Only search monic polynomials of degree m over GF(p)
start = order**degree
stop = 2 * order**degree

while True:
integer = random.randint(start, stop - 1)
poly = Poly.Int(integer, field=field)
if terms is not None and poly.nonzero_coeffs.size != terms:
continue
if poly.is_primitive():
return poly


@export
def conway_poly(characteristic: int, degree: int) -> Poly:
r"""
Expand Down

0 comments on commit 534de4e

Please sign in to comment.