Skip to content

Commit

Permalink
Preserve intellisense autocompletion and brief docstring of patched `…
Browse files Browse the repository at this point in the history
…Poly` methods
  • Loading branch information
mhostetter committed Feb 1, 2023
1 parent 289857a commit e228261
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 59 deletions.
12 changes: 5 additions & 7 deletions src/galois/_polys/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
"""
A subpackage containing arrays over Galois fields.
"""
from . import _constructors
from ._conway import *
from ._factor import *
from ._functions import *
from ._irreducible import *
from ._poly import *
from ._primitive import *

# pylint: disable=undefined-variable
Poly.square_free_factors = _factor.square_free_factors
Poly.distinct_degree_factors = _factor.distinct_degree_factors
Poly.equal_degree_factors = _factor.equal_degree_factors
Poly.factors = _factor.factors
Poly.is_irreducible = _irreducible.is_irreducible
Poly.is_primitive = _primitive.is_primitive
_constructors.POLY = Poly
_constructors.POLY_DEGREES = Poly.Degrees
_constructors.POLY_INT = Poly.Int
_constructors.POLY_RANDOM = Poly.Random
46 changes: 46 additions & 0 deletions src/galois/_polys/_constructors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
A module containing polynomial constructors that will be monkey-patched to Poly(), Poly.Int(), Poly.Degrees(),
and Poly.Random() in polys/__init__.py.
This is done to separate code into related modules without having circular imports with Poly().
"""
from __future__ import annotations

from typing import TYPE_CHECKING, Sequence, Type

import numpy as np
from typing_extensions import Literal

from .._domains import Array
from ..typing import ArrayLike

if TYPE_CHECKING:
from ._poly import Poly


def POLY(
coeffs: ArrayLike,
field: Type[Array] | None = None,
order: Literal["desc", "asc"] = "desc",
) -> Poly:
raise NotImplementedError


def POLY_DEGREES(
degrees: Sequence[int] | np.ndarray,
coeffs: ArrayLike | None = None,
field: Type[Array] | None = None,
) -> Poly:
raise NotImplementedError


def POLY_INT(integer: int, field: Type[Array] | None = None) -> Poly:
raise NotImplementedError


def POLY_RANDOM(
degree: int,
seed: int | np.integer | np.random.Generator | None = None,
field: Type[Array] | None = None,
) -> Poly:
raise NotImplementedError
45 changes: 23 additions & 22 deletions src/galois/_polys/_factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
"""
from __future__ import annotations

from typing import TYPE_CHECKING

from .._helper import verify_isinstance
from . import _constructors
from ._functions import gcd
from ._poly import Poly

if TYPE_CHECKING:
from ._poly import Poly


# NOTE: This is a method of the Poly class. It will be added to the Poly class in `polys/__init__.py`.
def square_free_factors(self) -> tuple[list[Poly], list[int]]:
def _square_free_factors(self) -> tuple[list[Poly], list[int]]:
r"""
Factors the monic polynomial :math:`f(x)` into a product of square-free polynomials.
Expand Down Expand Up @@ -68,7 +72,7 @@ def square_free_factors(self) -> tuple[list[Poly], list[int]]:

field = self.field
p = field.characteristic
one = Poly.One(field=field)
one = _constructors.POLY([1], field=field)

factors_ = []
multiplicities = []
Expand All @@ -95,8 +99,8 @@ def square_free_factors(self) -> tuple[list[Poly], list[int]]:
degrees = [degree // p for degree in d.nonzero_degrees]
# The inverse Frobenius automorphism of the coefficients
coeffs = d.nonzero_coeffs ** (field.characteristic ** (field.degree - 1))
delta = Poly.Degrees(degrees, coeffs=coeffs, field=field) # The p-th root of d(x)
f, m = square_free_factors(delta)
delta = _constructors.POLY_DEGREES(degrees, coeffs=coeffs, field=field) # The p-th root of d(x)
f, m = _square_free_factors(delta)
factors_.extend(f)
multiplicities.extend([mi * p for mi in m])

Expand All @@ -106,8 +110,7 @@ def square_free_factors(self) -> tuple[list[Poly], list[int]]:
return list(factors_), list(multiplicities)


# NOTE: This is a method of the Poly class. It will be added to the Poly class in `polys/__init__.py`.
def distinct_degree_factors(self) -> tuple[list[Poly], list[int]]:
def _distinct_degree_factors(self) -> tuple[list[Poly], list[int]]:
r"""
Factors the monic, square-free polynomial :math:`f(x)` into a product of polynomials whose irreducible factors
all have the same degree.
Expand Down Expand Up @@ -184,8 +187,8 @@ def distinct_degree_factors(self) -> tuple[list[Poly], list[int]]:
field = self.field
q = field.order
n = self.degree
one = Poly.One(field=field)
x = Poly.Identity(field=field)
one = _constructors.POLY([1], field=field)
x = _constructors.POLY([1, 0], field=field)

