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

Division in a finite extension #19593

Merged
merged 7 commits into from Jun 25, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions doc/src/modules/polys/agca.rst
Expand Up @@ -309,3 +309,28 @@ Finally, here is the detailed reference of the actual homomorphism class:

.. autoclass:: ModuleHomomorphism
:members:

Finite Extensions
-----------------

Let $A$ be a (commutative) ring and $B$ an extension ring of $A$.
An element $t$ of $B$ is a generator of $B$ (over $A$) if all elements
of $B$ can be represented as polynomials in $t$ with coefficients
in $A$. The representation is unique if and only if $t$ satisfies no
non-trivial polynomial relation, in which case $B$ can be identified
with a (univariate) polynomial ring over $A$.

The polynomials having $t$ as a root form a non-zero ideal in general.
The most important case in practice is that of an ideal generated by
a single monic polynomial. If $t$ satisfies such a polynomial relation,
then its highest power $t^n$ can be written as linear combination of
lower powers. It follows, inductively, that all higher powers of $t$
also have such a representation. Hence the lower powers $t^i$
($i = 0, \dots, n-1$) form a basis of $B$, which is then called a finite
extension of $A$, or, more precisely, a monogenic finite extension
as it is generated by a single element $t$.

.. currentmodule:: sympy.polys.agca.extensions

.. autoclass:: MonogenicFiniteExtension
:members:
94 changes: 89 additions & 5 deletions sympy/polys/agca/extensions.py
@@ -1,8 +1,6 @@
"""Finite extensions of ring domains."""

from __future__ import print_function, division

from sympy.polys.polyerrors import CoercionFailed
from sympy.polys.polyerrors import CoercionFailed, NotInvertible
from sympy.polys.polytools import Poly

class ExtensionElement(object):
Expand Down Expand Up @@ -70,11 +68,57 @@ def __mul__(f, g):

__rmul__ = __mul__

def inverse(f):
"""Multiplicative inverse.

Raises
======
NotInvertible
If the element is a zero divisor.

"""
if not f.ext.domain.is_Field:
raise NotImplementedError("base field expected")
return ExtElem(f.rep.invert(f.ext.mod), f.ext)

def _invrep(f, g):
rep = f._get_rep(g)
if rep is not None:
return rep.invert(f.ext.mod)
else:
return None

def __truediv__(f, g):
if not f.ext.domain.is_Field:
return NotImplemented
try:
rep = f._invrep(g)
except NotInvertible:
raise ZeroDivisionError

if rep is not None:
return f*ExtElem(rep, f.ext)
else:
return NotImplemented

__floordiv__ = __truediv__

def __rtruediv__(f, g):
try:
return f.ext.convert(g)/f
except CoercionFailed:
return NotImplemented

__rfloordiv__ = __rtruediv__

def __pow__(f, n):
if not isinstance(n, int):
raise TypeError("exponent of type 'int' expected")
if n < 0:
raise ValueError("negative powers are not defined")
try:
f, n = f.inverse(), -n
except NotImplementedError:
raise ValueError("negative powers are not defined")

b = f.rep
m = f.ext.mod
Expand Down Expand Up @@ -109,12 +153,52 @@ def __str__(f):


class MonogenicFiniteExtension(object):
"""
r"""
Finite extension generated by an integral element.

The generator is defined by a monic univariate
polynomial derived from the argument ``mod``.

A shorter alias is ``FiniteExtension``.

Examples
========

Quadratic integer ring $\mathbb{Z}[\sqrt2]$:

>>> from sympy import Symbol, Poly
>>> from sympy.polys.agca.extensions import FiniteExtension
>>> x = Symbol('x')
>>> R = FiniteExtension(Poly(x**2 - 2)); R
ZZ[x]/(x**2 - 2)
>>> R.rank
2
>>> R(1 + x)*(3 - 2*x)
x - 1

Finite field $GF(5^3)$ defined by the primitive
polynomial $x^3 + x^2 + 2$ (over $\mathbb{Z}_5$).

