Skip to content

Commit dd7661a

Browse files
Merge pull request #132 from Jake-Moss/fmpz_mpoly
Adding Fmpz mpoly #59, cont.
2 parents 7f1a1f9 + d4e6589 commit dd7661a

31 files changed

+3398
-473
lines changed

meson.build

+11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ gmp_dep = dependency('gmp')
99
mpfr_dep = dependency('mpfr')
1010
flint_dep = dependency('flint')
1111

12+
add_project_arguments(
13+
'-X', 'embedsignature=True',
14+
'-X', 'emit_code_comments=True',
15+
language : 'cython'
16+
)
17+
18+
if get_option('coverage')
19+
add_project_arguments('-X', 'linetrace=True', language : 'cython')
20+
add_project_arguments('-DCYTHON_TRACE=1', language : 'c')
21+
endif
22+
1223
# Add rpaths for a local build of flint found via pkgconfig
1324
# https://github.com/mesonbuild/meson/issues/13046
1425
if get_option('add_flint_rpath')

meson.options

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
option('coverage', type : 'boolean', value : false, description : 'enable coverage build')
12
option('add_flint_rpath', type : 'boolean', value : false)

setup.py

+5
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,14 @@
8181
("flint.flint_base.flint_context", ["src/flint/flint_base/flint_context.pyx"]),
8282

8383
("flint.types.fmpz", ["src/flint/types/fmpz.pyx"]),
84+
("flint.types.fmpz_vec", ["src/flint/types/fmpz_vec.pyx"]),
8485
("flint.types.fmpz_poly", ["src/flint/types/fmpz_poly.pyx"]),
8586
("flint.types.fmpz_mpoly", ["src/flint/types/fmpz_mpoly.pyx"]),
8687
("flint.types.fmpz_mat", ["src/flint/types/fmpz_mat.pyx"]),
8788
("flint.types.fmpz_series", ["src/flint/types/fmpz_series.pyx"]),
8889

8990
("flint.types.fmpq", ["src/flint/types/fmpq.pyx"]),
91+
("flint.types.fmpq_vec", ["src/flint/types/fmpq_vec.pyx"]),
9092
("flint.types.fmpq_poly", ["src/flint/types/fmpq_poly.pyx"]),
9193
("flint.types.fmpq_mat", ["src/flint/types/fmpq_mat.pyx"]),
9294
("flint.types.fmpq_series", ["src/flint/types/fmpq_series.pyx"]),
@@ -100,6 +102,9 @@
100102
("flint.types.fmpz_mod_poly", ["src/flint/types/fmpz_mod_poly.pyx"]),
101103
("flint.types.fmpz_mod_mat", ["src/flint/types/fmpz_mod_mat.pyx"]),
102104

105+
("flint.types.fmpq_mpoly", ["src/flint/types/fmpq_mpoly.pyx"]),
106+
("flint.types.fmpz_mpoly_q", ["src/flint/types/fmpz_mpoly_q.pyx"]),
107+
103108
("flint.types.arf", ["src/flint/types/arf.pyx"]),
104109
("flint.types.arb", ["src/flint/types/arb.pyx"]),
105110
("flint.types.arb_poly", ["src/flint/types/arb_poly.pyx"]),

src/flint/__init__.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,26 @@
44
from .types.fmpz_poly import *
55
from .types.fmpz_mat import *
66
from .types.fmpz_series import *
7+
from .types.fmpz_vec import fmpz_vec
78

89
from .types.fmpq import *
910
from .types.fmpq_poly import *
1011
from .types.fmpq_mat import *
1112
from .types.fmpq_series import *
13+
from .types.fmpq_vec import fmpq_vec
1214

1315
from .types.nmod import *
1416
from .types.nmod_poly import *
1517
from .types.nmod_mat import *
1618
from .types.nmod_series import *
1719

18-
from .types.fmpz_mpoly import *
20+
from .types.fmpz_mpoly import fmpz_mpoly_ctx, fmpz_mpoly, fmpz_mpoly_vec
1921
from .types.fmpz_mod import *
2022
from .types.fmpz_mod_poly import *
2123
from .types.fmpz_mod_mat import fmpz_mod_mat
2224

25+
from .types.fmpq_mpoly import fmpq_mpoly_ctx, fmpq_mpoly, fmpq_mpoly_vec
26+
2327
from .types.arf import *
2428
from .types.arb import *
2529
from .types.arb_poly import *
@@ -36,6 +40,7 @@
3640
from .flint_base.flint_base import (
3741
FLINT_VERSION as __FLINT_VERSION__,
3842
FLINT_RELEASE as __FLINT_RELEASE__,
43+
Ordering,
3944
)
4045