factors_ = []
degrees = []
Expand All @@ -211,8 +214,7 @@ def distinct_degree_factors(self) -> tuple[list[Poly], list[int]]:
return factors_, degrees


# NOTE: This is a method of the Poly class. It will be added to the Poly class in `polys/__init__.py`.
def equal_degree_factors(self, degree: int) -> list[Poly]:
def _equal_degree_factors(self, degree: int) -> list[Poly]:
r"""
Factors the monic, square-free polynomial :math:`f(x)` of degree :math:`rd` into a product of :math:`r`
irreducible factors with degree :math:`d`.
Expand Down Expand Up @@ -282,11 +284,11 @@ def equal_degree_factors(self, degree: int) -> list[Poly]:
field = self.field
q = field.order
r = self.degree // degree
one = Poly.One(field)
one = _constructors.POLY([1], field=field)

factors_ = [self]
while len(factors_) < r:
h = Poly.Random(degree, field=field)
h = _constructors.POLY_RANDOM(degree, field=field)
g = gcd(self, h)
if g == one:
g = pow(h, (q**degree - 1) // 2, self) - one
Expand All @@ -307,8 +309,7 @@ def equal_degree_factors(self, degree: int) -> list[Poly]:
return factors_


# NOTE: This is a method of the Poly class. It will be added to the Poly class in `polys/__init__.py`.
def factors(self) -> tuple[list[Poly], list[int]]:
def _factors(self) -> tuple[list[Poly], list[int]]:
r"""
Computes the irreducible factors of the non-constant, monic polynomial :math:`f(x)`.
Expand All @@ -327,11 +328,11 @@ def factors(self) -> tuple[list[Poly], list[int]]:
Steps:
1. Apply the Square-Free Factorization algorithm to factor the monic polynomial into square-free
polynomials. See :func:`~Poly.square_free_factors`.
polynomials. See :func:`~Poly.square_free_factors`.
2. Apply the Distinct-Degree Factorization algorithm to factor each square-free polynomial into a product
of factors with the same degree. See :func:`~Poly.distinct_degree_factors`.
of factors with the same degree. See :func:`~Poly.distinct_degree_factors`.
3. Apply the Equal-Degree Factorization algorithm to factor the product of factors of equal degree into
their irreducible factors. See :func:`~Poly.equal_degree_factors`.
their irreducible factors. See :func:`~Poly.equal_degree_factors`.
References:
- Hachenberger, D. and Jungnickel, D. Topics in Galois Fields. Algorithm 6.1.7.
Expand Down Expand Up @@ -374,15 +375,15 @@ def factors(self) -> tuple[list[Poly], list[int]]:
factors_, multiplicities = [], []

# Step 1: Find all the square-free factors
sf_factors, sf_multiplicities = square_free_factors(self)
sf_factors, sf_multiplicities = _square_free_factors(self)

# Step 2: Find all the factors with distinct degree
for sf_factor, sf_multiplicity in zip(sf_factors, sf_multiplicities):
df_factors, df_degrees = distinct_degree_factors(sf_factor)
df_factors, df_degrees = _distinct_degree_factors(sf_factor)

# Step 3: Find all the irreducible factors with degree d
for df_factor, df_degree in zip(df_factors, df_degrees):
f = equal_degree_factors(df_factor, df_degree)
f = _equal_degree_factors(df_factor, df_degree)
factors_.extend(f)
multiplicities.extend([sf_multiplicity] * len(f))

Expand Down
16 changes: 11 additions & 5 deletions src/galois/_polys/_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
"""
from __future__ import annotations

from typing import TYPE_CHECKING

from .._domains import Array
from .._helper import export, verify_isinstance
from . import _constructors
from ._dense import lagrange_poly_jit
from ._poly import Poly

if TYPE_CHECKING:
from ._poly import Poly


###############################################################################
# Divisibility
Expand Down Expand Up @@ -40,8 +46,8 @@ def egcd(a: Poly, b: Poly) -> tuple[Poly, Poly, Poly]:
raise ValueError(f"Polynomials `a` and `b` must be over the same Galois field, not {a.field} and {b.field}.")

field = a.field
zero = Poly.Zero(field)
one = Poly.One(field)
zero = _constructors.POLY([0], field=field)
one = _constructors.POLY([1], field=field)

r2, r1 = a, b
s2, s1 = one, zero
Expand Down Expand Up @@ -69,7 +75,7 @@ def lcm(*args: Poly) -> Poly:
"""
field = args[0].field

