Skip to content

Commit

Permalink
Merge pull request #619 from skirpichev/new-ANP
Browse files Browse the repository at this point in the history
New class for algebraic number field elements
  • Loading branch information
skirpichev committed Mar 16, 2018
2 parents 8db38e0 + 29d0dd0 commit 46cb86e
Show file tree
Hide file tree
Showing 15 changed files with 310 additions and 396 deletions.
185 changes: 167 additions & 18 deletions diofant/domains/algebraicfield.py
@@ -1,22 +1,28 @@
"""Implementation of :class:`AlgebraicField` class. """

from ..core import Integer, sympify
from ..polys.polyclasses import ANP
from ..polys.polyerrors import (CoercionFailed, DomainError, IsomorphismFailed,
NotAlgebraic)
from ..core.sympify import CantSympify
from ..polys.densearith import (dmp_neg, dmp_pow, dmp_rem, dup_add, dup_mul,
dup_sub)
from ..polys.densebasic import dmp_LC, dmp_strip, dmp_to_dict, dmp_to_tuple
from ..polys.densetools import dmp_compose
from ..polys.euclidtools import dup_invert
from ..polys.polyerrors import CoercionFailed, DomainError, NotAlgebraic
from .characteristiczero import CharacteristicZero
from .domainelement import DomainElement
from .field import Field
from .simpledomain import SimpleDomain


__all__ = ('AlgebraicField',)


_algebraic_numbers_cache = {}


class AlgebraicField(Field, CharacteristicZero, SimpleDomain):
"""A class for representing algebraic number fields. """

dtype = ANP

is_AlgebraicField = is_Algebraic = True
is_Numerical = True

Expand All @@ -41,28 +47,32 @@ def __init__(self, dom, *ext):
self.ngens = 1
self.symbols = self.gens = (self.ext.as_expr(),)

self.root = sum(self(h) for h in H)
self.unit = self([dom(1), dom(0)])
try:
self.dtype = _algebraic_numbers_cache[(self.domain, self.ext)]
except KeyError:
self.dtype = type("AlgebraicElement", (AlgebraicElement,), {"_parent": self})
_algebraic_numbers_cache[(self.domain, self.ext)] = self.dtype

self.root = sum(self.dtype(h) for h in H)
self.unit = self.dtype([dom(1), dom(0)])

self.zero = self.dtype.zero(self.mod.rep, dom)
self.one = self.dtype.one(self.mod.rep, dom)
self.zero = self.dtype([dom(0)])
self.one = self.dtype([dom(1)])

self.rep = str(self.domain) + '<' + str(self.ext) + '>'

def new(self, element):
if isinstance(element, list):
return self.dtype([self.domain.convert(_) for _ in element],
self.mod.rep, self.domain)
return self.dtype(element)
else:
return self.convert(element)

def __hash__(self):
return hash((self.__class__.__name__, self.dtype, self.domain, self.ext))
return hash((self.__class__.__name__, self.domain, self.ext))

def __eq__(self, other):
"""Returns ``True`` if two domains are equivalent. """
return isinstance(other, AlgebraicField) and \
self.dtype == other.dtype and self.ext == other.ext
return isinstance(other, AlgebraicField) and self.ext == other.ext

def algebraic_field(self, *extension):
r"""Returns an algebraic field, i.e. `\mathbb{Q}(\alpha, \ldots)`. """
Expand All @@ -74,11 +84,11 @@ def to_diofant(self, a):

def from_diofant(self, a):
"""Convert Diofant's expression to ``dtype``. """
from ..polys.numberfields import to_number_field
try:
return to_number_field(a, self)
except (NotAlgebraic, IsomorphismFailed):
K0 = self.domain.algebraic_field(a)
except NotAlgebraic:
raise CoercionFailed("%s is not a valid algebraic number in %s" % (a, self))
return self.from_AlgebraicField(K0.root, K0)

def from_ZZ_python(self, a, K0):
"""Convert a Python ``int`` object to ``dtype``. """
Expand All @@ -101,7 +111,15 @@ def from_RealField(self, a, K0):
return self([self.domain.convert(a, K0)])

def from_AlgebraicField(self, a, K0):
return self.from_diofant(K0.to_diofant(a))
from ..polys import field_isomorphism

coeffs = field_isomorphism(K0, self)

if coeffs is not None:
a_coeffs = dmp_compose(a.rep, coeffs, 0, self.domain)
return self(a_coeffs)
else:
raise CoercionFailed("%s is not in a subfield of %s" % (K0, self))

