Skip to content

Commit

Permalink
Merge pull request #132 from Jake-Moss/fmpz_mpoly
Browse files Browse the repository at this point in the history
Adding Fmpz mpoly #59, cont.
  • Loading branch information
oscarbenjamin authored Jul 8, 2024
2 parents 7f1a1f9 + d4e6589 commit dd7661a
Show file tree
Hide file tree
Showing 31 changed files with 3,398 additions and 473 deletions.
11 changes: 11 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ gmp_dep = dependency('gmp')
mpfr_dep = dependency('mpfr')
flint_dep = dependency('flint')

add_project_arguments(
'-X', 'embedsignature=True',
'-X', 'emit_code_comments=True',
language : 'cython'
)

if get_option('coverage')
add_project_arguments('-X', 'linetrace=True', language : 'cython')
add_project_arguments('-DCYTHON_TRACE=1', language : 'c')
endif

# Add rpaths for a local build of flint found via pkgconfig
# https://github.com/mesonbuild/meson/issues/13046
if get_option('add_flint_rpath')
Expand Down
1 change: 1 addition & 0 deletions meson.options
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
option('coverage', type : 'boolean', value : false, description : 'enable coverage build')
option('add_flint_rpath', type : 'boolean', value : false)
5 changes: 5 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,14 @@
("flint.flint_base.flint_context", ["src/flint/flint_base/flint_context.pyx"]),

("flint.types.fmpz", ["src/flint/types/fmpz.pyx"]),
("flint.types.fmpz_vec", ["src/flint/types/fmpz_vec.pyx"]),
("flint.types.fmpz_poly", ["src/flint/types/fmpz_poly.pyx"]),
("flint.types.fmpz_mpoly", ["src/flint/types/fmpz_mpoly.pyx"]),
("flint.types.fmpz_mat", ["src/flint/types/fmpz_mat.pyx"]),
("flint.types.fmpz_series", ["src/flint/types/fmpz_series.pyx"]),

("flint.types.fmpq", ["src/flint/types/fmpq.pyx"]),
("flint.types.fmpq_vec", ["src/flint/types/fmpq_vec.pyx"]),
("flint.types.fmpq_poly", ["src/flint/types/fmpq_poly.pyx"]),
("flint.types.fmpq_mat", ["src/flint/types/fmpq_mat.pyx"]),
("flint.types.fmpq_series", ["src/flint/types/fmpq_series.pyx"]),
Expand All @@ -100,6 +102,9 @@
("flint.types.fmpz_mod_poly", ["src/flint/types/fmpz_mod_poly.pyx"]),
("flint.types.fmpz_mod_mat", ["src/flint/types/fmpz_mod_mat.pyx"]),

("flint.types.fmpq_mpoly", ["src/flint/types/fmpq_mpoly.pyx"]),
("flint.types.fmpz_mpoly_q", ["src/flint/types/fmpz_mpoly_q.pyx"]),

("flint.types.arf", ["src/flint/types/arf.pyx"]),
("flint.types.arb", ["src/flint/types/arb.pyx"]),
("flint.types.arb_poly", ["src/flint/types/arb_poly.pyx"]),
Expand Down
7 changes: 6 additions & 1 deletion src/flint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@
from .types.fmpz_poly import *
from .types.fmpz_mat import *
from .types.fmpz_series import *
from .types.fmpz_vec import fmpz_vec

from .types.fmpq import *
from .types.fmpq_poly import *
from .types.fmpq_mat import *
from .types.fmpq_series import *
from .types.fmpq_vec import fmpq_vec

from .types.nmod import *
from .types.nmod_poly import *
from .types.nmod_mat import *
from .types.nmod_series import *

from .types.fmpz_mpoly import *
from .types.fmpz_mpoly import fmpz_mpoly_ctx, fmpz_mpoly, fmpz_mpoly_vec
from .types.fmpz_mod import *
from .types.fmpz_mod_poly import *
from .types.fmpz_mod_mat import fmpz_mod_mat