4146
__version__ = '0.7.0a2'

src/flint/flint_base/flint_base.pxd

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from flint.flintlib.mpoly cimport ordering_t
2+
13
cdef class flint_elem:
24
pass
35

@@ -7,6 +9,10 @@ cdef class flint_scalar(flint_elem):
79
cdef class flint_poly(flint_elem):
810
pass
911

12+
cdef class flint_mpoly_context(flint_elem):
13+
cdef public object py_names
14+
cdef const char ** c_names
15+
1016
cdef class flint_mpoly(flint_elem):
1117
pass
1218

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

1622
cdef class flint_series(flint_elem):
1723
pass
24+
25+
cpdef enum Ordering:
26+
lex, deglex, degrevlex
27+
28+
cdef ordering_t ordering_py_to_c(ordering: Ordering)
29+
cdef ordering_c_to_py(ordering_t ordering)

src/flint/flint_base/flint_base.pyx

+178-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@ from flint.flintlib.flint cimport (
22
FLINT_BITS as _FLINT_BITS,
33
FLINT_VERSION as _FLINT_VERSION,
44
__FLINT_RELEASE as _FLINT_RELEASE,
5+
slong
56
)
7+
from flint.flintlib.mpoly cimport ordering_t
68
from flint.flint_base.flint_context cimport thectx
9+
from flint.flint_base.flint_base cimport Ordering
10+
from flint.utils.typecheck cimport typecheck
11+
cimport libc.stdlib
12+
13+
from typing import Optional
714

815

916
FLINT_BITS = _FLINT_BITS
@@ -114,16 +121,163 @@ cdef class flint_poly(flint_elem):
114121
v = - fac[0]
115122
roots.append((v, m))
116123
return roots
117-
124+
118125
def complex_roots(self):
119126
raise AttributeError("Complex roots are not supported for this polynomial")
120127

121128

129+
cdef class flint_mpoly_context(flint_elem):
130+
"""
131+
Base class for multivariate ring contexts
132+
"""
133+
134+
_ctx_cache = None
135+
136+
def __init__(self, int nvars, names):
137+
if nvars < 0:
138+
raise ValueError("cannot have a negative amount of variables")
139+
elif len(names) != nvars:
140+
raise ValueError("number of variables must match number of variable names")
141+
self.py_names = tuple(name.encode("ascii") if not isinstance(name, bytes) else name for name in names)
142+
self.c_names = <const char**> libc.stdlib.malloc(nvars * sizeof(const char *))
143+
for i in range(nvars):
144+
self.c_names[i] = self.py_names[i]
145+
146+
def __dealloc__(self):
147+
libc.stdlib.free(self.c_names)
148+
self.c_names = NULL
149+
150+
def __str__(self):
151+
return self.__repr__()
152+
153+
def __repr__(self):
154+
return f"{self.__class__.__name__}({self.nvars()}, '{repr(self.ordering())}', {self.names()})"
155+
156+
def name(self, long i):
157+
if not 0 <= i < len(self.py_names):
158+
raise IndexError("variable name index out of range")
159+
return self.py_names[i].decode("ascii")
160+
161+
def names(self):
162+
return tuple(name.decode("ascii") for name in self.py_names)
163+
164+
def gens(self):
165+
return tuple(self.gen(i) for i in range(self.nvars()))
166+
167+
def variable_to_index(self, var: Union[int, str]):
168+
"""Convert a variable name string or possible index to its index in the context."""
169+
if isinstance(var, str):
170+
try:
171+
i = self.names().index(var)
172+
except ValueError:
173+
raise ValueError("variable not in context")
174+
elif isinstance(var, int):
175+
if not 0 <= var < self.nvars():
176+
raise IndexError("generator index out of range")
177+
i = var
178+
else:
179+
raise TypeError("invalid variable type")
180+
181+
return i
182+
183+
@staticmethod
184+
def create_variable_names(slong nvars, names: str):
185+
"""
186+
Create a tuple of variable names based on the comma separated `names` string.
187+
188+
If `names` contains a single value, and `nvars` > 1, then the variables are numbered, e.g.
189+
190+
>>> flint_mpoly_context.create_variable_names(3, "x")
191+
('x0', 'x1', 'x2')
192+
193+
"""
194+
nametup = tuple(name.strip() for name in names.split(','))
195+
if len(nametup) != nvars:
196+
if len(nametup) == 1:
197+
nametup = tuple(nametup[0] + str(i) for i in range(nvars))
198+
else:
199+
raise ValueError("number of variables does not equal number of names")
200+
return nametup
201+
202+
@classmethod
203+
def get_context(cls, slong nvars=1, ordering=Ordering.lex, names: Optional[str] = "x", nametup: Optional[tuple] = None):
204+
"""
205+
Retrieve a context via the number of variables, `nvars`, the ordering, `ordering`, and either a variable
206+
name string, `names`, or a tuple of variable names, `nametup`.
207+
"""
208+
209+
# A type hint of `ordering: Ordering` results in the error "TypeError: an integer is required" if a Ordering
210+
# object is not provided. This is pretty obtuse so we check it's type ourselves
211+
if not isinstance(ordering, Ordering):
212+
raise TypeError(f"`ordering` ('{ordering}') is not an instance of flint.Ordering")
213+
214+
if nametup is not None:
215+
key = nvars, ordering, nametup
216+
elif nametup is None and names is not None:
217+
key = nvars, ordering, cls.create_variable_names(nvars, names)
218+
else:
219+
raise ValueError("must provide either `names` or `nametup`")
220+
221+
ctx = cls._ctx_cache.get(key)
222+
if ctx is None:
223+
ctx = cls._ctx_cache.setdefault(key, cls(*key))
224+
return ctx
225+
226+
@classmethod
227+
def from_context(cls, ctx: flint_mpoly_context):
228+
return cls.get_context(
229+
nvars=ctx.nvars(),
230+
ordering=ctx.ordering(),
231+
names=None,
232+
nametup=ctx.names()
233+
)
234+
235+
122236
cdef class flint_mpoly(flint_elem):
123237
"""
124238
Base class for multivariate polynomials.
125239
"""
126240