>>> F = FiniteExtension(Poly(x**3 + x**2 + 2, modulus=5)); F
GF(5)[x]/(x**3 + x**2 + 2)
>>> F.basis
(1, x, x**2)
>>> F(x + 3)/(x**2 + 2)
-2*x**2 + x + 2

Function field of an elliptic curve:

>>> t = Symbol('t')
>>> FiniteExtension(Poly(t**2 - x**3 - x + 1, t, field=True))
ZZ(x)[t]/(t**2 - x**3 - x + 1)

Notes
=====

``FiniteExtension`` is not a subclass of :class:`~.Domain`. Consequently,
a ``FiniteExtension`` can't currently be used as ``domain`` for the
:class:`~.Poly` class.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why can't this be used as a domain?

Copy link
Member Author

Choose a reason for hiding this comment

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

A more a fitting comment might be that FiniteExtension is not a subclass of Domain. However, it should be possible to define a FiniteField class that would derive from Domain and FiniteExtension. It is also possible to define other wrapper subclasses of Domain for (some types of) FiniteExtension. (Consider PolynomialRing vs. PolyRing.)

Copy link
Contributor

Choose a reason for hiding this comment

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

A more a fitting comment might be that FiniteExtension is not a subclass of Domain. However, it should be possible to define a FiniteField class that would derive from Domain and FiniteExtension. It is also possible to define other wrapper subclasses of Domain for (some types of) FiniteExtension. (Consider PolynomialRing vs. PolyRing.)

I'll see what this entails and how this works out and at the very least improve the comment.

Finite fields were the motivation for looking into this, but in the meantime I've also had a closer look at galoistools.py and it definitely has everything that would be needed for implementing proper non-prime finite fields (including algorithms for determining irreducibility over Z_p and actually finding irreducible polynomials). So maybe that's the better way forward when it comes to implementing GF(p^n) as polys domain. At least it seems to have been the original plan.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm trying to work out a proof-of-concept for GF(p**d) using FiniteExtension, but I must confess that I'm struggling a bit with all the (messy/undocumented/labyrinthine) internals of polys. In particular:

  • what is the lev argument of DMP?
    class DMP(PicklableWithSlots, CantSympify):
    """Dense Multivariate Polynomials over `K`. """
    __slots__ = ('rep', 'lev', 'dom', 'ring')
    def __init__(self, rep, dom, lev=None, ring=None):
    if lev is not None:
    if type(rep) is dict:
    rep = dmp_from_dict(rep, lev, dom)
    elif type(rep) is not list:
    rep = dmp_ground(dom.convert(rep), lev)
    else:
    rep, lev = dmp_validate(rep)
    self.rep = rep
    self.lev = lev
    self.dom = dom
    self.ring = ring
  • I noticed the following:
In [60]: F = FiniteExtension(poly(x**3+x**2+2, modulus=5))

In [61]: type(F.ring)
Out[61]: sympy.polys.domains.old_polynomialring.GlobalPolynomialRing

Isn't the old_polynomialring somewhat deprecated? Does it matter for this application?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the lev argument is basically the number of generators:

In [5]: Poly(x, x, domain=ZZ).rep.lev                                                                                                          
Out[5]: 0

In [6]: Poly(x, x, y, domain=ZZ).rep.lev                                                                                                       
Out[6]: 1

In [7]: Poly(x, x, y, z, domain=ZZ).rep.lev                                                                                                    
Out[7]: 2

I think it's called lev as short for "level" representing the fact that in the dense representation a poly in n generators is implemented as a poly of polys in n-1 generators. The internal representation is a list of domain elements for an univariate poly and then a list of lists for bivariate etc:

In [9]: Poly(x, x, domain=ZZ).rep.rep                                                                                                          
Out[9]: [mpz(1), mpz(0)]

In [10]: Poly(x, x, y, domain=ZZ).rep.rep                                                                                                      
Out[10]: [[mpz(1)], []]

