Composition and Taylor shifts
#include <flint/nmod_poly.h>void _nmod_poly_compose(nn_ptr res, nn_srcptr poly1, slong len1, nn_srcptr poly2, slong len2, nmod_t mod)sourcevoid nmod_poly_compose(nmod_poly_t res, const nmod_poly_t poly1, const nmod_poly_t poly2)sourceSet res to the composition . The underscore version requires , writes coefficients and does not allow aliasing.
A constant poly2 gives an evaluation, a binomial a substitution of the monomial and a Taylor shift, Horner's rule is used for , and otherwise the divide-and-conquer algorithm that splits poly1 into halves and multiplies by precomputed powers , in operations for output length . See Composition and modular composition.
void _nmod_poly_compose_horner(nn_ptr res, nn_srcptr poly1, slong len1, nn_srcptr poly2, slong len2, nmod_t mod)sourcevoid nmod_poly_compose_horner(nmod_poly_t res, const nmod_poly_t poly1, const nmod_poly_t poly2)sourceThe composition by Horner's rule, with the conventions of _nmod_poly_compose.
void nmod_poly_taylor_shift(nmod_poly_t g, const nmod_poly_t f, ulong c)sourceSet , for reduced ; the underscore version works in place on (poly, len). Uses Horner's rule for or , and for and , and the convolution method otherwise, which requires to be invertible modulo (for prime , guaranteed by ); otherwise FLINT_IMPINV is thrown.
void nmod_poly_taylor_shift_horner(nmod_poly_t g, const nmod_poly_t f, ulong c)sourceThe Taylor shift by Horner's rule, with additions and multiplications. Works for every modulus.
void nmod_poly_taylor_shift_convolution(nmod_poly_t g, const nmod_poly_t f, ulong c)sourceThe composition of polynomials of degrees and has degree , so even writing it down costs operations. nmod_poly_compose computes it in operations, close to this bound. The most common special case, , is a Taylor shift, which changes the basis from powers of to powers of and costs only one multiplication of length . For compositions modulo a third polynomial see Modular composition, and for power series composition nmod_poly_compose_series.
nmod_poly.
Algorithms#
nmod_poly_compose handles constant by an evaluation, and by a Taylor shift followed by scaling the coefficients by powers of and spreading them out. Otherwise it uses Horner's rule, , for , and the divide-and-conquer algorithm for larger : with for a power of two about ,
where the powers are computed once. The last level multiplies polynomials of length about , which dominates the cost.
The Taylor shift by Horner's rule takes multiply-adds (only additions for ) and works for every modulus. The convolution method [ASU1975] [GG1997] uses
in which the inner sums are the coefficients of one product of length , after reversing . It divides by , so it needs a prime modulus larger than (or one without prime factors up to ); for other moduli it throws FLINT_IMPINV. nmod_poly_taylor_shift uses Horner's rule for lengths below 100, for lengths larger than the modulus, and for below length 1000, and the convolution method otherwise. A composite modulus with a small prime factor and a length between 100 and the modulus therefore needs nmod_poly_taylor_shift_horner.
Examples#
Composition agrees with evaluation, , and the Taylor shift by moves the roots by :
#include <flint/flint.h>
#include <flint/nmod_poly.h>
int main(void)
{
flint_rand_t state;
nmod_poly_t f, g, h;
ulong p = 1000003, c = 12345, roots[3] = { 1, 2, 3 };
flint_rand_init(state);
nmod_poly_init(f, p);
nmod_poly_init(g, p);
nmod_poly_init(h, p);
nmod_poly_rand(f, state, 200);
nmod_poly_rand(g, state, 30);
nmod_poly_compose(h, f, g);
flint_printf("deg f(g) = %wd\n", nmod_poly_degree(h));
flint_printf("f(g)(c) == f(g(c)): %d\n",
nmod_poly_evaluate_nmod(h, c) ==
nmod_poly_evaluate_nmod(f, nmod_poly_evaluate_nmod(g, c)));
/* (x - 1)(x - 2)(x - 3) shifted by 10 has roots -9, -8, -7 */
nmod_poly_product_roots_nmod_vec(f, roots, 3);
nmod_poly_taylor_shift(g, f, 10);
flint_printf("f(x + 10) = "); nmod_poly_print_pretty(g, "x");
flint_printf("\ng(-9) = %wu\n", nmod_poly_evaluate_nmod(g, p - 9));
/* the two Taylor shift algorithms agree */
nmod_poly_rand(f, state, 5000);
nmod_poly_taylor_shift_horner(g, f, c);
nmod_poly_taylor_shift_convolution(h, f, c);
flint_printf("horner == convolution: %d\n", nmod_poly_equal(g, h));
nmod_poly_clear(f);
nmod_poly_clear(g);
nmod_poly_clear(h);
flint_rand_clear(state);
return 0;
}deg f(g) = 5771 f(g)(c) == f(g(c)): 1 f(x + 10) = x^3+24*x^2+191*x+504 g(-9) = 0 horner == convolution: 1
References#
- [ASU1975] A. V. Aho, K. Steiglitz and J. D. Ullman. Evaluating polynomials at fixed sets of points. SIAM Journal on Computing 4 (1975), 533–539.
- [GG1997] J. von zur Gathen and J. Gerhard. Fast algorithms for Taylor shifts and certain difference equations. ISSAC 1997, 40–47.
The FLINT team, “Composition and Taylor shifts (nmod_poly.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/nmod_poly/compose.html (generated 2026-11-02).
@misc{flint-doc-nmod-poly-compose,
author = {{The FLINT team}},
title = {{Composition and Taylor shifts (nmod\_poly.h)}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/nmod_poly/compose.html},
note = {Generated 2026-11-02}
}