FLINT3.7.0-dev

Composition and Taylor shifts

Synopsis#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)source
void nmod_poly_compose(nmod_poly_t res, const nmod_poly_t poly1, const nmod_poly_t poly2)source

Set res to the composition \mathit{poly1}(\mathit{poly2}). The underscore version requires \mathit{len1}, \mathit{len2} \ge 1, writes (\mathit{len1} - 1)(\mathit{len2} - 1) + 1 coefficients and does not allow aliasing.

A constant poly2 gives an evaluation, a binomial a x^k + c a substitution of the monomial and a Taylor shift, Horner's rule is used for \mathit{len1} \le 7, and otherwise the divide-and-conquer algorithm that splits poly1 into halves and multiplies by precomputed powers \mathit{poly2}^{2^k}, in O(M(N) \log N) operations for output length N. 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)source
void nmod_poly_compose_horner(nmod_poly_t res, const nmod_poly_t poly1, const nmod_poly_t poly2)source

The composition by Horner's rule, with the conventions of _nmod_poly_compose.

void _nmod_poly_taylor_shift(nn_ptr poly, ulong c, slong len, nmod_t mod)source
void nmod_poly_taylor_shift(nmod_poly_t g, const nmod_poly_t f, ulong c)source

Set g(x) = f(x + c), for reduced c; the underscore version works in place on (poly, len). Uses Horner's rule for \mathit{len} < 100 or \mathit{len} > n, and for c = \pm 1 and \mathit{len} < 1000, and the convolution method otherwise, which requires (\mathit{len} - 1)! to be invertible modulo n (for prime n, guaranteed by \mathit{len} \le n); otherwise FLINT_IMPINV is thrown.

void _nmod_poly_taylor_shift_horner(nn_ptr poly, ulong c, slong len, nmod_t mod)source
void nmod_poly_taylor_shift_horner(nmod_poly_t g, const nmod_poly_t f, ulong c)source

The Taylor shift by Horner's rule, with O(\mathit{len}^2) additions and multiplications. Works for every modulus.

void _nmod_poly_taylor_shift_convolution(nn_ptr poly, ulong c, slong len, nmod_t mod)source
void nmod_poly_taylor_shift_convolution(nmod_poly_t g, const nmod_poly_t f, ulong c)source

The Taylor shift as one product of length len, using \sum_k f_k (x + c)^k = \sum_j \frac{x^j}{j!} \sum_k k!\, f_k \frac{c^{k-j}}{(k-j)!} [ASU1975] [GG1997], in O(M(\mathit{len})) operations. Requires (\mathit{len} - 1)! to be invertible modulo n.

The composition f(g) of polynomials of degrees d and e has degree de, so even writing it down costs de operations. nmod_poly_compose computes it in O(M(de) \log d) operations, close to this bound. The most common special case, g = x + c, is a Taylor shift, which changes the basis from powers of x to powers of x - c and costs only one multiplication of length d. For compositions modulo a third polynomial see Modular composition, and for power series composition nmod_poly_compose_series.

AlgorithmComposition and modular compositionComputing f(g), f(g) \bmod x^n and f(g) \bmod h by Horner's rule, divide and conquer, Taylor shifts, the baby-step giant-step method of Brent and Kung, and the algorithm of Kinoshita and Li, with the choices made by nmod_poly.

Algorithms#

nmod_poly_compose handles constant g by an evaluation, and g = a x^k + c by a Taylor shift followed by scaling the coefficients by powers of a and spreading them out. Otherwise it uses Horner's rule, f(g) = (\cdots(f_d g + f_{d-1}) g + \cdots) g + f_0, for d \le 6, and the divide-and-conquer algorithm for larger d: with f = f_{\mathrm{lo}} + x^{k} f_{\mathrm{hi}} for k a power of two about d/2,

f(g) = f_{\mathrm{lo}}(g) + g^{k} f_{\mathrm{hi}}(g),

where the powers g^{2^j} are computed once. The last level multiplies polynomials of length about de/2, which dominates the cost.

The Taylor shift f(x + c) by Horner's rule takes d^2/2 multiply-adds (only additions for c = \pm 1) and works for every modulus. The convolution method [ASU1975] [GG1997] uses

f(x + c) = \sum_j \frac{x^j}{j!} \sum_{k \ge j} \bigl(k!\, f_k\bigr) \frac{c^{k-j}}{(k-j)!},

in which the inner sums are the coefficients of one product of length d + 1, after reversing (k! f_k). It divides by d!, so it needs a prime modulus larger than d (or one without prime factors up to d); 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 c = \pm 1 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, f(g)(c) = f(g(c)), and the Taylor shift by c moves the roots by -c:

Examplenmod_poly_compose_basic.cchecked by make checkdoc
#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;
}
Output
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.
Cite this page

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-10-20).