@@ -2,6 +2,7 @@ from flint.flintlib.types.flint cimport (
22 FLINT_BITS as _FLINT_BITS,
33 FLINT_VERSION as _FLINT_VERSION,
44 __FLINT_RELEASE as _FLINT_RELEASE,
5+ slong,
56)
67from flint.utils.flint_exceptions import DomainError
78from flint.flintlib.types.mpoly cimport ordering_t
@@ -344,13 +345,20 @@ cdef class flint_mpoly_context(flint_elem):
344345 return tuple (self .gen(i) for i in range (self .nvars()))
345346
346347 def variable_to_index (self , var: Union[int , str]) -> int:
347- """Convert a variable name string or possible index to its index in the context."""
348+ """
349+ Convert a variable name string or possible index to its index in the context.
350+
351+ If ``var`` is negative , return the index of the ``self.nvars() + var``
352+ """
348353 if isinstance(var , str ):
349354 try :
350355 i = self .names().index(var)
351356 except ValueError :
352357 raise ValueError (" variable not in context" )
353358 elif isinstance (var, int ):
359+ if var < 0 :
360+ var = self .nvars() + var
361+
354362 if not 0 <= var < self .nvars():
355363 raise IndexError (" generator index out of range" )
356364 i = var
@@ -379,7 +387,7 @@ cdef class flint_mpoly_context(flint_elem):
379387 names = (names,)
380388
381389 for name in names:
382- if isinstance (name, str ):
390+ if isinstance (name, ( str , bytes) ):
383391 res.append(name)
384392 else :
385393 base, num = name
@@ -415,10 +423,14 @@ cdef class flint_mpoly_context(flint_elem):
415423 return ctx
416424
417425 @classmethod
418- def from_context (cls , ctx: flint_mpoly_context ):
426+ def from_context (cls , ctx: flint_mpoly_context , names = None , ordering = None ):
427+ """
428+ Get a new context from an existing one. Optionally override ``names`` or
429+ ``ordering``.
430+ """
419431 return cls .get(
420- ordering = ctx.ordering() ,
421- names = ctx.names() ,
432+ names = ctx.names() if names is None else names ,
433+ ordering = ctx.ordering() if ordering is None else names ,
422434 )
423435
424436 def _any_as_scalar (self , other ):
@@ -451,6 +463,47 @@ cdef class flint_mpoly_context(flint_elem):
451463 exp_vec = (0 ,) * self .nvars()
452464 return self .from_dict({tuple (exp_vec): coeff})
453465
466+ def drop_gens (self , *gens: str | int ):
467+ """
468+ Get a context with the specified generators removed.
469+
470+ >>> from flint import fmpz_mpoly_ctx
471+ >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z', 'a', 'b'))
472+ >>> ctx.drop_gens('x', -2)
473+ fmpz_mpoly_ctx(3, '<Ordering.lex: 'lex'>', ('y', 'z', 'b'))
474+ """
475+ nvars = self .nvars()
476+ gen_idxs = set (self .variable_to_index(i) for i in gens)
477+
478+ if len (gens) > nvars:
479+ raise ValueError (f" expected at most {nvars} unique generators, got {len(gens)}" )
480+
481+ remaining_gens = []
482+ for i in range (nvars):
483+ if i not in gen_idxs:
484+ remaining_gens.append(self .py_names[i])
485+
486+ return self .from_context(self , names = remaining_gens)
487+
488+ def infer_generator_mapping (self , ctx: flint_mpoly_context ):
489+ """
490+ Infer a mapping of generator indexes from this contexts generators, to the
491+ provided contexts generators. Inference is done based upon generator names.
492+
493+ >>> from flint import fmpz_mpoly_ctx
494+ >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z', 'a', 'b'))
495+ >>> ctx2 = fmpz_mpoly_ctx.get(('b', 'a'))
496+ >>> ctx.infer_generator_mapping(ctx2)
497+ {3: 1, 4: 0}
498+ """
499+ gens_to_idxs = {x: i for i, x in enumerate (self .names())}
500+ other_gens_to_idxs = {x: i for i, x in enumerate (ctx.names())}
501+ return {
502+ gens_to_idxs[k]: other_gens_to_idxs[k]
503+ for k in (gens_to_idxs.keys() & other_gens_to_idxs.keys())
504+ }
505+
506+
454507cdef class flint_mod_mpoly_context(flint_mpoly_context):
455508 @classmethod
456509 def _new_ (_ , flint_mod_mpoly_context self , names , prime_modulus ):
@@ -472,11 +525,15 @@ cdef class flint_mod_mpoly_context(flint_mpoly_context):
472525 return * super ().create_context_key(names, ordering), modulus
473526
474527 @classmethod
475- def from_context (cls , ctx: flint_mod_mpoly_context ):
528+ def from_context (cls , ctx: flint_mod_mpoly_context , names = None , ordering = None , modulus = None ):
529+ """
530+ Get a new context from an existing one. Optionally override ``names``,
531+ ``modulus``, or ``ordering``.
532+ """
476533 return cls .get(
477- names = ctx.names(),
478- modulus = ctx.modulus(),
479- ordering = ctx.ordering(),
534+ names = ctx.names() if names is None else names ,
535+ modulus = ctx.modulus() if modulus is None else modulus ,
536+ ordering = ctx.ordering() if ordering is None else ordering ,
480537 )
481538
482539 def is_prime (self ):
@@ -869,6 +926,88 @@ cdef class flint_mpoly(flint_elem):
869926 """
870927 return zip (self .monoms(), self .coeffs())
871928
929+ def drop_unused_gens (self ):
930+ """
931+ Remove unused generators from this polynomial. Returns a potentially new
932+ context, and potentially new polynomial.
933+
934+ A generator is unused if it's maximum degree is 0.
935+
936+ >>> from flint import fmpz_mpoly_ctx
937+ >>> ctx = fmpz_mpoly_ctx.get(('x', 4))
938+ >>> ctx2 = fmpz_mpoly_ctx.get(('x1', 'x2'))
939+ >>> f = sum(ctx.gens()[1:3])
940+ >>> f
941+ x1 + x2
942+ >>> new_ctx, new_f = f.drop_unused_gens()
943+ >>> new_ctx
944+ fmpz_mpoly_ctx(2, '<Ordering.lex: 'lex'>', ('x1', 'x2'))
945+ >>> new_f
946+ x1 + x2
947+ """
948+ new_ctx = self .context().drop_gens(
949+ * (i for i, x in enumerate (self .degrees()) if not x)
950+ )
951+ return new_ctx, self .coerce_to_context(new_ctx)
952+
953+ def coerce_to_context (self , ctx , mapping: dict[str | int , str | int] = None ):
954+ """
955+ Coerce this polynomial to a different context.
956+
957+ This is equivalent to composing this polynomial with the generators of another
958+ context. By default the mapping between contexts is inferred based on the name
959+ of the generators. Generators with names that are not found within the other
960+ context are mapped to 0. The mapping can be explicitly provided.
961+
962+ The resulting polynomial is also normalised.
963+
964+ >>> from flint import fmpz_mpoly_ctx
965+ >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'a', 'b'))
966+ >>> ctx2 = fmpz_mpoly_ctx.get(('a', 'b'))
967+ >>> x, y, a, b = ctx.gens()
968+ >>> f = x + 2*y + 3*a + 4*b
969+ >>> f.coerce_to_context(ctx2)
970+ 3*a + 4*b
971+ >>> f.coerce_to_context(ctx2, mapping={"x": "a", "b": 0})
972+ 5*a
973+ """
974+ cdef:
975+ slong * c_mapping
976+ slong i
977+
978+ if not typecheck(ctx, type (self .ctx)):
979+ raise ValueError (
980+ f" provided context is not a {self.ctx.__class__.__name__}"
981+ )
982+ elif self .ctx is ctx:
983+ return self
984+
985+ if mapping is None :
986+ mapping = self .ctx.infer_generator_mapping(ctx)
987+ else :
988+ mapping = {
989+ self .ctx.variable_to_index(k): ctx.variable_to_index(v)
990+ for k, v in mapping.items()
991+ }
992+
993+ try :
994+ c_mapping = < slong * > libc.stdlib.malloc(self .ctx.nvars() * sizeof(slong * ))
995+ if c_mapping is NULL :
996+ raise MemoryError (" malloc returned a null pointer" )
997+
998+ for i in range (self .ctx.nvars()):
999+ c_mapping[i] = < slong> - 1
1000+
1001+ for k, v in mapping.items():
1002+ c_mapping[k] = < slong> v
1003+
1004+ return self ._compose_gens_(ctx, c_mapping)
1005+ finally :
1006+ libc.stdlib.free(c_mapping)
1007+
1008+ cdef _compose_gens_(self , ctx, slong * mapping):
1009+ raise NotImplementedError (" abstract method" )
1010+
8721011
8731012cdef class flint_series(flint_elem):
8741013 """
0 commit comments