@@ -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
68from 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
916FLINT_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+
122236cdef 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
128282cdef 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)
0 commit comments