Skip to content

Commit

Permalink
Better solution for avoiding circular imports
Browse files Browse the repository at this point in the history
  • Loading branch information
mhostetter committed Feb 1, 2023
1 parent 9485b5d commit 4c61dad
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 183 deletions.
18 changes: 18 additions & 0 deletions src/galois/_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ def export(obj):
return obj


def method_of(class_):
"""
Monkey-patches the decorated function into the class as a method. The class should already have a stub method
that raises `NotImplementedError`. The docstring of the stub method is replaced with the docstring of the
decorated function.
This is used to separate code into multiple files while still keeping the methods in the same class.
"""

def decorator(func):
setattr(class_, func.__name__, func)
setattr(getattr(class_, func.__name__), "__doc__", func.__doc__)

return func

return decorator


def extend_docstring(method, replace=None, docstring=""):
"""
A decorator to extend the docstring of `method` with the provided docstring. The decorator also finds
Expand Down
7 changes: 0 additions & 7 deletions src/galois/_polys/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
"""
A subpackage containing arrays over Galois fields.
"""
from . import _constructors
from ._conway import *
from ._factor import *
from ._functions import *
from ._irreducible import *
from ._lagrange import *
from ._poly import *
from ._primitive import *

_constructors.POLY = Poly
_constructors.POLY_DEGREES = Poly.Degrees
_constructors.POLY_INT = Poly.Int
_constructors.POLY_RANDOM = Poly.Random
46 changes: 0 additions & 46 deletions src/galois/_polys/_constructors.py

This file was deleted.

88 changes: 68 additions & 20 deletions src/galois/_polys/_factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,62 @@
"""
from __future__ import annotations

from typing import TYPE_CHECKING

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

__all__ = []


@method_of(Poly)
def is_square_free(f) -> bool:
r"""
Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` is square-free.
.. info::
This is a method, not a property, to indicate this test is computationally expensive.
Returns:
`True` if the polynomial is square-free.
Notes:
A square-free polynomial :math:`f(x)` has no irreducible factors with multiplicity greater than one.
Therefore, its canonical factorization is
.. math::
f(x) = \prod_{i=1}^{k} g_i(x)^{e_i} = \prod_{i=1}^{k} g_i(x) .
Examples:
Generate irreducible polynomials over :math:`\mathrm{GF}(3)`.
.. ipython:: python
GF = galois.GF(3)
f1 = galois.irreducible_poly(3, 3); f1
f2 = galois.irreducible_poly(3, 4); f2
Determine if composite polynomials are square-free over :math:`\mathrm{GF}(3)`.
.. ipython:: python
(f1 * f2).is_square_free()
(f1**2 * f2).is_square_free()
"""
if not f.is_monic:
f //= f.coeffs[0]

# Constant polynomials are square-free
if f.degree == 0:
return True

_, multiplicities = square_free_factors(f)

if TYPE_CHECKING:
from ._poly import Poly
return multiplicities == [1]


def _square_free_factors(f: Poly) -> tuple[list[Poly], list[int]]:
@method_of(Poly)
def square_free_factors(f: Poly) -> tuple[list[Poly], list[int]]:
r"""
Factors the monic polynomial :math:`f(x)` into a product of square-free polynomials.
Expand Down Expand Up @@ -72,7 +117,7 @@ def _square_free_factors(f: Poly) -> tuple[list[Poly], list[int]]:

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

factors_ = []
multiplicities = []
Expand Down Expand Up @@ -100,8 +145,8 @@ def _square_free_factors(f: Poly) -> 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 = _constructors.POLY_DEGREES(degrees, coeffs=coeffs, field=field) # The p-th root of d(x)
g, m = _square_free_factors(delta)
delta = Poly.Degrees(degrees, coeffs=coeffs, field=field) # The p-th root of d(x)
g, m = square_free_factors(delta)
factors_.extend(g)
multiplicities.extend([mi * p for mi in m])

Expand All @@ -111,7 +156,8 @@ def _square_free_factors(f: Poly) -> tuple[list[Poly], list[int]]:
return list(factors_), list(multiplicities)


def _distinct_degree_factors(f: Poly) -> tuple[list[Poly], list[int]]:
@method_of(Poly)
def distinct_degree_factors(f: Poly) -> 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 @@ -188,8 +234,8 @@ def _distinct_degree_factors(f: Poly) -> tuple[list[Poly], list[int]]:
field = f.field
q = field.order
n = f.degree
one = _constructors.POLY([1], field=field)
x = _constructors.POLY([1, 0], field=field)
one = Poly([1], field=field)
x = Poly([1, 0], field=field)

factors_ = []
degrees = []
Expand All @@ -215,7 +261,8 @@ def _distinct_degree_factors(f: Poly) -> tuple[list[Poly], list[int]]:
return factors_, degrees


def _equal_degree_factors(f: Poly, degree: int) -> list[Poly]:
@method_of(Poly)
def equal_degree_factors(f: Poly, 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 @@ -283,11 +330,11 @@ def _equal_degree_factors(f: Poly, degree: int) -> list[Poly]:
field = f.field
q = field.order
r = f.degree // degree
one = _constructors.POLY([1], field=field)
one = Poly([1], field=field)

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


def _factors(f) -> tuple[list[Poly], list[int]]:
@method_of(Poly)
def factors(f) -> tuple[list[Poly], list[int]]:
r"""
Computes the irreducible factors of the non-constant, monic polynomial :math:`f(x)`.
Expand Down Expand Up @@ -374,15 +422,15 @@ def _factors(f) -> tuple[list[Poly], list[int]]:
factors_, multiplicities = [], []

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

# 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
15 changes: 5 additions & 10 deletions src/galois/_polys/_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@
"""
from __future__ import annotations

from typing import TYPE_CHECKING

from . import _constructors

if TYPE_CHECKING:
from ._poly import Poly
from ._poly import Poly


def gcd(a: Poly, b: Poly) -> Poly:
Expand Down Expand Up @@ -38,8 +33,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 = _constructors.POLY([0], field=field)
one = _constructors.POLY([1], field=field)
zero = Poly([0], field=field)
one = Poly([1], field=field)

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

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

prod_ = _constructors.POLY([1], field=field)
prod_ = Poly([1], field=field)
for arg in args:
if not arg.field == field:
raise ValueError(
Expand Down
16 changes: 7 additions & 9 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 TYPE_CHECKING, Iterator
from typing import Iterator

from typing_extensions import Literal

from .._domains import _factory
from .._helper import export, verify_isinstance
from .._helper import export, method_of, 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,12 +21,10 @@
_random_search_fixed_terms,
)

if TYPE_CHECKING:
from ._poly import Poly


@method_of(Poly)
@functools.lru_cache(maxsize=8192)
def _is_irreducible(f: Poly) -> bool:
def is_irreducible(f: Poly) -> bool:
r"""
Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(p^m)` is irreducible.
Expand Down Expand Up @@ -101,10 +99,10 @@ def _is_irreducible(f: Poly) -> bool:
field = f.field
q = field.order
m = f.degree
x = _constructors.POLY([1, 0], field=field)
x = Poly([1, 0], field=field)

primes, _ = factors(m)
h0 = _constructors.POLY([1, 0], field=field)
h0 = 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
Loading

0 comments on commit 4c61dad

Please sign in to comment.