241+
def leading_coefficient(self):
242+
return self.coefficient(0)
243+
244+
def to_dict(self):
245+
return {self.monomial(i): self.coefficient(i) for i in range(len(self))}
246+
247+
def __contains__(self, x):
248+
"""
249+
Returns True if `self` contains a term with exponent vector `x` and a non-zero coefficient.
250+
251+
>>> from flint import fmpq_mpoly_ctx, Ordering
252+
>>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, 'x')
253+
>>> p = ctx.from_dict({(0, 1): 2, (1, 1): 3})
254+
>>> (1, 1) in p
255+
True
256+
>>> (5, 1) in p
257+
False
258+
259+
"""
260+
return bool(self[x])
261+
262+
def __iter__(self):
263+
return iter(self.monoms())
264+
265+
def __pos__(self):
266+
return self
267+
268+
def terms(self):
269+
"""
270+
Return the exponent vectors and coefficient of each term.
271+
272+
>>> from flint import fmpq_mpoly_ctx, Ordering
273+
>>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, 'x')
274+
>>> f = ctx.from_dict({(0, 0): 1, (1, 0): 2, (0, 1): 3, (1, 1): 4})
275+
>>> list(f.terms())
276+
[((1, 1), 4), ((1, 0), 2), ((0, 1), 3), ((0, 0), 1)]
277+
278+
"""
279+
return zip(self.monoms(), self.coeffs())
280+
127281

