// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // Polynomial.hpp // Polynomial template class // // Internal representation: ascending order (c[0] = constant term, c[n] = leading coefficient) // Design: // - Usable with T = double, float, Int, Float, Rational, Complex, etc. // - O(n) evaluation via Horner's method // - Polynomial division (quotient and remainder) // - Euclidean GCD // - Differentiation / integration // - Special polynomials: Chebyshev, Legendre, Hermite, Laguerre #pragma once #include #include #include #include #include #include #include #include #include #include namespace sangi { // ================================================================ // Concepts // ================================================================ template concept PolynomialCoeff = requires(T a, T b) { { a + b } -> std::convertible_to; { a - b } -> std::convertible_to; { a * b } -> std::convertible_to; { -a } -> std::convertible_to; { T(0) }; { T(1) }; }; // ================================================================ // Polynomial class // ================================================================ template class Polynomial { private: // Coefficient array (ascending order): coeffs_[i] is the coefficient of x^i. // Empty = zero polynomial. std::vector coeffs_; // Normalize by trimming trailing zero coefficients void normalize() { while (!coeffs_.empty() && coeffs_.back() == T(0)) coeffs_.pop_back(); } public: // ============================================================ // Constructors // ============================================================ // Zero polynomial Polynomial() = default; // Constant polynomial Polynomial(const T& c) { if (!(c == T(0))) coeffs_ = {c}; } // From a coefficient vector (ascending order) explicit Polynomial(std::vector coeffs) : coeffs_(std::move(coeffs)) { normalize(); } // From an initializer list (ascending order: coeffs_[i] = coefficient of x^i) // Example: {1, 2, 3} -> 1 + 2x + 3x^2 Polynomial(std::initializer_list il) { coeffs_.assign(il.begin(), il.end()); normalize(); } // ============================================================ // Accessors // ============================================================ // Degree (-1 means the zero polynomial) [[nodiscard]] int degree() const { return static_cast(coeffs_.size()) - 1; } // Whether this is the zero polynomial [[nodiscard]] bool isZero() const { return coeffs_.empty(); } // Leading coefficient (T(0) if zero polynomial) [[nodiscard]] T leadingCoefficient() const { return coeffs_.empty() ? T(0) : coeffs_.back(); } // Constant term [[nodiscard]] T constantTerm() const { return coeffs_.empty() ? T(0) : coeffs_[0]; } // Coefficient access (out of range returns T(0)) [[nodiscard]] const T& operator[](size_t i) const { static const T zero = T(0); return (i < coeffs_.size()) ? coeffs_[i] : zero; } // Coefficient reference access (auto-extends if out of range) T& operator[](size_t i) { if (i >= coeffs_.size()) coeffs_.resize(i + 1, T(0)); return coeffs_[i]; } // Access to the coefficient vector [[nodiscard]] const std::vector& coefficients() const { return coeffs_; } // ============================================================ // Comparison operators // ============================================================ [[nodiscard]] bool operator==(const Polynomial& rhs) const { return coeffs_ == rhs.coeffs_; } [[nodiscard]] bool operator!=(const Polynomial& rhs) const { return !(*this == rhs); } [[nodiscard]] bool operator==(const T& rhs) const { if (rhs == T(0)) return coeffs_.empty(); return coeffs_.size() == 1 && coeffs_[0] == rhs; } [[nodiscard]] bool operator!=(const T& rhs) const { return !(*this == rhs); } // ============================================================ // Unary operators // ============================================================ [[nodiscard]] Polynomial operator+() const { return *this; } [[nodiscard]] Polynomial operator-() const { Polynomial result; result.coeffs_.resize(coeffs_.size()); for (size_t i = 0; i < coeffs_.size(); ++i) result.coeffs_[i] = -coeffs_[i]; return result; } // ============================================================ // Polynomial + polynomial // ============================================================ [[nodiscard]] Polynomial operator+(const Polynomial& rhs) const { size_t n = std::max(coeffs_.size(), rhs.coeffs_.size()); Polynomial result; result.coeffs_.resize(n, T(0)); for (size_t i = 0; i < coeffs_.size(); ++i) result.coeffs_[i] = result.coeffs_[i] + coeffs_[i]; for (size_t i = 0; i < rhs.coeffs_.size(); ++i) result.coeffs_[i] = result.coeffs_[i] + rhs.coeffs_[i]; result.normalize(); return result; } [[nodiscard]] Polynomial operator-(const Polynomial& rhs) const { size_t n = std::max(coeffs_.size(), rhs.coeffs_.size()); Polynomial result; result.coeffs_.resize(n, T(0)); for (size_t i = 0; i < coeffs_.size(); ++i) result.coeffs_[i] = result.coeffs_[i] + coeffs_[i]; for (size_t i = 0; i < rhs.coeffs_.size(); ++i) result.coeffs_[i] = result.coeffs_[i] - rhs.coeffs_[i]; result.normalize(); return result; } // Polynomial multiplication O(n*m) [[nodiscard]] Polynomial operator*(const Polynomial& rhs) const { if (coeffs_.empty() || rhs.coeffs_.empty()) return Polynomial(); size_t n = coeffs_.size() + rhs.coeffs_.size() - 1; Polynomial result; result.coeffs_.resize(n, T(0)); for (size_t i = 0; i < coeffs_.size(); ++i) for (size_t j = 0; j < rhs.coeffs_.size(); ++j) result.coeffs_[i + j] = result.coeffs_[i + j] + coeffs_[i] * rhs.coeffs_[j]; result.normalize(); return result; } // ============================================================ // Polynomial division: this = quotient * rhs + remainder // ============================================================ struct DivResult { Polynomial quotient; Polynomial remainder; }; [[nodiscard]] DivResult divmod(const Polynomial& divisor) const { assert(!divisor.isZero() && "Division by zero polynomial"); if (degree() < divisor.degree()) return DivResult{Polynomial(), *this}; Polynomial rem = *this; int qDeg = degree() - divisor.degree(); Polynomial quot; quot.coeffs_.resize(qDeg + 1, T(0)); T lcDiv = divisor.leadingCoefficient(); int divDeg = divisor.degree(); for (int i = qDeg; i >= 0; --i) { T coeff = rem.coeffs_[i + divDeg] / lcDiv; quot.coeffs_[i] = coeff; for (int j = 0; j <= divDeg; ++j) rem.coeffs_[i + j] = rem.coeffs_[i + j] - coeff * divisor.coeffs_[j]; } rem.normalize(); quot.normalize(); return DivResult{std::move(quot), std::move(rem)}; } // Quotient of polynomial division (Euclidean division). // When SANGI_POLY_DIV_RATIONAL is defined, operator/ returns a // RationalFunction, so use this function when the quotient is explicitly // required. [[nodiscard]] Polynomial quo(const Polynomial& rhs) const { return divmod(rhs).quotient; } #ifndef SANGI_POLY_DIV_RATIONAL // Default: operator/ returns the polynomial quotient (same meaning as int / int). // Defining SANGI_POLY_DIV_RATIONAL makes it return a RationalFunction // instead (the definition is a free function in RationalFunction.hpp). [[nodiscard]] Polynomial operator/(const Polynomial& rhs) const { return divmod(rhs).quotient; } #endif [[nodiscard]] Polynomial operator%(const Polynomial& rhs) const { return divmod(rhs).remainder; } // ============================================================ // Polynomial + scalar / scalar + polynomial // ============================================================ [[nodiscard]] Polynomial operator+(const T& c) const { Polynomial result = *this; if (result.coeffs_.empty()) result.coeffs_.push_back(c); else result.coeffs_[0] = result.coeffs_[0] + c; result.normalize(); return result; } [[nodiscard]] Polynomial operator-(const T& c) const { return *this + (-c); } [[nodiscard]] Polynomial operator*(const T& c) const { if (c == T(0)) return Polynomial(); Polynomial result; result.coeffs_.resize(coeffs_.size()); for (size_t i = 0; i < coeffs_.size(); ++i) result.coeffs_[i] = coeffs_[i] * c; result.normalize(); return result; } [[nodiscard]] Polynomial operator/(const T& c) const { Polynomial result; result.coeffs_.resize(coeffs_.size()); for (size_t i = 0; i < coeffs_.size(); ++i) result.coeffs_[i] = coeffs_[i] / c; result.normalize(); return result; } friend Polynomial operator+(const T& lhs, const Polynomial& rhs) { return rhs + lhs; } friend Polynomial operator-(const T& lhs, const Polynomial& rhs) { return (-rhs) + lhs; } friend Polynomial operator*(const T& lhs, const Polynomial& rhs) { return rhs * lhs; } // ============================================================ // Compound assignment operators // ============================================================ Polynomial& operator+=(const Polynomial& rhs) { *this = *this + rhs; return *this; } Polynomial& operator-=(const Polynomial& rhs) { *this = *this - rhs; return *this; } Polynomial& operator*=(const Polynomial& rhs) { *this = *this * rhs; return *this; } Polynomial& operator/=(const Polynomial& rhs) { *this = *this / rhs; return *this; } Polynomial& operator%=(const Polynomial& rhs) { *this = *this % rhs; return *this; } Polynomial& operator+=(const T& c) { *this = *this + c; return *this; } Polynomial& operator-=(const T& c) { *this = *this - c; return *this; } Polynomial& operator*=(const T& c) { *this = *this * c; return *this; } Polynomial& operator/=(const T& c) { *this = *this / c; return *this; } // ============================================================ // Power // ============================================================ [[nodiscard]] Polynomial pow(int n) const { assert(n >= 0 && "Negative exponent not supported"); if (n == 0) return Polynomial(T(1)); Polynomial result(T(1)); Polynomial base = *this; unsigned int m = static_cast(n); while (m > 0) { if (m & 1) result *= base; base *= base; m >>= 1; } return result; } // ============================================================ // Evaluation (Horner's method) // ============================================================ // Compute P(x) by Horner's method: O(n) template [[nodiscard]] U operator()(const U& x) const { if (coeffs_.empty()) return U(0); U result = U(coeffs_.back()); for (int i = static_cast(coeffs_.size()) - 2; i >= 0; --i) result = result * x + U(coeffs_[i]); return result; } // Estrin's scheme (polynomial evaluation suited to SIMD / ILP) // Tree-based parallel evaluation: same result as Horner in O(n), but the // multiplication dependency chain is reduced to O(log n), giving higher ILP. // // Example: a0 + a1*x + a2*x^2 + a3*x^3 // = (a0 + a1*x) + x^2 * (a2 + a3*x) // level 0: p[i] = a[2i] + a[2i+1]*x (in parallel) // level 1: p[i] = p[2i] + p[2i+1]*x^2 (in parallel) // ... template [[nodiscard]] U evaluateEstrin(const U& x) const { size_t n = coeffs_.size(); if (n == 0) return U(0); if (n == 1) return U(coeffs_[0]); // Work array: initially a copy of the coefficients std::vector buf(n); for (size_t i = 0; i < n; ++i) buf[i] = U(coeffs_[i]); // Square x progressively U xpow = x; // level 0: x, level 1: x^2, level 2: x^4, ... while (n > 1) { size_t half = n / 2; for (size_t i = 0; i < half; ++i) buf[i] = buf[2 * i] + buf[2 * i + 1] * xpow; // When n is odd, leave the last element as-is if (n & 1) buf[half] = buf[n - 1]; n = half + (n & 1); xpow = xpow * xpow; } return buf[0]; } // ============================================================ // Differentiation / integration // ============================================================ // n-th derivative (n > 0) [[nodiscard]] Polynomial derivative(int n = 1) const { assert(n >= 0); Polynomial result = *this; for (int k = 0; k < n; ++k) { if (result.coeffs_.size() <= 1) return Polynomial(); Polynomial diff; diff.coeffs_.resize(result.coeffs_.size() - 1); for (size_t i = 1; i < result.coeffs_.size(); ++i) diff.coeffs_[i - 1] = result.coeffs_[i] * T(static_cast(i)); result = std::move(diff); } return result; } // Indefinite integral (constant of integration = 0) // Note: T must support division. [[nodiscard]] Polynomial integral() const { if (coeffs_.empty()) return Polynomial(); Polynomial result; result.coeffs_.resize(coeffs_.size() + 1, T(0)); result.coeffs_[0] = T(0); // Constant of integration = 0 for (size_t i = 0; i < coeffs_.size(); ++i) result.coeffs_[i + 1] = coeffs_[i] / T(static_cast(i + 1)); return result; } // ============================================================ // String conversion / output // ============================================================ [[nodiscard]] std::string toString(const std::string& var = "x") const { if (coeffs_.empty()) return "0"; std::string s; bool first = true; // Output in descending order for (int i = degree(); i >= 0; --i) { if (coeffs_[i] == T(0)) continue; std::ostringstream oss; oss << coeffs_[i]; std::string coefStr = oss.str(); if (!first && !coefStr.empty() && coefStr[0] != '-') s += "+"; if (i == 0) { s += coefStr; } else if (i == 1) { if (coefStr == "1") s += var; else if (coefStr == "-1") s += "-" + var; else s += coefStr + var; } else { if (coefStr == "1") s += var + "^" + std::to_string(i); else if (coefStr == "-1") s += "-" + var + "^" + std::to_string(i); else s += coefStr + var + "^" + std::to_string(i); } first = false; } return s.empty() ? "0" : s; } friend std::ostream& operator<<(std::ostream& os, const Polynomial& p) { return os << p.toString(); } }; // ================================================================ // Free functions // ================================================================ // --- Helpers for constructing polynomials --- // Monomial c * x^n template [[nodiscard]] Polynomial monomial(const T& c, int n) { assert(n >= 0); std::vector coeffs(n + 1, T(0)); coeffs[n] = c; return Polynomial(std::move(coeffs)); } // Variable x (= 1 * x^1) template [[nodiscard]] Polynomial X() { return monomial(T(1), 1); } // --- GCD --- // Integer types: use pseudo-remainder + primitive part to suppress coefficient explosion. // Floating-point types: use the Euclidean algorithm (divmod computes the quotient exactly). template [[nodiscard]] Polynomial gcd(Polynomial a, Polynomial b) { if (a.isZero()) return b; if (b.isZero()) return a; if constexpr (std::is_integral_v) { // Integer types: pseudo-remainder approach (avoids the problem that // integer divmod fails to reduce the remainder). // Use the primitive part to shrink coefficients. a = primitivePart(a); b = primitivePart(b); if (a.degree() < b.degree()) std::swap(a, b); while (!b.isZero()) { // Pseudo-remainder: remainder of (lc(b)^delta * a) divided by b // (delta = deg(a) - deg(b) + 1) int delta = a.degree() - b.degree() + 1; T lc_b = b.leadingCoefficient(); T scale = T(1); for (int i = 0; i < delta; ++i) scale *= lc_b; Polynomial r = (a * scale).divmod(b).remainder; if (r.isZero()) break; r = primitivePart(r); a = std::move(b); b = std::move(r); } auto& result = b.isZero() ? a : b; if (result.leadingCoefficient() < T(0)) result = -result; return result; } else { // Floating-point types: ordinary Euclidean algorithm while (!b.isZero()) { Polynomial r = a % b; a = std::move(b); b = std::move(r); } // Make monic (set the leading coefficient to 1) if (!a.isZero()) { T lc = a.leadingCoefficient(); if (!(lc == T(1))) a = a / lc; } return a; } } // --- Approximate GCD --- // Compute the GCD of polynomials whose coefficients carry numerical error. // Epsilon-Euclidean method: when the maximum absolute value of the remainder's // coefficients falls below tolerance, treat the preceding remainder as the GCD. /** * @brief Approximate GCD (epsilon-Euclidean method) * * Exact GCD's degree can change with tiny coefficient errors. This function * stops once the norm of the remainder falls at or below tolerance. Used in * signal processing, control theory, and numerical algebra. * * @param a input polynomial * @param b input polynomial * @param tolerance maximum absolute value of remainder coefficients below * which the remainder is considered zero * @return approximate GCD (monicized) */ template [[nodiscard]] Polynomial approximateGCD( Polynomial a, Polynomial b, double tolerance = 1e-10) { // Ensure |a| >= |b| if (a.degree() < b.degree()) std::swap(a, b); while (!b.isZero()) { Polynomial r = a % b; // Compute the maximum absolute value of the remainder's coefficients double max_coeff = 0.0; for (int i = 0; i <= r.degree(); ++i) { double c = std::abs(static_cast(r[i])); if (c > max_coeff) max_coeff = c; } // Stop if it is small enough if (max_coeff <= tolerance) break; a = std::move(b); b = std::move(r); } // Monicize if (!b.isZero()) { // If we stopped while b was still non-zero, the GCD candidate would // be the one before b (i.e. a), but due to the loop structure, a at // the stopping point is the previous b. Here a is the last non-zero // remainder. } if (!a.isZero()) { T lc = a.leadingCoefficient(); if (!(lc == T(1))) a = a / lc; } return a; } // --- Content: GCD of all coefficients --- // Integer polynomials only. Uses std::gcd. template [[nodiscard]] T content(const Polynomial& p) { static_assert(std::is_integral_v, "content() requires integral coefficient type"); if (p.isZero()) return T(0); T g = T(0); for (int i = 0; i <= p.degree(); ++i) { T c = p[i]; if (c < T(0)) c = -c; if (g == T(0)) g = c; else if (c != T(0)) g = std::gcd(g, c); } return (g == T(0)) ? T(1) : g; } // --- Primitive part: p / content(p) --- template [[nodiscard]] Polynomial primitivePart(const Polynomial& p) { static_assert(std::is_integral_v, "primitivePart() requires integral coefficient type"); T c = content(p); if (c == T(0) || c == T(1)) return p; return p / c; } // --- Definite integral --- template [[nodiscard]] T definiteIntegral(const Polynomial& p, const T& from, const T& to) { Polynomial F = p.integral(); return F(to) - F(from); } // --- Composition p(q(x)) --- template [[nodiscard]] Polynomial compose(const Polynomial& p, const Polynomial& q) { return p(q); } // ================================================================ // Special polynomials // ================================================================ // --- Chebyshev polynomials of the first kind --- // T_0 = 1, T_1 = x, T_n = 2x*T_{n-1} - T_{n-2} template [[nodiscard]] Polynomial chebyshevT(int n) { assert(n >= 0); if (n == 0) return Polynomial(T(1)); if (n == 1) return Polynomial({T(0), T(1)}); // x Polynomial twoX({T(0), T(2)}); // 2x Polynomial prev2(T(1)); // T_0 Polynomial prev1({T(0), T(1)}); // T_1 for (int i = 2; i <= n; ++i) { Polynomial curr = twoX * prev1 - prev2; prev2 = std::move(prev1); prev1 = std::move(curr); } return prev1; } // --- Chebyshev polynomials of the second kind --- // U_0 = 1, U_1 = 2x, U_n = 2x*U_{n-1} - U_{n-2} template [[nodiscard]] Polynomial chebyshevU(int n) { assert(n >= 0); if (n == 0) return Polynomial(T(1)); if (n == 1) return Polynomial({T(0), T(2)}); // 2x Polynomial twoX({T(0), T(2)}); // 2x Polynomial prev2(T(1)); // U_0 Polynomial prev1({T(0), T(2)}); // U_1 for (int i = 2; i <= n; ++i) { Polynomial curr = twoX * prev1 - prev2; prev2 = std::move(prev1); prev1 = std::move(curr); } return prev1; } // --- Legendre polynomials --- // P_0 = 1, P_1 = x, (n+1)P_{n+1} = (2n+1)x*P_n - n*P_{n-1} template [[nodiscard]] Polynomial legendre(int n) { assert(n >= 0); if (n == 0) return Polynomial(T(1)); if (n == 1) return Polynomial({T(0), T(1)}); // x Polynomial xPoly({T(0), T(1)}); // x Polynomial prev2(T(1)); // P_0 Polynomial prev1({T(0), T(1)}); // P_1 for (int i = 2; i <= n; ++i) { // P_i = ((2i-1)*x*P_{i-1} - (i-1)*P_{i-2}) / i Polynomial curr = (xPoly * prev1 * T(2 * i - 1) - prev2 * T(i - 1)) / T(i); prev2 = std::move(prev1); prev1 = std::move(curr); } return prev1; } // --- Hermite polynomials (probabilist's version: He) --- // H_0 = 1, H_1 = x, H_n = x*H_{n-1} - (n-1)*H_{n-2} // Note: this differs from the physicist's version (2x*H_{n-1} - 2(n-1)*H_{n-2}). template [[nodiscard]] Polynomial hermite(int n) { assert(n >= 0); if (n == 0) return Polynomial(T(1)); if (n == 1) return Polynomial({T(0), T(2)}); // 2x (physicist's version) Polynomial twoX({T(0), T(2)}); // 2x Polynomial prev2(T(1)); // H_0 Polynomial prev1({T(0), T(2)}); // H_1 = 2x for (int i = 2; i <= n; ++i) { // H_n = 2x*H_{n-1} - 2(n-1)*H_{n-2} (physicist's version) Polynomial curr = twoX * prev1 - prev2 * T(2 * (i - 1)); prev2 = std::move(prev1); prev1 = std::move(curr); } return prev1; } // --- Laguerre polynomials --- // L_0 = 1, L_1 = 1-x, L_n = ((2n-1-x)*L_{n-1} - (n-1)*L_{n-2}) / n template [[nodiscard]] Polynomial laguerre(int n) { assert(n >= 0); if (n == 0) return Polynomial(T(1)); if (n == 1) return Polynomial({T(1), T(-1)}); // 1-x Polynomial xPoly({T(0), T(1)}); // x Polynomial prev2(T(1)); // L_0 Polynomial prev1({T(1), T(-1)}); // L_1 = 1-x for (int i = 2; i <= n; ++i) { // L_i = ((2i-1-x)*L_{i-1} - (i-1)*L_{i-2}) / i Polynomial curr = ((Polynomial(T(2 * i - 1)) - xPoly) * prev1 - prev2 * T(i - 1)) / T(i); prev2 = std::move(prev1); prev1 = std::move(curr); } return prev1; } /// Bessel polynomial y_n(x) /// y_0(x) = 1, y_1(x) = x + 1 /// y_n(x) = (2n-1) * x * y_{n-1}(x) + y_{n-2}(x) /// Used in Bessel filter design and signal processing. template [[nodiscard]] Polynomial bessel(int n) { assert(n >= 0); if (n == 0) return Polynomial(T(1)); if (n == 1) return Polynomial({T(1), T(1)}); // 1 + x Polynomial xPoly({T(0), T(1)}); // x Polynomial prev2(T(1)); // y_0 Polynomial prev1({T(1), T(1)}); // y_1 = 1 + x for (int i = 2; i <= n; ++i) { // y_i = (2i-1)*x*y_{i-1} + y_{i-2} Polynomial curr = xPoly * prev1 * T(2 * i - 1) + prev2; prev2 = std::move(prev1); prev1 = std::move(curr); } return prev1; } /// Jacobi polynomial P_n^{(alpha,beta)}(x) /// P_0 = 1, P_1 = (alpha-beta)/2 + (alpha+beta+2)/2 * x /// (2n+alpha+beta)(2n+alpha+beta-1)/(2n(n+alpha+beta)) /// * ((2n+alpha+beta-1)*x + (alpha^2-beta^2)/(2n+alpha+beta-2)) * P_{n-1} /// - (n+alpha-1)(n+beta-1)(2n+alpha+beta) / (n(n+alpha+beta)(2n+alpha+beta-2)) * P_{n-2} template [[nodiscard]] Polynomial jacobi(int n, T alpha, T beta) { assert(n >= 0); if (n == 0) return Polynomial(T(1)); T ab = alpha + beta; if (n == 1) return Polynomial({(alpha - beta) / T(2), (ab + T(2)) / T(2)}); Polynomial xPoly({T(0), T(1)}); Polynomial prev2(T(1)); Polynomial prev1({(alpha - beta) / T(2), (ab + T(2)) / T(2)}); for (int i = 2; i <= n; ++i) { T ni = T(i), ab2 = T(2) * ni + ab; T c1 = ab2 * (ab2 - T(1)) / (T(2) * ni * (ni + ab)); T c2 = (alpha * alpha - beta * beta) / ((ab2 - T(2)) * ab2); T c3 = (ni + alpha - T(1)) * (ni + beta - T(1)) * ab2 / (ni * (ni + ab) * (ab2 - T(2))); Polynomial curr = (xPoly * c1 + Polynomial(c1 * c2)) * prev1 - prev2 * c3; prev2 = std::move(prev1); prev1 = std::move(curr); } return prev1; } /// Gegenbauer (ultraspherical) polynomial C_n^{(lambda)}(x) /// C_0 = 1, C_1 = 2*lambda*x /// C_n = (2(n+lambda-1)*x*C_{n-1} - (n+2*lambda-2)*C_{n-2}) / n template [[nodiscard]] Polynomial gegenbauer(int n, T lambda) { assert(n >= 0); if (n == 0) return Polynomial(T(1)); if (n == 1) return Polynomial({T(0), T(2) * lambda}); Polynomial xPoly({T(0), T(1)}); Polynomial prev2(T(1)); Polynomial prev1({T(0), T(2) * lambda}); for (int i = 2; i <= n; ++i) { Polynomial curr = (xPoly * prev1 * T(2 * (i + lambda - 1)) - prev2 * T(i + 2 * lambda - 2)) / T(i); prev2 = std::move(prev1); prev1 = std::move(curr); } return prev1; } // ================================================================ // Type traits // ================================================================ template struct is_polynomial : std::false_type {}; template struct is_polynomial> : std::true_type {}; template inline constexpr bool is_polynomial_v = is_polynomial::value; } // namespace sangi