Skip to content

Commit

Permalink
Several fixes and performance improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
GDeLaurentis committed May 17, 2024
1 parent 920690a commit 703c22f
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 56 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Custom `Monomial` and `Polynomial` classes, to better handle polynomials with coefficients in any of $\mathbb{C}, \mathbb{F}_p, \mathbb{Q}_p$.
- DOI.
- `Ideal.point_on_variety` now checks whether the provided `directions` are consistent with the given variety.
- Added support for rational field, `Field('rational', 0, 0)`.
- Tentatively introducing shorter aliases for field names, such as `C`, `Qp` and `Fp`.

### Changed

Expand All @@ -21,6 +24,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Improved logic for call to `Singular` with long commands requiring the writing of a file. As a consequence, parallelised calls should now have much better stability.

### Deprecated

- `Field.random_element` is now deprecated. Use `Field.random` instead.

## [0.2.1] - 2024-05-04

### Added
Expand Down
2 changes: 1 addition & 1 deletion syngular/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
from .qring import QuotientRing, QRing # noqa
from .tools import SingularException # noqa
from .field import Field # noqa
from .polynomial import Polynomial # noqa
from .polynomial import Polynomial, Monomial # noqa
50 changes: 31 additions & 19 deletions syngular/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ def __repr__(self):

def __call__(self, other):
"""Cast to field."""
if self.name == "mpc":
if self.name in ["mpc", "C"]:
return mpmath.mpc(mpmath.mpmathify(other))
elif self.name == "padic":
elif self.name in ["padic", "Qp"]:
return PAdic(other, self.characteristic, self.digits)
elif self.name == "finite field":
elif self.name in ["finite field", "Fp"]:
return ModP(other, self.characteristic)
elif self.name in ["rational", "Q"]:
return Q(other)
else:
raise NotImplementedError

Expand All @@ -51,7 +53,7 @@ def name(self):

@name.setter
def name(self, value):
if value not in ['mpc', 'gaussian rational', 'finite field', 'padic']:
if value not in ['rational', 'Q', 'mpc', 'C', 'gaussian rational', 'finite field', 'Fp', 'padic', 'Qp']:
raise Exception("Field must be one of 'mpc', 'gaussian rational', 'finite field', 'padic'.")
else:
self._name = value
Expand All @@ -69,7 +71,7 @@ def characteristic(self, value):

@property
def digits(self):
if self.name == 'mpc':
if self.name in ['mpc', 'C']:
return mpmath.mp.dps
else:
return self._digits
Expand All @@ -78,7 +80,7 @@ def digits(self):
def digits(self, value):
if value < 0:
raise Exception("Digits must be positive.")
elif self.name == 'mpc':
elif self.name in ['mpc', 'C']:
mpmath.mp.dps = value
else:
self._digits = value
Expand All @@ -88,7 +90,7 @@ def __contains__(self, other):

@property
def is_algebraically_closed(self):
if self.name == 'mpc':
if self.name in ['mpc', 'C']:
return True
else:
return False
Expand All @@ -97,30 +99,30 @@ def is_algebraically_closed(self):
def tollerance(self):
if self.name in ['gaussian rational', 'finite field']:
return 0
elif self.name == 'mpc':
elif self.name in ['mpc', 'C']:
return mpmath.mpf('10e-{}'.format(int(min([0.95 * mpmath.mp.dps, mpmath.mp.dps - 4]))))
elif self.name == 'padic':
elif self.name in ['padic', 'Qp']:
return PAdic(0, self.characteristic, 0, self.digits)

@property
def singular_notation(self):
if self.name == 'mpc':
if self.name in ['mpc', 'C']:
return '(complex,{},I)'.format(self.digits - 5)
elif self.name in ['finite field', 'padic']:
elif self.name in ['finite field', 'Fp', 'padic', 'Qp']:
return str(self.characteristic)
else:
return None

def sqrt(self, val):
if self.name == "finite field":
if self.name in ["finite field", 'Fp']:
if not isinstance(val, ModP):
val = self(val)
return finite_field_sqrt(val)
elif self.name == "padic":
elif self.name in ["padic", 'Qp']:
if not isinstance(val, PAdic):
val = self(val)
return padic_sqrt(val)
elif self.name == "mpc":
elif self.name in ["mpc", 'C']:
return mpmath.sqrt(val)
else:
raise Exception(f"Field not understood: {self.field.name}")
Expand All @@ -131,22 +133,32 @@ def j(self):