In [11]: Poly(x, x, y, z, domain=ZZ).rep.rep                                                                                                   
Out[11]: [[[mpz(1)]], [[]]]

The lev argument basically tells the dmp_* functions how many levels to recurse.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the lev argument is basically the number of generators [...]

Thanks!

I'm also hitting circular import issues (concerning Poly) when I try adding a PrimePowerFiniteField class in domains/finitefield.py that subclasses FiniteField and FiniteExtension. That's probaby not unexpected given that logically the chain is domains -> polynomials -> extensions. Maybe a more direct implementation is less prone to such issues.

Concerning this PR I'll modify the note in the docstring and that's it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Isn't the old_polynomialring somewhat deprecated?

It depends on the number of generators. For a small number of generators, say, 1, 2, (3,) and dense polynomials, I think that it is quite efficient. For a large number of generators, the nested list representation becomes very inefficient.

Copy link
Member Author

Choose a reason for hiding this comment

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

lev is the number of generators minus 1.

Copy link
Member Author

Choose a reason for hiding this comment

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

'm also hitting circular import issues (concerning Poly) when I try adding a PrimePowerFiniteField class in domains/finitefield.py that subclasses FiniteField and FiniteExtension.

I have suggested another approach: Define a new class, say, FiniteRing, that would do what FiniteField does now (or simply rename FiniteField). Invoking FiniteField with a non-prime argument would pass the call to FiniteRing, presumably with a deprecation statement. Then FiniteField could subclass from FiniteExtension.

Copy link
Contributor

Choose a reason for hiding this comment

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

presumably with a deprecation statement

I don't think that's necessary as long as the new FiniteField also accepts the current parameters for creating a finite field of prime order. There are only some 3 or 4 very minor (and non-sensical) tests in the whole testsuite that actually instantiate a ring of integers modulo a composite. There's no real use anywhere and the current broken behavior is (fortunately) not publicly documented.

I think it's best to take the discussion concerning the implementation to #9544.


"""
def __init__(self, mod):
if not (isinstance(mod, Poly) and mod.is_univariate):
Expand Down
12 changes: 12 additions & 0 deletions sympy/polys/agca/tests/test_extensions.py
@@ -1,5 +1,7 @@
from sympy.polys.polytools import Poly
from sympy.polys.polyerrors import NotInvertible
from sympy.polys.agca.extensions import FiniteExtension
from sympy.testing.pytest import raises
from sympy.abc import x, t

def test_FiniteExtension():
Expand All @@ -14,6 +16,7 @@ def test_FiniteExtension():
assert i**2 != -1 # no coercion
assert (2 + i)*(1 - i) == 3 - i
assert (1 + i)**8 == A(16)
raises(NotImplementedError, lambda: A(1).inverse())

# Finite field of order 27
F = FiniteExtension(Poly(x**3 - x + 1, x, modulus=3))
Expand All @@ -26,11 +29,20 @@ def test_FiniteExtension():
assert a**9 == a + 1
assert a**3 == a - 1
assert a**6 == a**2 + a + 1
assert F(x**2 + x).inverse() == 1 - a
assert F(x + 2)**(-1) == F(x + 2).inverse()
assert a**19 * a**(-19) == F(1)
assert (a - 1) / (2*a**2 - 1) == a**2 + 1
assert (a - 1) // (2*a**2 - 1) == a**2 + 1
assert 2/(a**2 + 1) == a**2 - a + 1
assert (a**2 + 1)/2 == -a**2 - 1
raises(NotInvertible, lambda: F(0).inverse())

# Function field of an elliptic curve
K = FiniteExtension(Poly(t**2 - x**3 - x + 1, t, field=True))
assert K.rank == 2
assert str(K) == 'ZZ(x)[t]/(t**2 - x**3 - x + 1)'
y = K.generator
c = 1/(x**3 - x**2 + x - 1)
assert ((y + x)*(y - x)).inverse() == K(c)
assert (y + x)*(y - x)*c == K(1) # explicit inverse of y + x