from .types.fmpq_mpoly import fmpq_mpoly_ctx, fmpq_mpoly, fmpq_mpoly_vec

from .types.arf import *
from .types.arb import *
from .types.arb_poly import *
Expand All @@ -36,6 +40,7 @@
from .flint_base.flint_base import (
FLINT_VERSION as __FLINT_VERSION__,
FLINT_RELEASE as __FLINT_RELEASE__,
Ordering,
)

__version__ = '0.7.0a2'
12 changes: 12 additions & 0 deletions src/flint/flint_base/flint_base.pxd
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from flint.flintlib.mpoly cimport ordering_t

cdef class flint_elem:
pass

Expand All @@ -7,6 +9,10 @@ cdef class flint_scalar(flint_elem):
cdef class flint_poly(flint_elem):
pass

cdef class flint_mpoly_context(flint_elem):
cdef public object py_names
cdef const char ** c_names

cdef class flint_mpoly(flint_elem):
pass

Expand All @@ -15,3 +21,9 @@ cdef class flint_mat(flint_elem):

cdef class flint_series(flint_elem):
pass

cpdef enum Ordering:
lex, deglex, degrevlex

cdef ordering_t ordering_py_to_c(ordering: Ordering)
cdef ordering_c_to_py(ordering_t ordering)
179 changes: 178 additions & 1 deletion src/flint/flint_base/flint_base.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ from flint.flintlib.flint cimport (
FLINT_BITS as _FLINT_BITS,
FLINT_VERSION as _FLINT_VERSION,
__FLINT_RELEASE as _FLINT_RELEASE,
slong
)
from flint.flintlib.mpoly cimport ordering_t
from flint.flint_base.flint_context cimport thectx
from flint.flint_base.flint_base cimport Ordering
from flint.utils.typecheck cimport typecheck
cimport libc.stdlib

from typing import Optional


FLINT_BITS = _FLINT_BITS
Expand Down Expand Up @@ -114,16 +121,163 @@ cdef class flint_poly(flint_elem):
v = - fac[0]
roots.append((v, m))
return roots

def complex_roots(self):
raise AttributeError("Complex roots are not supported for this polynomial")


cdef class flint_mpoly_context(flint_elem):
"""
Base class for multivariate ring contexts
"""

_ctx_cache = None

def __init__(self, int nvars, names):
if nvars < 0:
raise ValueError("cannot have a negative amount of variables")
elif len(names) != nvars:
raise ValueError("number of variables must match number of variable names")
self.py_names = tuple(name.encode("ascii") if not isinstance(name, bytes) else name for name in names)
self.c_names = <const char**> libc.stdlib.malloc(nvars * sizeof(const char *))
for i in range(nvars):
self.c_names[i] = self.py_names[i]

def __dealloc__(self):
libc.stdlib.free(self.c_names)
self.c_names = NULL

def __str__(self):
return self.__repr__()

def __repr__(self):
return f"{self.__class__.__name__}({self.nvars()}, '{repr(self.ordering())}', {self.names()})"

def name(self, long i):
if not 0 <= i < len(self.py_names):
raise IndexError("variable name index out of range")
return self.py_names[i].decode("ascii")

def names(self):
return tuple(name.decode("ascii") for name in self.py_names)

def gens(self):
return tuple(self.gen(i) for i in range(self.nvars()))

def variable_to_index(self, var: Union[int, str]):
"""Convert a variable name string or possible index to its index in the context."""
if isinstance(var, str):
try:
i = self.names().index(var)
except ValueError:
raise ValueError("variable not in context")
elif isinstance(var, int):
if not 0 <= var < self.nvars():
raise IndexError("generator index out of range")
i = var
else:
raise TypeError("invalid variable type")

return i

