@@ -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 ordering ,
422434 )
423435
424436 def _any_as_scalar (self , other ):
@@ -451,6 +463,62 @@ 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: Iterable[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+ names = self .names()
482+ remaining_gens = []
483+ for i in range (nvars):
484+ if i not in gen_idxs:
485+ remaining_gens.append(names[i])
486+
487+ return self .from_context(self , names = remaining_gens)
488+
489+ def append_gens (self , *gens: str ):
490+ """
491+ Get a context with the specified generators appended.
492+
493+ >>> from flint import fmpz_mpoly_ctx
494+ >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z'))
495+ >>> ctx.append_gens('a', 'b')
496+ fmpz_mpoly_ctx(5, '<Ordering.lex: 'lex'>', ('x', 'y', 'z', 'a', 'b'))
497+ """
498+ return self .from_context(self , names = self .names() + gens)
499+
500+ def infer_generator_mapping (self , ctx: flint_mpoly_context ):
501+ """
502+ Infer a mapping of generator indexes from this contexts generators, to the
503+ provided contexts generators. Inference is done based upon generator names.
504+
505+ >>> from flint import fmpz_mpoly_ctx
506+ >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z', 'a', 'b'))
507+ >>> ctx2 = fmpz_mpoly_ctx.get(('b', 'a'))
508+ >>> mapping = ctx.infer_generator_mapping(ctx2)
509+ >>> mapping # doctest: +SKIP
510+ {3: 1, 4: 0}
511+ >>> list(sorted(mapping.items())) # Set ordering is not stable
512+ [(3, 1), (4, 0)]
513+ """
514+ gens_to_idxs = {x: i for i, x in enumerate (self .names())}
515+ other_gens_to_idxs = {x: i for i, x in enumerate (ctx.names())}
516+ return {
517+ gens_to_idxs[k]: other_gens_to_idxs[k]
518+ for k in (gens_to_idxs.keys() & other_gens_to_idxs.keys())
519+ }
520+
521+
454522cdef class flint_mod_mpoly_context(flint_mpoly_context):
455523 @classmethod
456524 def _new_ (_ , flint_mod_mpoly_context self , names , prime_modulus ):
@@ -472,11 +540,15 @@ cdef class flint_mod_mpoly_context(flint_mpoly_context):
472540 return * super ().create_context_key(names, ordering), modulus
473541
474542 @classmethod
475- def from_context (cls , ctx: flint_mod_mpoly_context ):
543+ def from_context (cls , ctx: flint_mod_mpoly_context , names = None , ordering = None , modulus = None ):
544+ """
545+ Get a new context from an existing one. Optionally override ``names``,
546+ ``modulus``, or ``ordering``.
547+ """
476548 return cls .get(
477- names = ctx.names(),
478- modulus = ctx.modulus(),
479- ordering = ctx.ordering(),
549+ names = ctx.names() if names is None else names ,
550+ modulus = ctx.modulus() if modulus is None else modulus ,
551+ ordering = ctx.ordering() if ordering is None else ordering ,
480552 )
481553
482554 def is_prime (self ):
@@ -869,6 +941,81 @@ cdef class flint_mpoly(flint_elem):
869941 """
870942 return zip (self .monoms(), self .coeffs())
871943
944+ def unused_gens (self ):
945+ """
946+ Report the unused generators from this polynomial.
947+
948+ A generator is unused if it's maximum degree is 0.
949+
950+ >>> from flint import fmpz_mpoly_ctx
951+ >>> ctx = fmpz_mpoly_ctx.get(('x', 4))
952+ >>> ctx2 = fmpz_mpoly_ctx.get(('x1', 'x2'))
953+ >>> f = sum(ctx.gens()[1:3])
954+ >>> f
955+ x1 + x2
956+ >>> f.unused_gens()
957+ ('x0', 'x3')
958+ """
959+ names = self .context().names()
960+ return tuple (names[i] for i, x in enumerate (self .degrees()) if not x)
961+
962+ def project_to_context (self , other_ctx , mapping: dict[str | int , str | int] = None ):
963+ """
964+ Project this polynomial to a different context.
965+
966+ This is equivalent to composing this polynomial with the generators of another
967+ context. By default the mapping between contexts is inferred based on the name
968+ of the generators. Generators with names that are not found within the other
969+ context are mapped to 0. The mapping can be explicitly provided.
970+
971+ >>> from flint import fmpz_mpoly_ctx
972+ >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'a', 'b'))
973+ >>> ctx2 = fmpz_mpoly_ctx.get(('a', 'b'))
974+ >>> x, y, a, b = ctx.gens()
975+ >>> f = x + 2*y + 3*a + 4*b
976+ >>> f.project_to_context(ctx2)
977+ 3*a + 4*b
978+ >>> f.project_to_context(ctx2, mapping={"x": "a", "b": 0})
979+ 5*a
980+ """
981+ cdef:
982+ slong * c_mapping
983+ slong i
984+
985+ ctx = self .context()
986+ if not typecheck(other_ctx, type (ctx)):
987+ raise ValueError (
988+ f" provided context is not a {ctx.__class__.__name__}"
989+ )
990+ elif ctx is other_ctx:
991+ return self
992+
993+ if mapping is None :
994+ mapping = ctx.infer_generator_mapping(other_ctx)
995+ else :
996+ mapping = {
997+ ctx.variable_to_index(k): other_ctx.variable_to_index(v)
998+ for k, v in mapping.items()
999+ }
1000+
1001+ try :
1002+ c_mapping = < slong * > libc.stdlib.malloc(ctx.nvars() * sizeof(slong * ))
1003+ if c_mapping is NULL :
1004+ raise MemoryError (" malloc returned a null pointer" )
1005+
1006+ for i in range (ctx.nvars()):
1007+ c_mapping[i] = < slong> - 1
1008+
1009+ for k, v in mapping.items():
1010+ c_mapping[k] = < slong> v
1011+
1012+ return self ._compose_gens_(other_ctx, c_mapping)
1013+ finally :
1014+ libc.stdlib.free(c_mapping)
1015+
1016+ cdef _compose_gens_(self , other_ctx, slong * mapping):
1017+ raise NotImplementedError (" abstract method" )
1018+
8721019
8731020cdef class flint_series(flint_elem):
8741021 """
0 commit comments