@@ -2,6 +2,7 @@ from flint.flintlib.types.flint cimport (
2
2
FLINT_BITS as _FLINT_BITS,
3
3
FLINT_VERSION as _FLINT_VERSION,
4
4
__FLINT_RELEASE as _FLINT_RELEASE,
5
+ slong,
5
6
)
6
7
from flint.utils.flint_exceptions import DomainError
7
8
from flint.flintlib.types.mpoly cimport ordering_t
@@ -344,13 +345,20 @@ cdef class flint_mpoly_context(flint_elem):
344
345
return tuple (self .gen(i) for i in range (self .nvars()))
345
346
346
347
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
+ """
348
353
if isinstance(var , str ):
349
354
try :
350
355
i = self .names().index(var)
351
356
except ValueError :
352
357
raise ValueError (" variable not in context" )
353
358
elif isinstance (var, int ):
359
+ if var < 0 :
360
+ var = self .nvars() + var
361
+
354
362
if not 0 <= var < self .nvars():
355
363
raise IndexError (" generator index out of range" )
356
364
i = var
@@ -379,7 +387,7 @@ cdef class flint_mpoly_context(flint_elem):
379
387
names = (names,)
380
388
381
389
for name in names:
382
- if isinstance (name, str ):
390
+ if isinstance (name, ( str , bytes) ):
383
391
res.append(name)
384
392
else :
385
393
base, num = name
@@ -415,10 +423,14 @@ cdef class flint_mpoly_context(flint_elem):
415
423
return ctx
416
424
417
425
@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
+ """
419
431
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 ,
422
434
)
423
435
424
436
def _any_as_scalar (self , other ):
@@ -451,6 +463,62 @@ cdef class flint_mpoly_context(flint_elem):
451
463
exp_vec = (0 ,) * self .nvars()
452
464
return self .from_dict({tuple (exp_vec): coeff})
453
465
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
+
454
522
cdef class flint_mod_mpoly_context(flint_mpoly_context):
455
523
@classmethod
456
524
def _new_ (_ , flint_mod_mpoly_context self , names , prime_modulus ):
@@ -472,11 +540,15 @@ cdef class flint_mod_mpoly_context(flint_mpoly_context):
472
540
return * super ().create_context_key(names, ordering), modulus
473
541
474
542
@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
+ """
476
548
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 ,
480
552
)
481
553
482
554
def is_prime (self ):
@@ -869,6 +941,81 @@ cdef class flint_mpoly(flint_elem):
869
941
"""
870
942
return zip (self .monoms(), self .coeffs())
871
943
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
+
872
1019
873
1020
cdef class flint_series(flint_elem):
874
1021
"""
0 commit comments