128282
cdef class flint_series(flint_elem):
129283
"""
@@ -190,3 +344,26 @@ cdef class flint_mat(flint_elem):
190344

191345
# supports mpmath conversions
192346
tolist = table
347+
348+
349+
cdef ordering_t ordering_py_to_c(ordering): # Cython does not like an "Ordering" type hint here
350+
if not isinstance(ordering, Ordering):
351+
raise TypeError(f"`ordering` ('{ordering}') is not an instance of flint.Ordering")
352+
353+
if ordering == Ordering.lex:
354+
return ordering_t.ORD_LEX
355+
elif ordering == Ordering.deglex:
356+
return ordering_t.ORD_DEGLEX
357+
elif ordering == Ordering.degrevlex:
358+
return ordering_t.ORD_DEGREVLEX
359+
360+
361+
cdef ordering_c_to_py(ordering_t ordering):
362+
if ordering == ordering_t.ORD_LEX:
363+
return Ordering.lex
364+
elif ordering == ordering_t.ORD_DEGLEX:
365+
return Ordering.deglex
366+
elif ordering == ordering_t.ORD_DEGREVLEX:
367+
return Ordering.degrevlex
368+
else:
369+
raise ValueError("unimplemented term order %d" % ordering)

src/flint/flint_base/flint_context.pyx

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ cdef class FlintContext:
1818
self.threads = 1
1919
self.cap = 10
2020

21-
@property
21+
@property
2222
def prec(self):
2323
return self._prec
2424

src/flint/flintlib/fmpq.pxd

+8-38
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,22 @@
11
from flint.flintlib.flint cimport ulong, flint_rand_t, mp_bitcnt_t, slong, flint_bitcnt_t
22
from flint.flintlib.fmpz cimport fmpz_struct, fmpz_t
33

4+
cdef extern from *:
5+
"""
6+
/* An ugly hack to get around the ugly hack of renaming fmpq to avoid a c/python name collision */
7+
typedef fmpq fmpq_struct;
8+
"""
9+
410
cdef extern from "flint/fmpq.h":
511
ctypedef struct fmpq_struct:
612
fmpz_struct num
713
fmpz_struct den
14+
815
ctypedef fmpq_struct fmpq_t[1]
16+
917
fmpz_struct * fmpq_numref(fmpq_t x)
1018
fmpz_struct * fmpq_denref(fmpq_t x)
1119

12-
# from here on is parsed
13-
void fmpq_init(fmpq_t x)
14-
void fmpq_clear(fmpq_t x)
15-
void fmpq_canonicalise(fmpq_t res)
16-
void _fmpq_canonicalise(fmpz_t num, fmpz_t den)
17-
int fmpq_is_canonical(const fmpq_t x)
18-
int _fmpq_is_canonical(const fmpz_t num, const fmpz_t den)
19-
void fmpq_set(fmpq_t dest, const fmpq_t src)
20-
void fmpq_swap(fmpq_t op1, fmpq_t op2)
21-
void fmpq_neg(fmpq_t dest, const fmpq_t src)
22-
void fmpq_abs(fmpq_t dest, const fmpq_t src)
23-
void fmpq_zero(fmpq_t res)
24-
void fmpq_one(fmpq_t res)
25-
int fmpq_is_zero(const fmpq_t res)
26-
int fmpq_is_one(const fmpq_t res)
27-
int fmpq_is_pm1(const fmpq_t res)
28-
int fmpq_equal(const fmpq_t x, const fmpq_t y)
29-
int fmpq_sgn(const fmpq_t x)
30-
int fmpq_cmp(const fmpq_t x, const fmpq_t y)
31-
int fmpq_cmp_fmpz(const fmpq_t x, const fmpz_t y)
32-
int fmpq_cmp_ui(const fmpq_t x, ulong y)
33-
int fmpq_cmp_si(const fmpq_t x, slong y)
34-
int fmpq_equal_ui(const fmpq_t x, ulong y)
35-
int fmpq_equal_si(const fmpq_t x, slong y)
36-
void fmpq_height(fmpz_t height, const fmpq_t x)
37-
flint_bitcnt_t fmpq_height_bits(const fmpq_t x)
38-
void fmpq_set_fmpz_frac(fmpq_t res, const fmpz_t p, const fmpz_t q)
39-
# void fmpq_get_mpz_frac(mpz_t a, mpz_t b, fmpq_t c)
40-
void fmpq_set_si(fmpq_t res, slong p, ulong q)
41-
void _fmpq_set_si(fmpz_t rnum, fmpz_t rden, slong p, ulong q)
42-
void fmpq_set_ui(fmpq_t res, ulong p, ulong q)
43-
void _fmpq_set_ui(fmpz_t rnum, fmpz_t rden, ulong p, ulong q)
44-
# void fmpq_set_mpq(fmpq_t dest, const mpq_t src)
45-
void fmpq_set_str(fmpq_t dest, const char * s, int base)
46-
# void fmpq_init_set_mpz_frac_readonly(fmpq_t z, const mpz_t p, const mpz_t q)
47-
double fmpq_get_d(const fmpq_t f)
48-
# void fmpq_get_mpq(mpq_t dest, const fmpq_t src)
49-
# int fmpq_get_mpfr(mpfr_t dest, const fmpq_t src, mpfr_rnd_t rnd)
5020
# from here on is parsed
5121
void fmpq_init(fmpq_t x)
5222
void fmpq_clear(fmpq_t x)

0 commit comments

Comments
 (0)