// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // orthogonal_classical.hpp // Classical orthogonal polynomials (Hermite / Laguerre / Chebyshev) // // Functions provided: // hermiteH(n, x) — Hermite polynomial H_n(x) (physicists' version, weight e^{-x²}) // hermiteHe(n, x) — Hermite polynomial He_n(x) (probabilists' version, weight e^{-x²/2}) // laguerreL(n, x) — Laguerre polynomial L_n(x) (weight e^{-x}, [0,∞)) // assocLaguerreL(n, α, x) — generalized Laguerre polynomial L_n^α(x) (weight x^α e^{-x}) // chebyshevT(n, x) — Chebyshev of the first kind T_n(x) (weight 1/√(1-x²)) // chebyshevU(n, x) — Chebyshev of the second kind U_n(x) (weight √(1-x²)) // // All computed stably via three-term recurrences (Bonnet/Favard). // // Relation to the existing jacobiP: // T_n(x) ∝ P_n^{(-1/2,-1/2)}(x), U_n(x) ∝ P_n^{(1/2,1/2)}(x) // The standalone implementation is faster with lower overhead (about one order of magnitude in the double-precision range). // // Supported types: // float, double, long double — direct recurrence // Float

— TODO: arbitrary-precision version (native only for now) // Audit: TODO/PLAN_CAS_PHASE2C_ALGEBRAIC_20260507.md is a separate matter; this implementation is // priority item 1 of the audit (project_sangi_special_functions_audit_20260507). #ifndef SANGI_SPECIAL_ORTHOGONAL_CLASSICAL_HPP #define SANGI_SPECIAL_ORTHOGONAL_CLASSICAL_HPP #include #include #include namespace sangi { namespace special { // ================================================================ // Hermite polynomial (physicists' version) H_n(x) // Recurrence: H_{n+1}(x) = 2x·H_n(x) - 2n·H_{n-1}(x) // H_0 = 1, H_1 = 2x // Orthogonality: ∫_{-∞}^{∞} H_m(x) H_n(x) e^{-x²} dx = √π · 2^n · n! · δ_{mn} // Eigenfunctions of the quantum harmonic oscillator // ================================================================ template [[nodiscard]] T hermiteH(int n, T x) { if (std::isnan(x)) return std::numeric_limits::quiet_NaN(); if (n < 0) return std::numeric_limits::quiet_NaN(); if (n == 0) return T(1); T two_x = T(2) * x; if (n == 1) return two_x; T h_prev = T(1); // H_0 T h_curr = two_x; // H_1 for (int k = 1; k < n; ++k) { T h_next = two_x * h_curr - T(2 * k) * h_prev; h_prev = h_curr; h_curr = h_next; } return h_curr; } // ================================================================ // Hermite polynomial (probabilists' version) He_n(x) // Recurrence: He_{n+1}(x) = x·He_n(x) - n·He_{n-1}(x) // He_0 = 1, He_1 = x // Orthogonality: ∫ He_m He_n e^{-x²/2} dx = √(2π) · n! · δ_{mn} // Relation: H_n(x) = 2^{n/2} · He_n(x·√2) // ================================================================ template [[nodiscard]] T hermiteHe(int n, T x) { if (std::isnan(x)) return std::numeric_limits::quiet_NaN(); if (n < 0) return std::numeric_limits::quiet_NaN(); if (n == 0) return T(1); if (n == 1) return x; T h_prev = T(1); // He_0 T h_curr = x; // He_1 for (int k = 1; k < n; ++k) { T h_next = x * h_curr - T(k) * h_prev; h_prev = h_curr; h_curr = h_next; } return h_curr; } // ================================================================ // Laguerre polynomial L_n(x) // Recurrence: (k+1) L_{k+1}(x) = (2k+1-x) L_k(x) - k L_{k-1}(x) // L_0 = 1, L_1 = 1 - x // Orthogonality: ∫_0^∞ L_m L_n e^{-x} dx = δ_{mn} // ================================================================ template [[nodiscard]] T laguerreL(int n, T x) { if (std::isnan(x)) return std::numeric_limits::quiet_NaN(); if (n < 0) return std::numeric_limits::quiet_NaN(); if (n == 0) return T(1); T l1 = T(1) - x; if (n == 1) return l1; T l_prev = T(1); T l_curr = l1; for (int k = 1; k < n; ++k) { T kf = T(k); T l_next = ((T(2) * kf + T(1) - x) * l_curr - kf * l_prev) / (kf + T(1)); l_prev = l_curr; l_curr = l_next; } return l_curr; } // ================================================================ // Generalized Laguerre polynomial L_n^α(x) // Recurrence: (k+1) L_{k+1}^α(x) = (2k+1+α-x) L_k^α(x) - (k+α) L_{k-1}^α(x) // L_0^α = 1, L_1^α = 1 + α - x // Orthogonality: ∫_0^∞ L_m^α L_n^α x^α e^{-x} dx = Γ(n+α+1)/n! · δ_{mn} // Used in the radial wavefunction of the hydrogen atom (α depends on the azimuthal quantum number) // ================================================================ template [[nodiscard]] T assocLaguerreL(int n, T alpha, T x) { if (std::isnan(x) || std::isnan(alpha)) return std::numeric_limits::quiet_NaN(); if (n < 0) return std::numeric_limits::quiet_NaN(); if (n == 0) return T(1); T l1 = T(1) + alpha - x; if (n == 1) return l1; T l_prev = T(1); T l_curr = l1; for (int k = 1; k < n; ++k) { T kf = T(k); T l_next = ((T(2) * kf + T(1) + alpha - x) * l_curr - (kf + alpha) * l_prev) / (kf + T(1)); l_prev = l_curr; l_curr = l_next; } return l_curr; } // ================================================================ // Chebyshev of the first kind T_n(x) // Recurrence: T_{n+1}(x) = 2x·T_n(x) - T_{n-1}(x) // T_0 = 1, T_1 = x // Orthogonality: ∫_{-1}^{1} T_m T_n / √(1-x²) dx = δ_{mn} · π/(2 if n>0, 1 if n=0) // Relation: T_n(cos θ) = cos(nθ) // Stable for |x| ≤ 1; the recurrence still works for |x| > 1 (can also be computed via cosh) // ================================================================ template [[nodiscard]] T chebyshevT(int n, T x) { if (std::isnan(x)) return std::numeric_limits::quiet_NaN(); if (n < 0) return std::numeric_limits::quiet_NaN(); if (n == 0) return T(1); if (n == 1) return x; T t_prev = T(1); // T_0 T t_curr = x; // T_1 T two_x = T(2) * x; for (int k = 1; k < n; ++k) { T t_next = two_x * t_curr - t_prev; t_prev = t_curr; t_curr = t_next; } return t_curr; } // ================================================================ // Chebyshev of the second kind U_n(x) // Recurrence: U_{n+1}(x) = 2x·U_n(x) - U_{n-1}(x) // U_0 = 1, U_1 = 2x // Orthogonality: ∫_{-1}^{1} U_m U_n √(1-x²) dx = (π/2)·δ_{mn} // Relation: U_n(cos θ) = sin((n+1)θ) / sin θ // ================================================================ template [[nodiscard]] T chebyshevU(int n, T x) { if (std::isnan(x)) return std::numeric_limits::quiet_NaN(); if (n < 0) return std::numeric_limits::quiet_NaN(); if (n == 0) return T(1); T two_x = T(2) * x; if (n == 1) return two_x; T u_prev = T(1); // U_0 T u_curr = two_x; // U_1 for (int k = 1; k < n; ++k) { T u_next = two_x * u_curr - u_prev; u_prev = u_curr; u_curr = u_next; } return u_curr; } } // namespace special } // namespace sangi #endif // SANGI_SPECIAL_ORTHOGONAL_CLASSICAL_HPP