Composition and Taylor shifts
#include <flint/fmpz_poly.h>void _fmpz_poly_compose(fmpz * res, const fmpz * poly1, slong len1, const fmpz * poly2, slong len2)sourcevoid fmpz_poly_compose(fmpz_poly_t res, const fmpz_poly_t poly1, const fmpz_poly_t poly2)sourceSet res to the composition g(h(x)) of g = poly1 and h = poly2. Inner polynomials of the form ax^n + b use a Taylor shift and a scaling, short outer polynomials Horner's rule, and others the divide-and-conquer algorithm. The underscore version requires positive lengths, writes (\mathit{len1} - 1)(\mathit{len2} - 1) + 1 coefficients and does not allow aliasing. See Composition and Taylor shifts.
void _fmpz_poly_compose_horner(fmpz * res, const fmpz * poly1, slong len1, const fmpz * poly2, slong len2)sourcevoid fmpz_poly_compose_horner(fmpz_poly_t res, const fmpz_poly_t poly1, const fmpz_poly_t poly2)sourcevoid _fmpz_poly_compose_divconquer(fmpz * res, const fmpz * poly1, slong len1, const fmpz * poly2, slong len2)sourcevoid fmpz_poly_compose_divconquer(fmpz_poly_t res, const fmpz_poly_t poly1, const fmpz_poly_t poly2)sourceComposition by Horner's rule, respectively by the divide-and-conquer algorithm that splits g into blocks evaluated at h and combines them with the powers h^{2^k}. The underscore versions have the conventions of _fmpz_poly_compose.
void fmpz_poly_taylor_shift(fmpz_poly_t g, const fmpz_poly_t f, const fmpz_t c)sourceSet g to f(x + c); the underscore version shifts (poly, n) in place. It uses Horner's scheme for length less than 64 and the divide-and-conquer algorithm otherwise.
void fmpz_poly_taylor_shift_horner(fmpz_poly_t g, const fmpz_poly_t f, const fmpz_t c)sourceTaylor shift by Horner's scheme, O(n^2) additions and multiplications by c (only additions for c = \pm 1).
void fmpz_poly_taylor_shift_divconquer(fmpz_poly_t g, const fmpz_poly_t f, const fmpz_t c)sourceTaylor shift by divide and conquer, f_0(x + c) + (x + c)^m f_1(x + c) with the powers of x + c computed by the binomial theorem; the two halves are shifted in parallel when threads are available. Short inputs use Horner's scheme, with a cutoff that grows with the size of the coefficients.
void fmpz_poly_taylor_shift_multi_mod(fmpz_poly_t g, const fmpz_poly_t f, const fmpz_t c)sourceTaylor shift by a multimodular algorithm: the shift is computed modulo enough word-size primes for a bound on the result and combined by the Chinese remainder theorem, using flint_get_num_threads threads.
fmpz_poly_compose computes g(h(x)), a polynomial of degree \deg g \cdot \deg h. The Taylor shift f(x + c) is the special case h = x + c, which occurs often enough (for example in root isolation) to have its own functions. For composition of power series, see fmpz_poly_compose_series.
nmod_poly.
Algorithms#
Horner's rule g(h) = (\cdots(g_m h + g_{m-1}) h + \cdots) h + g_0 performs m = \deg g products of increasingly unbalanced size. The divide-and-conquer algorithm groups the coefficients of g in pairs, g_{2i} + g_{2i+1} h, then combines pairs with h^2, quadruples with h^4 and so on, so that all products are balanced; with fast multiplication its cost is O(M(mn) \log m) for n = \deg h, against O(m M(mn)) for Horner's rule. fmpz_poly_compose uses Horner's rule for \operatorname{len}(g) \le 4 and divide and conquer otherwise, and reduces inner polynomials of the form a x^k + b to a Taylor shift, a scaling and an inflation.
For the Taylor shift, Horner's scheme f \mapsto f(x + c) costs O(n^2) additions and multiplications by c (only additions for c = \pm 1), on coefficients that grow to O(n \log |c| + n + b) bits. The divide-and-conquer algorithm writes f = f_0 + x^m f_1, shifts the halves recursively and computes (x + c)^m by the binomial theorem, so that the work is in a few large products [GG1997]. fmpz_poly_taylor_shift uses Horner's scheme below length 64 and divide and conquer otherwise; the divide-and-conquer function itself returns to Horner's scheme below a length between 100 and 1000 that grows with the coefficient size, and runs the two recursive halves in parallel when threads are available. fmpz_poly_taylor_shift_multi_mod computes the shift modulo many word-size primes in parallel and reconstructs the result, which is competitive for long polynomials with small coefficients and many threads.
Examples#
Composition with h = x^2 + 1 and a Taylor shift by -1; the shift of a polynomial by c and then by -c returns the original:
#include <flint/flint.h>
#include <flint/fmpz.h>
#include <flint/fmpz_poly.h>
int main(void)
{
fmpz_poly_t g, h, r, s;
fmpz_t c;
fmpz_poly_init(g);
fmpz_poly_init(h);
fmpz_poly_init(r);
fmpz_poly_init(s);
fmpz_init(c);
fmpz_poly_set_str(g, "3 1 -3 2"); /* 2x^2 - 3x + 1 */
fmpz_poly_set_str(h, "3 1 0 1"); /* x^2 + 1 */
fmpz_poly_compose(r, g, h);
flint_printf("g(h(x)) = "); fmpz_poly_print_pretty(r, "x"); flint_printf("\n");
fmpz_set_si(c, -1);
fmpz_poly_taylor_shift(r, g, c);
flint_printf("g(x - 1) = "); fmpz_poly_print_pretty(r, "x"); flint_printf("\n");
/* the shift agrees with composition by x + c */
fmpz_poly_chebyshev_t(g, 100);
fmpz_set_si(c, 7);
fmpz_poly_taylor_shift(r, g, c);
fmpz_poly_set_str(h, "2 7 1");
fmpz_poly_compose(s, g, h);
flint_printf("shift == compose: %d\n", fmpz_poly_equal(r, s));
fmpz_neg(c, c);
fmpz_poly_taylor_shift(r, r, c);
flint_printf("shift back: %d\n", fmpz_poly_equal(r, g));
fmpz_poly_clear(g);
fmpz_poly_clear(h);
fmpz_poly_clear(r);
fmpz_poly_clear(s);
fmpz_clear(c);
return 0;
}g(h(x)) = 2*x^4+x^2 g(x - 1) = 2*x^2-7*x+6 shift == compose: 1 shift back: 1
References#
- [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 (fmpz_poly.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/fmpz_poly/compose.html (generated 2026-10-20).
@misc{flint-doc-fmpz-poly-compose,
author = {{The FLINT team}},
title = {{Composition and Taylor shifts (fmpz\_poly.h)}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/fmpz_poly/compose.html},
note = {Generated 2026-10-20}
}