i = I = j

def random_element(self, shape=(1, )):
def random(self, shape=(1, )):
if shape == (1, ):
if self.name == "padic":
if self.name in ["padic", 'Qp']:
p, k = self.characteristic, self.digits
return PAdic(random.randrange(0, p ** k - 1), p, k)
elif self.name == "finite field":
elif self.name in ["finite field", 'Fp']:
p = self.characteristic
return ModP(random.randrange(0, p), p)
elif self.name == "mpc":
elif self.name in ["mpc", 'C']:
return mpmath.mpc(str(Q(random.randrange(-100, 101), random.randrange(1, 201))),
str(Q(random.randrange(-100, 101), random.randrange(1, 201))))
else:
raise NotImplementedError
else:
raise NotImplementedError

def random_element(self, *args, **kwargs):
import warnings
warnings.warn(
"random_element is deprecated and will be removed in a future version. "
"Use random instead.",
DeprecationWarning,
stacklevel=2
)
return self.random(*args, **kwargs)

def epsilon(self, shape=(1, ), ):
if shape == (1, ):
if not hasattr(self, "_ε"):
Expand All @@ -165,7 +177,7 @@ def epsilon(self, shape=(1, ), ):
@property
def ε(self):
if not hasattr(self, "_ε"):
self. = self.epsilon
self. = self.epsilon()
return self.

@ε.setter
Expand Down
7 changes: 5 additions & 2 deletions syngular/ideal.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .qring import QuotientRing
from .ideal_algorithms import Ideal_Algorithms
from .variety import Variety_of_Ideal
from .polynomial import Polynomial


class Ideal(Ideal_Algorithms, Variety_of_Ideal, object):
Expand Down Expand Up @@ -192,8 +193,10 @@ def __contains__(self, other):
"""Implements ideal membership."""
if isinstance(other, Ideal):
return self.__ideal_contains__(other)
elif isinstance(other, sympy.Expr) or isinstance(other, str):
elif isinstance(other, (str, sympy.Expr, Polynomial)):
return self.__poly_contains__(other)
else:
raise NotImplementedError

def __ideal_contains__(self, other):
qIdeal = self / other
Expand All @@ -203,7 +206,7 @@ def __ideal_contains__(self, other):
return False

def __poly_contains__(self, other):
if isinstance(other, sympy.Expr):
if not isinstance(other, str):
other = str(other)
assert isinstance(other, str)
singular_commands = [f"ring r = {self.ring};",
Expand Down
44 changes: 36 additions & 8 deletions syngular/polynomial.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import numpy
import operator
import re
import sympy

from collections import defaultdict
from multiset import FrozenMultiset
Expand Down Expand Up @@ -48,6 +49,9 @@ def __repr__(self):
def __str__(self):
return "*".join([f"{key}^{val}" for key, val in self.items()])

def tolist(self):
return [entry for key, val in self.items() for entry in [key, ] * val]

def subs(self, values_dict):
return functools.reduce(operator.mul, [values_dict[key] ** val for key, val in self.items()], 1)

Expand Down Expand Up @@ -77,12 +81,19 @@ class Polynomial(object):
# return self

def __init__(self, coeffs_and_monomials, field):
if isinstance(coeffs_and_monomials, str):
coeffs_and_monomials = self.__rstr__(coeffs_and_monomials, field)
elif coeffs_and_monomials in field:
coeffs_and_monomials = [(coeffs_and_monomials, Monomial(''))]
if isinstance(coeffs_and_monomials, str) or isinstance(coeffs_and_monomials, sympy.Basic):
coeffs_and_monomials = self.__rstr__(str(coeffs_and_monomials), field)
elif isinstance(coeffs_and_monomials, Polynomial):
coeffs_and_monomials = coeffs_and_monomials.coeffs_and_monomials
elif isinstance(coeffs_and_monomials, list):
assert all([isinstance(entry, tuple) for entry in coeffs_and_monomials])
assert all([len(entry) == 2 for entry in coeffs_and_monomials])
assert all([entry[0] in field for entry in coeffs_and_monomials if entry[0] != 0])
assert all([isinstance(entry[1], Monomial) for entry in coeffs_and_monomials])
elif coeffs_and_monomials in field:
coeffs_and_monomials = [(coeffs_and_monomials, Monomial(''))]
else:
raise NotImplementedError(f"Received {coeffs_and_monomials} \n of type {type(coeffs_and_monomials)}")
self.coeffs_and_monomials = coeffs_and_monomials
self._field = field

Expand All @@ -91,7 +102,7 @@ def __str__(self, for_repr=False):
if hasattr(self.field, "name") and self.field.name in ["padic", "finite field", ] else
f"{str(coeff) if not for_repr else repr(coeff)}") +
(f"*{monomial}" if str(monomial) != "" else "")
for coeff, monomial in self.coeffs_and_monomials).replace("+-", "-")
for coeff, monomial in self.coeffs_and_monomials).replace("+-", "-").replace("+ -", "- ")