@staticmethod
def create_variable_names(slong nvars, names: str):
"""
Create a tuple of variable names based on the comma separated `names` string.
If `names` contains a single value, and `nvars` > 1, then the variables are numbered, e.g.
>>> flint_mpoly_context.create_variable_names(3, "x")
('x0', 'x1', 'x2')
"""
nametup = tuple(name.strip() for name in names.split(','))
if len(nametup) != nvars:
if len(nametup) == 1:
nametup = tuple(nametup[0] + str(i) for i in range(nvars))
else:
raise ValueError("number of variables does not equal number of names")
return nametup

@classmethod
def get_context(cls, slong nvars=1, ordering=Ordering.lex, names: Optional[str] = "x", nametup: Optional[tuple] = None):
"""
Retrieve a context via the number of variables, `nvars`, the ordering, `ordering`, and either a variable
name string, `names`, or a tuple of variable names, `nametup`.
"""

# A type hint of `ordering: Ordering` results in the error "TypeError: an integer is required" if a Ordering
# object is not provided. This is pretty obtuse so we check it's type ourselves
if not isinstance(ordering, Ordering):
raise TypeError(f"`ordering` ('{ordering}') is not an instance of flint.Ordering")

if nametup is not None:
key = nvars, ordering, nametup
elif nametup is None and names is not None:
key = nvars, ordering, cls.create_variable_names(nvars, names)
else:
raise ValueError("must provide either `names` or `nametup`")

ctx = cls._ctx_cache.get(key)
if ctx is None:
ctx = cls._ctx_cache.setdefault(key, cls(*key))
return ctx

@classmethod
def from_context(cls, ctx: flint_mpoly_context):
return cls.get_context(
nvars=ctx.nvars(),
ordering=ctx.ordering(),
names=None,
nametup=ctx.names()
)


cdef class flint_mpoly(flint_elem):
"""
Base class for multivariate polynomials.
"""

def leading_coefficient(self):
return self.coefficient(0)

def to_dict(self):
return {self.monomial(i): self.coefficient(i) for i in range(len(self))}

def __contains__(self, x):
"""
Returns True if `self` contains a term with exponent vector `x` and a non-zero coefficient.
>>> from flint import fmpq_mpoly_ctx, Ordering
>>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, 'x')
>>> p = ctx.from_dict({(0, 1): 2, (1, 1): 3})
>>> (1, 1) in p
True
>>> (5, 1) in p
False
"""
return bool(self[x])

def __iter__(self):
return iter(self.monoms())

def __pos__(self):
return self

def terms(self):
"""
Return the exponent vectors and coefficient of each term.
>>> from flint import fmpq_mpoly_ctx, Ordering
>>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, 'x')
>>> f = ctx.from_dict({(0, 0): 1, (1, 0): 2, (0, 1): 3, (1, 1): 4})
>>> list(f.terms())
[((1, 1), 4), ((1, 0), 2), ((0, 1), 3), ((0, 0), 1)]
"""
return zip(self.monoms(), self.coeffs())


