Skip to content

Commit 4bef177

Browse files
committed
Split __call__ methods
1 parent efb2c27 commit 4bef177

3 files changed

Lines changed: 183 additions & 189 deletions

File tree

src/flint/test/test_all.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2789,28 +2789,30 @@ def quick_poly():
27892789
assert P(1, ctx=ctx).total_degree() == 0
27902790

27912791
p = quick_poly()
2792-
assert p(0, 0) == p(0, S(0)) == p(S(0), S(0)) == p(x1=S(0), x0=S(0)) == S(1) == 1
2793-
assert p(1, 1) == p(x1=S(1), x0=S(1)) == S(10) == 10
2794-
assert p(x0=p(x1=0)) == mpoly({
2792+
assert p(0, 0) == p(0, S(0)) == p(S(0), S(0)) == S(1) == 1
2793+
assert p(1, 1) == S(10) == 10
2794+
2795+
assert p.subs({"x1": S(0), "x0": S(0)}) == ctx.from_dict({(0, 0): 1})
2796+
assert p.compose(p.subs({"x1": 0}), ctx.from_dict({(0, 1): 1})) == mpoly({
27952797
(2, 2): 36,
27962798
(1, 2): 24,
27972799
(1, 0): 9,
27982800
(0, 2): 4,
27992801
(0, 1): 2,
28002802
(0, 0): 4
28012803
})
2802-
assert p() == p
2803-
assert p(x0=ctx.from_dict({(1, 0): 1}), x1=ctx.from_dict({(0, 1): 1})) == p
2804+
assert p.compose(ctx.from_dict({(1, 0): 1}), ctx.from_dict({(0, 1): 1})) == p
28042805

2805-
assert raises(lambda: p(x0=None), TypeError)
2806-
assert raises(lambda: p(x0=None, x1=None), TypeError)
2806+
assert raises(lambda: p(None, None), TypeError)
28072807
assert raises(lambda: p(1), ValueError)
2808-
assert raises(lambda: p(1, x1=1), ValueError)
2809-
assert raises(lambda: p(a=1), ValueError)
28102808
assert raises(lambda: p(0, 1, 2), ValueError)
2811-
assert raises(lambda: p(x0=0, x1=1, x2=2), ValueError)
2812-
assert raises(lambda: p(x0=p, x1=P(ctx=ctx1)), IncompatibleContextError)
2813-
assert raises(lambda: P(ctx=ctx1)(x0=p), IncompatibleContextError)
2809+
2810+
assert raises(lambda: p.subs({"x0": None}), TypeError)
2811+
assert raises(lambda: p.subs({"x0": None, "x1": None}), TypeError)
2812+
assert raises(lambda: p.subs({"a": 1}), ValueError)
2813+
assert raises(lambda: p.subs({"x0": 0, "x1": 1, "x2": 2}), ValueError)
2814+
2815+
assert raises(lambda: p.compose(p, P(ctx=ctx1)), IncompatibleContextError)
28142816

28152817
assert bool(P(ctx=ctx)) is False
28162818
assert bool(P(1, ctx=ctx)) is True

src/flint/types/fmpq_mpoly.pyx

Lines changed: 73 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -576,103 +576,89 @@ cdef class fmpq_mpoly(flint_mpoly):
576576
def __rmod__(self, other):
577577
return divmod(other, self)[1]
578578

579-
def __call__(self, *args, **kwargs):
579+
def __call__(self, *args) -> fmpq:
580+
cdef:
581+
fmpq_vec V
582+
fmpq vres
583+
slong nvars = self.ctx.nvars(), nargs = len(args)
584+
585+
if nargs < nvars:
586+
raise ValueError("Not enough arguments provided")
587+
elif nargs > nvars:
588+
raise ValueError("More arguments provided than variables")
589+
590+
args_fmpq = tuple(any_as_fmpq(v) for v in args)
591+
for arg in args_fmpq:
592+
if arg is NotImplemented:
593+
raise TypeError(f"Cannot coerce argument ('{arg}') to fmpq")
594+
595+
V = fmpq_vec(args_fmpq, double_indirect=True)
596+
vres = fmpq.__new__(fmpq)
597+
if fmpq_mpoly_evaluate_all_fmpq(vres.val, self.val, V.double_indirect, self.ctx.val) == 0:
598+
raise ValueError("Unreasonably large polynomial") # pragma: no cover
599+
return vres
600+
601+
def subs(self, dict_args) -> fmpq_mpoly:
602+
"""
603+
Partial evaluate this polynomial.
604+
"""
580605
cdef:
581606
fmpq_mpoly res
582607
fmpq_mpoly res2
608+
slong i, nargs
609+
610+
partial_args = tuple((i, dict_args[x]) for i, x in enumerate(self.ctx.names()) if x in dict_args)
611+
nargs = len(partial_args)
612+
613+
# If we've been provided with an invalid keyword arg then the length of our filter
614+
# args will be less than what we've been provided with.
615+
# If the length is equal to the number of variables then all arguments have been provided
616+
# otherwise we need to do partial application
617+
if nargs < len(dict_args):
618+
raise ValueError("Unknown keyword argument provided")
619+
620+
args_fmpq = tuple(any_as_fmpq(v) for _, v in partial_args)
621+
for arg in args_fmpq:
622+
if arg is NotImplemented:
623+
raise TypeError(f"Cannot coerce argument ('{arg}') to fmpq")
624+
625+
# Partial application with args in Z. We evaluate the polynomial one variable at a time
626+
res = create_fmpq_mpoly(self.ctx)
627+
res2 = create_fmpq_mpoly(self.ctx)
628+
629+
fmpq_mpoly_set(res2.val, self.val, self.ctx.val)
630+
for (i, _), arg in zip(partial_args, args_fmpq):
631+
if fmpq_mpoly_evaluate_one_fmpq(res.val, res2.val, i, (<fmpq>arg).val, self.ctx.val) == 0:
632+
raise ValueError("Unreasonably large polynomial") # pragma: no cover
633+
fmpq_mpoly_set(res2.val, res.val, self.ctx.val)
634+
return res
635+
636+
def compose(self, *args) -> fmpq_mpoly:
637+
"""
638+
Compose this polynomial with other fmpq_mpolys. Takes positional arguments.
639+
"""
640+
cdef:
641+
fmpq_mpoly res
583642
fmpq_mpoly_ctx res_ctx
584-
fmpq_vec V
585-
fmpq vres
586643
fmpq_mpoly_vec C
587644
slong i, nvars = self.ctx.nvars(), nargs = len(args)
588645

589-
if not args and not kwargs:
590-
return self
591-
elif args and kwargs:
592-
raise ValueError("only supply positional or keyword arguments")
593-
elif len(args) < nvars and not kwargs:
594-
raise ValueError("partial application requires keyword arguments")
595-
596-
if kwargs:
597-
# Sort and filter the provided keyword args to only valid ones
598-
partial_args = tuple((i, kwargs[x]) for i, x in enumerate(self.ctx.names()) if x in kwargs)
599-
nargs = len(partial_args)
600-
601-
# If we've been provided with an invalid keyword arg then the length of our filter
602-
# args will be less than what we've been provided with.
603-
# If the length is equal to the number of variables then all arguments have been provided
604-
# otherwise we need to do partial application
605-
if nargs < len(kwargs):
606-
raise ValueError("unknown keyword argument provided")
607-
elif nargs == nvars:
608-
args = tuple(arg for _, arg in partial_args)
609-
kwargs = None
646+
if nargs < nvars:
647+
raise ValueError("Not enough arguments provided")
610648
elif nargs > nvars:
611-
raise ValueError("more arguments provided than variables")
649+
raise ValueError("More arguments provided than variables")
650+
elif not all(typecheck(arg, fmpq_mpoly) for arg in args):
651+
raise TypeError("All arguments must be fmpq_mpolys")
612652

613-
if kwargs:
614-
args_fmpq = tuple(any_as_fmpq(v) for _, v in partial_args)
615-
else:
616-
args_fmpq = tuple(any_as_fmpq(v) for v in args)
617-
618-
all_fmpq = NotImplemented not in args_fmpq
619-
620-
if args and all_fmpq:
621-
# Normal evaluation
622-
V = fmpq_vec(args_fmpq, double_indirect=True)
623-
vres = fmpq.__new__(fmpq)
624-
if fmpq_mpoly_evaluate_all_fmpq(vres.val, self.val, V.double_indirect, self.ctx.val) == 0:
625-
raise ValueError("unreasonably large polynomial") # pragma: no cover
626-
return vres
627-
elif kwargs and all_fmpq:
628-
# Partial application with args in Z. We evaluate the polynomial one variable at a time
629-
res = create_fmpq_mpoly(self.ctx)
630-
res2 = create_fmpq_mpoly(self.ctx)
653+
res_ctx = (<fmpq_mpoly> args[0]).ctx
654+
if not all((<fmpq_mpoly> args[i]).ctx is res_ctx for i in range(1, len(args))):
655+
raise IncompatibleContextError("All arguments must share the same context")
631656

632-
fmpq_mpoly_set(res2.val, self.val, self.ctx.val)
633-
for (i, _), arg in zip(partial_args, args_fmpq):
634-
if fmpq_mpoly_evaluate_one_fmpq(res.val, res2.val, i, (<fmpq>arg).val, self.ctx.val) == 0:
635-
raise ValueError("unreasonably large polynomial") # pragma: no cover
636-
fmpq_mpoly_set(res2.val, res.val, self.ctx.val)
637-
return res
638-
elif args and not all_fmpq:
639-
# Complete function composition
640-
if not all(typecheck(arg, fmpq_mpoly) for arg in args):
641-
raise TypeError("all arguments must be fmpq_mpolys")
642-
res_ctx = (<fmpq_mpoly> args[0]).ctx
643-
if not all((<fmpq_mpoly> args[i]).ctx is res_ctx for i in range(1, len(args))):
644-
raise IncompatibleContextError("all arguments must share the same context")
645-
646-
C = fmpq_mpoly_vec(args, self.ctx, double_indirect=True)
647-
res = create_fmpq_mpoly(self.ctx)
648-
if fmpq_mpoly_compose_fmpq_mpoly(res.val, self.val, C.double_indirect, self.ctx.val, res_ctx.val) == 0:
649-
raise ValueError("unreasonably large polynomial") # pragma: no cover
650-
return res
651-
else:
652-
# Partial function composition. We do this by composing with all arguments, however the ones
653-
# that have not be provided are set to the trivial monomial. This is why we require all the
654-
# polynomial itself, and all arguments exist in the same context, otherwise we have no way of
655-
# finding the correct monomial to use.
656-
if not all(typecheck(arg, fmpq_mpoly) for _, arg in partial_args):
657-
raise TypeError("all arguments must be fmpq_mpolys for partial composition")
658-
if not all((<fmpq_mpoly> arg).ctx is self.ctx for _, arg in partial_args):
659-
raise IncompatibleContextError("all arguments must share the same context")
660-
661-
polys = [None] * nvars
662-
for i, poly in partial_args:
663-
polys[i] = poly
664-
665-
for i in range(nvars):
666-
if polys[i] is None:
667-
vec = [0] * nvars
668-
vec[i] = 1
669-
polys[i] = self.ctx.from_dict({tuple(vec): 1})
670-
671-
C = fmpq_mpoly_vec(polys, self.ctx, double_indirect=True)
672-
res = create_fmpq_mpoly(self.ctx)
673-
if fmpq_mpoly_compose_fmpq_mpoly(res.val, self.val, C.double_indirect, self.ctx.val, self.ctx.val) == 0:
674-
raise ValueError("unreasonably large polynomial") # pragma: no cover
675-
return res
657+
C = fmpq_mpoly_vec(args, self.ctx, double_indirect=True)
658+
res = create_fmpq_mpoly(self.ctx)
659+
if fmpq_mpoly_compose_fmpq_mpoly(res.val, self.val, C.double_indirect, self.ctx.val, res_ctx.val) == 0:
660+
raise ValueError("Unreasonably large polynomial") # pragma: no cover
661+
return res
676662

677663
def context(self):
678664
"""

0 commit comments

Comments
 (0)