@property
def ring(self):
Expand All @@ -123,3 +141,134 @@ def is_nonpositive(self, a):
def is_nonnegative(self, a):
"""Returns True if ``a`` is non-negative. """
return self.domain.is_nonnegative(a.LC())


class AlgebraicElement(DomainElement, CantSympify):
"""Dense Algebraic Number Polynomials over a field. """

def __init__(self, rep):
dom = self.domain

if type(rep) is not list:
rep = [dom.convert(rep)]
else:
rep = [dom.convert(_) for _ in rep]

self.rep = dmp_strip(rep, 0)

@property
def parent(self):
return self._parent

@property
def mod(self):
return self._parent.mod.rep

@property
def domain(self):
return self._parent.domain

def __hash__(self):
return hash((self.__class__.__name__, dmp_to_tuple(self.rep, 0),
self._parent.domain, self._parent.ext))

def per(self, rep):
return type(self)(rep)

def to_dict(self):
"""Convert ``self`` to a dict representation with native coefficients. """
return dmp_to_dict(self.rep, 0, self.domain)

def to_diofant_list(self):
"""Convert ``self`` to a list representation with Diofant coefficients. """
return [self.domain.to_diofant(c) for c in self.rep]

@classmethod
def from_list(cls, rep):
return cls(dmp_strip(list(map(cls._parent.domain.convert, rep)), 0))

def LC(self):
"""Returns the leading coefficient of ``self``. """
return dmp_LC(self.rep, self.domain)

@property
def is_ground(self):
"""Returns ``True`` if ``self`` is an element of the ground domain. """
return len(self.rep) <= 1

def __neg__(self):
return self.per(dmp_neg(self.rep, 0, self.domain))

def __add__(self, other):
if not isinstance(other, self.parent.dtype):
try:
other = self.per(other)
except CoercionFailed:
return NotImplemented

return self.per(dup_add(self.rep, other.rep, self.domain))

def __radd__(self, other):
return self.__add__(other)

def __sub__(self, other):
if not isinstance(other, self.parent.dtype):
try:
other = self.per(other)
except CoercionFailed:
return NotImplemented

return self.per(dup_sub(self.rep, other.rep, self.domain))

def __rsub__(self, other):
return (-self).__add__(other)

def __mul__(self, other):
if not isinstance(other, self.parent.dtype):
try:
other = self.per(other)
except CoercionFailed:
return NotImplemented

return self.per(dmp_rem(dup_mul(self.rep, other.rep, self.domain),
self.mod, 0, self.domain))

def __rmul__(self, other):
return self.__mul__(other)

def __pow__(self, n):
if isinstance(n, int):
if n < 0:
F, n = dup_invert(self.rep, self.mod, self.domain), -n
else:
F = self.rep

return self.per(dmp_rem(dmp_pow(F, n, 0, self.domain),
self.mod, 0, self.domain))
else:
raise TypeError("``int`` expected, got %s" % type(n))

def __truediv__(self, other):
if not isinstance(other, self.parent.dtype):
try:
other = self.per(other)
except CoercionFailed:
return NotImplemented

return self.per(dmp_rem(dup_mul(self.rep, dup_invert(other.rep, self.mod, self.domain), self.domain),
self.mod, 0, self.domain))

def __eq__(self, other):
if not isinstance(other, self.parent.dtype):
try:
other = self.per(other)
except CoercionFailed:
return False

return self.rep == other.rep

def __lt__(self, other):
return self.rep.__lt__(other.rep)

def __bool__(self):
return bool(self.rep)
2 changes: 1 addition & 1 deletion diofant/domains/expressiondomain.py
Expand Up @@ -173,7 +173,7 @@ def from_ExpressionDomain(self, a, K0):
return a

def from_AlgebraicField(self, a, K0):
"""Convert a ``ANP`` object to ``dtype``. """
"""Convert an algebraic number to ``dtype``. """
return self(K0.to_diofant(a))

@property
Expand Down
2 changes: 1 addition & 1 deletion diofant/domains/integerring.py
Expand Up @@ -32,7 +32,7 @@ def algebraic_field(self, *extension):
return self.field.algebraic_field(*extension)

def from_AlgebraicField(self, a, K0):
"""Convert a ``ANP`` object to ``dtype``. """
"""Convert an algebraic number to ``dtype``. """
if a.is_ground:
return self.convert(a.LC(), K0.domain)