def __repr__(self):
return f"Polynomial(\"{self.__str__(for_repr=True)}\", {self.field})"
Expand All @@ -107,6 +118,8 @@ def coeffs_and_monomials(self):
def coeffs_and_monomials(self, temp_coeffs_and_monomials):
self._coeffs_and_monomials = [(coeff, monomial) for coeff, monomial in temp_coeffs_and_monomials if coeff != 0]
self.reduce()
if self._coeffs_and_monomials == []: # ensure there is at least an entry
self._coeffs_and_monomials = [(0, Monomial(''))]

@property
def field(self):
Expand All @@ -128,6 +141,10 @@ def coeffs(self):
def coeffs(self, temp_coeffs):
self.coeffs_and_monomials = [(temp_coeff, monomial) for temp_coeff, (coeff, monomial) in zip(temp_coeffs, self.coeffs_and_monomials)]

@property
def monomials(self):
return [monomial for _, monomial in self.coeffs_and_monomials]

def rationalise(self):
from pyadic.finite_field import vec_chained_FF_rationalize
rat_coeffs = vec_chained_FF_rationalize([numpy.array(self.coeffs).astype(int), ], [self.field.characteristic, ]).tolist()
Expand All @@ -138,7 +155,7 @@ def rationalise(self):

def reduce(self):
"""Merges equal monomials"""
unequal_monoms = set([monom for coeff, monom in self.coeffs_and_monomials])
unequal_monoms = set([monom for _, monom in self.coeffs_and_monomials])
if len(unequal_monoms) == len(self.coeffs_and_monomials):
return # already reduced
new_coeffs_and_monomials = []
Expand All @@ -149,10 +166,12 @@ def reduce(self):

@staticmethod
def __rstr__(polynomial, field):
polynomial = polynomial.replace(" ", "").replace("+-", "-")
polynomial = re.sub(r"(\+|\-)I\*{0,1}([\d\.]+)", r"\1\2j", polynomial) # format complex nbrs
parentheses = [(("(", ), (")", )), (("{", ), ("}", )), (("[", "⟨", "<", ), ("]", "⟩", ">"))]
lopen_parentheses = [parenthesis[0] for parenthesis in parentheses]
lclos_parentheses = [parenthesis[1] for parenthesis in parentheses]
parentheses_balance = [0 for parenthesis in parentheses]
parentheses_balance = [0 for _ in parentheses]
next_match = ""
coeffs_and_monomials_strings = []
for char in polynomial:
Expand Down Expand Up @@ -201,12 +220,15 @@ def subs(self, base_point, field=None):
new_poly.reduce()
return new_poly

def __call__(self, *args, **kwargs):
return self.subs(*args, **kwargs)

def __len__(self):
return len(self.coeffs_and_monomials)

def __eq__(self, other):
if other == 0:
if self.coeffs_and_monomials == []:
if len(self) == 1 and self.coeffs[0] == 0 and self.monomials[0] == Monomial(''):
return True
else:
return False
Expand All @@ -229,6 +251,9 @@ def __add__(self, other):
else:
raise NotImplementedError(f"Operation: __add__; self: {self}; self class {self.__class__}; other: {other}; other class {other.__class__}.")

def __sub__(self, other):
return self + (-1 * other)

def __mul__(self, other):
if isinstance(other, Polynomial):
new_coeffs_and_monomials = []
Expand All @@ -245,6 +270,9 @@ def __mul__(self, other):
def __rmul__(self, other):
return self * other

def __neg__(self):
return -1 * self

def __pow__(self, n):
assert (isinstance(n, int) or n.is_integer())
if n < 0:
Expand Down
Loading

0 comments on commit 703c22f

Please sign in to comment.