lcm_ = Poly.One(field)
lcm_ = _constructors.POLY([1], field=field)
for arg in args:
if not arg.field == field:
raise ValueError(
Expand All @@ -89,7 +95,7 @@ def prod(*args: Poly) -> Poly:
"""
field = args[0].field

prod_ = Poly.One(field)
prod_ = _constructors.POLY([1], field=field)
for arg in args:
if not arg.field == field:
raise ValueError(
Expand Down
14 changes: 8 additions & 6 deletions src/galois/_polys/_irreducible.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
from __future__ import annotations

import functools
from typing import Iterator
from typing import TYPE_CHECKING, Iterator

from typing_extensions import Literal

from .._domains import _factory
from .._helper import export, verify_isinstance
from .._prime import factors, is_prime_power
from . import _constructors
from ._functions import gcd
from ._poly import Poly
from ._search import (
_deterministic_search,
_deterministic_search_fixed_terms,
Expand All @@ -21,10 +21,12 @@
_random_search_fixed_terms,
)

if TYPE_CHECKING:
from ._poly import Poly


# NOTE: This is a method of the Poly class. It will be added to the Poly class in `polys/__init__.py`.
@functools.lru_cache(maxsize=8192)
def is_irreducible(self) -> bool:
def _is_irreducible(self) -> bool:
r"""
Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(p^m)` is irreducible.
Expand Down Expand Up @@ -98,10 +100,10 @@ def is_irreducible(self) -> bool:
field = self.field
q = field.order
m = self.degree
x = Poly.Identity(field)
x = _constructors.POLY([1, 0], field=field)

primes, _ = factors(m)
h0 = Poly.Identity(field)
h0 = _constructors.POLY([1, 0], field=field)
n0 = 0
for ni in sorted([m // pi for pi in primes]):
# The GCD of f(x) and (x^(q^(m/pi)) - x) must be 1 for f(x) to be irreducible, where pi are the
Expand Down
58 changes: 58 additions & 0 deletions src/galois/_polys/_poly.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
sparse_poly_to_str,
str_to_sparse_poly,
)
from ._factor import (
_distinct_degree_factors,
_equal_degree_factors,
_factors,
_square_free_factors,
)
from ._irreducible import _is_irreducible
from ._primitive import _is_primitive

# Values were obtained by running scripts/sparse_poly_performance_test.py
SPARSE_VS_DENSE_POLY_FACTOR = 0.00_125 # 1.25% density
Expand Down Expand Up @@ -767,6 +775,40 @@ def roots(self, multiplicity=False):
multiplicities = np.array([_root_multiplicity(self, root) for root in roots])
return roots, multiplicities

def square_free_factors(self) -> tuple[list[Poly], list[int]]:
r"""
Factors the monic polynomial :math:`f(x)` into a product of square-free polynomials.
"""
return _square_free_factors(self)

square_free_factors.__doc__ = _square_free_factors.__doc__

def distinct_degree_factors(self) -> tuple[list[Poly], list[int]]:
r"""
Factors the monic, square-free polynomial :math:`f(x)` into a product of polynomials whose irreducible factors
all have the same degree.
"""
return _distinct_degree_factors(self)

distinct_degree_factors.__doc__ = _distinct_degree_factors.__doc__

def equal_degree_factors(self, degree: int) -> list[Poly]:
r"""
Factors the monic, square-free polynomial :math:`f(x)` of degree :math:`rd` into a product of :math:`r`
irreducible factors with degree :math:`d`.
"""
return _equal_degree_factors(self, degree)

equal_degree_factors.__doc__ = _equal_degree_factors.__doc__

def factors(self) -> tuple[list[Poly], list[int]]:
r"""
Computes the irreducible factors of the non-constant, monic polynomial :math:`f(x)`.
"""
return _factors(self)

factors.__doc__ = _factors.__doc__

def derivative(self, k: int = 1) -> Poly:
r"""
Computes the :math:`k`-th formal derivative :math:`\frac{d^k}{dx^k} f(x)` of the polynomial :math:`f(x)`.
Expand Down Expand Up @@ -849,6 +891,22 @@ def derivative(self, k: int = 1) -> Poly:

return p_prime

def is_irreducible(self) -> bool:
r"""
Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(p^m)` is irreducible.
"""
return _is_irreducible(self)

is_irreducible.__doc__ = _is_irreducible.__doc__

def is_primitive(self) -> bool:
r"""
Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` is primitive.
"""
return _is_primitive(self)

is_primitive.__doc__ = _is_primitive.__doc__

def is_square_free(self) -> bool:
r"""
Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` is square-free.
Expand Down
Loading

0 comments on commit e228261

Please sign in to comment.