Expand Down
2 changes: 1 addition & 1 deletion diofant/domains/rationalfield.py
Expand Up @@ -25,6 +25,6 @@ def algebraic_field(self, *extension):
return AlgebraicField(self, *extension)

def from_AlgebraicField(self, a, K0):
"""Convert a ``ANP`` object to ``dtype``. """
"""Convert an algebraic number to ``dtype``. """
if a.is_ground:
return self.convert(a.LC(), K0.domain)
109 changes: 108 additions & 1 deletion diofant/domains/tests/test_domains.py
Expand Up @@ -2,7 +2,7 @@

import pytest

from diofant import Float, I, Integer, Poly, Rational, oo, sin, sqrt
from diofant import Float, I, Integer, Poly, Rational, oo, root, sin, sqrt
from diofant.abc import x, y, z
from diofant.domains import CC, EX, FF, GF, QQ, RR, ZZ, QQ_python, ZZ_python
from diofant.domains.algebraicfield import AlgebraicField
Expand Down Expand Up @@ -752,6 +752,113 @@ def test_RealField_from_diofant():
pytest.raises(CoercionFailed, lambda: RR.convert(x))


def test_AlgebraicElement():
A = QQ.algebraic_field(I)

rep = [QQ(1), QQ(1)]
mod = [QQ(1), QQ(0), QQ(1)]

f = A(rep)

assert f.rep == rep
assert f.mod == mod
assert f.domain == QQ

f = A(1)

assert f.rep == [QQ(1)]
assert f.mod == mod
assert f.domain == QQ

B = QQ.algebraic_field(I*sqrt(2))

a = A([QQ(1), QQ(1)])
b = B([QQ(1), QQ(1)])

assert (a == a) is True
assert (a != a) is False

assert (a == b) is False
assert (a != b) is True

b = A([QQ(1), QQ(2)])

assert (a == b) is False
assert (a != b) is True

assert A([1, 1]) == A([int(1), int(1)])
assert hash(A([1, 1])) == hash(A([int(1), int(1)]))

assert a.to_dict() == {(0,): QQ(1), (1,): QQ(1)}

assert bool(A([])) is False
assert bool(A([QQ(1)])) is True

a = A([QQ(1), -QQ(1), QQ(2)])
assert a.LC() == 1

A = QQ.algebraic_field(root(2, 3))

a = A([QQ(2), QQ(-1), QQ(1)])
b = A([QQ(1), QQ(2)])

c = A([QQ(-2), QQ(1), QQ(-1)])

assert -a == c

c = A([QQ(2), QQ(0), QQ(3)])

assert a + b == c
assert b + a == c

assert c + 1 == A([QQ(2), QQ(0), QQ(4)])
pytest.raises(TypeError, lambda: c + "x")
pytest.raises(TypeError, lambda: "x" + c)

c = A([QQ(2), QQ(-2), QQ(-1)])

assert a - b == c

c = A([QQ(-2), QQ(2), QQ(1)])

assert b - a == c

assert c - 1 == A([QQ(-2), QQ(2), QQ(0)])
pytest.raises(TypeError, lambda: c - "x")
pytest.raises(TypeError, lambda: "x" - c)

c = A([QQ(3), QQ(-1), QQ(6)])

assert a*b == c
assert b*a == c

assert c*2 == A([QQ(6), QQ(-2), QQ(12)])
pytest.raises(TypeError, lambda: c*"x")
pytest.raises(TypeError, lambda: "x"*c)

c = A([QQ(11, 10), -QQ(1, 5), -QQ(3, 5)])

assert c/2 == A([QQ(11, 20), -QQ(1, 10), -QQ(3, 10)])
pytest.raises(TypeError, lambda: c/"x")
pytest.raises(TypeError, lambda: "x"/c)

c = A([QQ(-1, 43), QQ(9, 43), QQ(5, 43)])

assert a**0 == A(1)
assert a**1 == a
assert a**-1 == c
pytest.raises(TypeError, lambda: a**QQ(1, 2))

assert a*a**(-1) == A(1)

A = QQ.algebraic_field(I)

a = A([QQ(1, 2), QQ(1), QQ(2)])
b = A([ZZ(1), ZZ(1), ZZ(2)])
c = A([QQ(3, 2), QQ(2), QQ(4)])
assert a + b == b + a == c


def test_ModularInteger():
F3 = FF(3)

Expand Down

0 comments on commit 46cb86e

Please sign in to comment.