cdef class flint_series(flint_elem):
"""
Expand Down Expand Up @@ -190,3 +344,26 @@ cdef class flint_mat(flint_elem):

# supports mpmath conversions
tolist = table


cdef ordering_t ordering_py_to_c(ordering): # Cython does not like an "Ordering" type hint here
if not isinstance(ordering, Ordering):
raise TypeError(f"`ordering` ('{ordering}') is not an instance of flint.Ordering")

if ordering == Ordering.lex:
return ordering_t.ORD_LEX
elif ordering == Ordering.deglex:
return ordering_t.ORD_DEGLEX
elif ordering == Ordering.degrevlex:
return ordering_t.ORD_DEGREVLEX


cdef ordering_c_to_py(ordering_t ordering):
if ordering == ordering_t.ORD_LEX:
return Ordering.lex
elif ordering == ordering_t.ORD_DEGLEX:
return Ordering.deglex
elif ordering == ordering_t.ORD_DEGREVLEX:
return Ordering.degrevlex
else:
raise ValueError("unimplemented term order %d" % ordering)
2 changes: 1 addition & 1 deletion src/flint/flint_base/flint_context.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ cdef class FlintContext:
self.threads = 1
self.cap = 10

@property
@property
def prec(self):
return self._prec

Expand Down
46 changes: 8 additions & 38 deletions src/flint/flintlib/fmpq.pxd
Original file line number Diff line number Diff line change
@@ -1,52 +1,22 @@
from flint.flintlib.flint cimport ulong, flint_rand_t, mp_bitcnt_t, slong, flint_bitcnt_t
from flint.flintlib.fmpz cimport fmpz_struct, fmpz_t

cdef extern from *:
"""
/* An ugly hack to get around the ugly hack of renaming fmpq to avoid a c/python name collision */
typedef fmpq fmpq_struct;
"""

cdef extern from "flint/fmpq.h":
ctypedef struct fmpq_struct:
fmpz_struct num
fmpz_struct den

ctypedef fmpq_struct fmpq_t[1]

fmpz_struct * fmpq_numref(fmpq_t x)
fmpz_struct * fmpq_denref(fmpq_t x)

# from here on is parsed
void fmpq_init(fmpq_t x)
void fmpq_clear(fmpq_t x)
void fmpq_canonicalise(fmpq_t res)
void _fmpq_canonicalise(fmpz_t num, fmpz_t den)
int fmpq_is_canonical(const fmpq_t x)
int _fmpq_is_canonical(const fmpz_t num, const fmpz_t den)
void fmpq_set(fmpq_t dest, const fmpq_t src)
void fmpq_swap(fmpq_t op1, fmpq_t op2)
void fmpq_neg(fmpq_t dest, const fmpq_t src)
void fmpq_abs(fmpq_t dest, const fmpq_t src)
void fmpq_zero(fmpq_t res)
void fmpq_one(fmpq_t res)
int fmpq_is_zero(const fmpq_t res)
int fmpq_is_one(const fmpq_t res)
int fmpq_is_pm1(const fmpq_t res)
int fmpq_equal(const fmpq_t x, const fmpq_t y)
int fmpq_sgn(const fmpq_t x)
int fmpq_cmp(const fmpq_t x, const fmpq_t y)
int fmpq_cmp_fmpz(const fmpq_t x, const fmpz_t y)
int fmpq_cmp_ui(const fmpq_t x, ulong y)
int fmpq_cmp_si(const fmpq_t x, slong y)
int fmpq_equal_ui(const fmpq_t x, ulong y)
int fmpq_equal_si(const fmpq_t x, slong y)
void fmpq_height(fmpz_t height, const fmpq_t x)
flint_bitcnt_t fmpq_height_bits(const fmpq_t x)
void fmpq_set_fmpz_frac(fmpq_t res, const fmpz_t p, const fmpz_t q)
# void fmpq_get_mpz_frac(mpz_t a, mpz_t b, fmpq_t c)
void fmpq_set_si(fmpq_t res, slong p, ulong q)
void _fmpq_set_si(fmpz_t rnum, fmpz_t rden, slong p, ulong q)
void fmpq_set_ui(fmpq_t res, ulong p, ulong q)
void _fmpq_set_ui(fmpz_t rnum, fmpz_t rden, ulong p, ulong q)
# void fmpq_set_mpq(fmpq_t dest, const mpq_t src)
void fmpq_set_str(fmpq_t dest, const char * s, int base)
# void fmpq_init_set_mpz_frac_readonly(fmpq_t z, const mpz_t p, const mpz_t q)
double fmpq_get_d(const fmpq_t f)
# void fmpq_get_mpq(mpq_t dest, const fmpq_t src)
# int fmpq_get_mpfr(mpfr_t dest, const fmpq_t src, mpfr_rnd_t rnd)
# from here on is parsed
void fmpq_init(fmpq_t x)
void fmpq_clear(fmpq_t x)
Expand Down
Loading

0 comments on commit dd7661a

Please sign in to comment.