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 terms kwarg to irreducible and primitive poly functions #463

Merged
merged 7 commits into from
Jan 22, 2023
185 changes: 119 additions & 66 deletions src/galois/_polys/_irreducible.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,55 @@
"""
from __future__ import annotations

import functools
import random
from typing import Iterator

from typing_extensions import Literal

from .._domains import _factory
from .._helper import export, verify_isinstance
from .._prime import is_prime_power
from ._poly import Poly
from ._poly import (
Poly,
_deterministic_search,
_deterministic_search_fixed_terms,
_minimum_terms,
_random_search,
_random_search_fixed_terms,
)


@export
def irreducible_poly(order: int, degree: int, method: Literal["min", "max", "random"] = "min") -> Poly:
def irreducible_poly(
order: int,
degree: int,
terms: int | str | None = None,
method: Literal["min", "max", "random"] = "min",
) -> Poly:
r"""
Returns a monic irreducible polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` with degree :math:`m`.

Arguments:
order: The prime power order :math:`q` of the field :math:`\mathrm{GF}(q)` that the polynomial is over.
degree: The degree :math:`m` of the desired irreducible polynomial.
terms: The desired number of non-zero terms :math:`t` in the polynomial.

- `None` (default): Disregards the number of terms while searching for the polynomial.
- `int`: The exact number of non-zero terms in the polynomial.
- `"min"`: The minimum possible number of non-zero terms.

method: The search method for finding the irreducible polynomial.

- `"min"` (default): Returns the lexicographically-minimal monic irreducible polynomial.
- `"max"`: Returns the lexicographically-maximal monic irreducible polynomial.
- `"random"`: Returns a randomly generated degree-:math:`m` monic irreducible polynomial.
- `"min"` (default): Returns the lexicographically-first polynomial.
- `"max"`: Returns the lexicographically-last polynomial.
- `"random"`: Returns a random polynomial.

Returns:
The degree-:math:`m` monic irreducible polynomial over :math:`\mathrm{GF}(q)`.

Raises:
RuntimeError: If no monic irreducible polynomial of degree :math:`m` over :math:`\mathrm{GF}(q)` with
:math:`t` terms exists. If `terms` is `None` or `"min"`, this should never be raised.

See Also:
Poly.is_irreducible, primitive_poly, conway_poly

Expand All @@ -43,15 +63,26 @@ def irreducible_poly(order: int, degree: int, method: Literal["min", "max", "ran
:math:`\mathrm{GF}(q)`.

Examples:
Find the lexicographically minimal and maximal monic irreducible polynomial. Also find a random monic
irreducible polynomial.
Find the lexicographically-first, lexicographically-last, and a random monic irreducible polynomial.

.. ipython:: python

galois.irreducible_poly(7, 3)
galois.irreducible_poly(7, 3, method="max")
galois.irreducible_poly(7, 3, method="random")

Find the lexicographically-first monic irreducible polynomial with four terms.

.. ipython:: python

galois.irreducible_poly(7, 3, terms=4)

Find the lexicographically-first monic irreducible polynomial with the minimum number of non-zero terms.

.. ipython:: python

galois.irreducible_poly(7, 3, terms="min")

Monic irreducible polynomials scaled by non-zero field elements (now non-monic) are also irreducible.

.. ipython:: python
Expand All @@ -67,34 +98,61 @@ def irreducible_poly(order: int, degree: int, method: Literal["min", "max", "ran
"""
verify_isinstance(order, int)
verify_isinstance(degree, int)
verify_isinstance(terms, (int, str), optional=True)

if not is_prime_power(order):
raise ValueError(f"Argument 'order' must be a prime power, not {order}.")
if not degree >= 1:
raise ValueError(
f"Argument 'degree' must be at least 1, not {degree}. There are no irreducible polynomials with degree 0."
)
if isinstance(terms, int) and not 1 <= terms <= degree + 1:
raise ValueError(f"Argument 'terms' must be at least 1 and at most {degree + 1}, not {terms}.")
if isinstance(terms, str) and not terms in ["min"]:
raise ValueError(f"Argument 'terms' must be 'min', not {terms!r}.")
if not method in ["min", "max", "random"]:
raise ValueError(f"Argument 'method' must be in ['min', 'max', 'random'], not {method!r}.")

if method == "min":
poly = next(irreducible_polys(order, degree))
elif method == "max":
poly = next(irreducible_polys(order, degree, reverse=True))
else:
poly = _random_search(order, degree)
try:
if method == "min":
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"))

return poly
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


@export
def irreducible_polys(order: int, degree: int, reverse: bool = False) -> Iterator[Poly]:
def irreducible_polys(
order: int,
degree: int,
terms: int | str | None = None,
reverse: bool = False,
) -> Iterator[Poly]:
r"""
Iterates through all monic irreducible polynomials :math:`f(x)` over :math:`\mathrm{GF}(q)` with degree :math:`m`.

Arguments:
order: The prime power order :math:`q` of the field :math:`\mathrm{GF}(q)` that the polynomial is over.
degree: The degree :math:`m` of the desired irreducible polynomial.
reverse: Indicates to return the irreducible polynomials from lexicographically maximal to minimal.
terms: The desired number of non-zero terms :math:`t` in the polynomial.

- `None` (default): Disregards the number of terms while searching for the polynomial.
- `int`: The exact number of non-zero terms in the polynomial.
- `"min"`: The minimum possible number of non-zero terms.

reverse: Indicates to return the irreducible polynomials from lexicographically last to first.
The default is `False`.

Returns:
Expand All @@ -118,6 +176,18 @@ def irreducible_polys(order: int, degree: int, reverse: bool = False) -> Iterato

list(galois.irreducible_polys(3, 4))

Find all monic irreducible polynomials with four terms.

.. ipython:: python

list(galois.irreducible_polys(3, 4, terms=4))

Find all monic irreducible polynomials with the minimum number of non-zero terms.

.. ipython:: python

list(galois.irreducible_polys(3, 4, terms="min"))

Loop over all the polynomials in reversed order, only finding them as needed. The search cost for the
polynomials that would have been found after the `break` condition is never incurred.

Expand All @@ -142,56 +212,39 @@ def irreducible_polys(order: int, degree: int, reverse: bool = False) -> Iterato
"""
verify_isinstance(order, int)
verify_isinstance(degree, int)
verify_isinstance(terms, (int, str), optional=True)
verify_isinstance(reverse, bool)

if not is_prime_power(order):
raise ValueError(f"Argument 'order' must be a prime power, not {order}.")
if not degree >= 0:
raise ValueError(f"Argument 'degree' must be at least 0, not {degree}.")

field = _factory.FIELD_FACTORY(order)

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

if reverse:
start, stop, step = stop - 1, start - 1, -1

while True:
poly = _deterministic_search(field, start, stop, step)
if poly is not None:
start = int(poly) + step
yield poly
else:
break


@functools.lru_cache(maxsize=4096)
def _deterministic_search(field, start, stop, step) -> Poly | None:
"""
Searches for an irreducible polynomial in the range using the specified deterministic method.
"""
for element in range(start, stop, step):
poly = Poly.Int(element, field=field)
if poly.is_irreducible():
return poly

return None


def _random_search(order, degree) -> 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 poly.is_irreducible():
return poly
if isinstance(terms, int) and not 1 <= terms <= degree + 1:
raise ValueError(f"Argument 'terms' must be at least 1 and at most {degree + 1}, not {terms}.")
if isinstance(terms, str) and not terms in ["min"]:
raise ValueError(f"Argument 'terms' must be 'min', not {terms!r}.")

if terms == "min":
# Find the minimum number of terms required to produce an irreducible polynomial of degree m over GF(q).
# Then yield all monic irreducible polynomials of with that number of terms.
min_terms = _minimum_terms(order, degree, "is_irreducible")
yield from _deterministic_search_fixed_terms(order, degree, min_terms, "is_irreducible", reverse)
elif isinstance(terms, int):
# Iterate over and test monic polynomials of degree m over GF(q) with `terms` non-zero terms.
yield from _deterministic_search_fixed_terms(order, degree, terms, "is_irreducible", reverse)
else:
# Iterate over and test all monic polynomials of degree m over GF(q).
start = order**degree
stop = 2 * order**degree
step = 1
if reverse:
start, stop, step = stop - 1, start - 1, -1
field = _factory.FIELD_FACTORY(order)

while True:
poly = _deterministic_search(field, start, stop, step, "is_irreducible")
if poly is not None:
start = int(poly) + step
yield poly
else:
break
Loading