// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // RationalFunction_partialFractions.hpp // Partial-fraction expansion of rational functions (Heaviside's method) // // Usage: // #include // #include #pragma once #include #include #include #include namespace sangi { // ================================================================ // Struct holding the result of a partial-fraction expansion // ================================================================ /// Each term of the partial-fraction expansion: coefficient / (x - pole) template struct PartialFraction { Complex coefficient; // Residue Complex pole; // Pole }; /// Result of the partial-fraction expansion template struct PartialFractionExpansion { Polynomial quotient; // Polynomial part (when deg(num) >= deg(den)) std::vector> terms; // Each partial-fraction term bool success = false; // Whether the expansion succeeded }; // ================================================================ // Partial-fraction expansion (Heaviside's method) // ================================================================ /// Compute the partial-fraction expansion of the rational function f = P(x)/Q(x). /// Precondition: all roots of Q(x) are simple (multiple roots are unsupported) /// /// Result: f(x) = quotient(x) + Sum A_k / (x - r_k) /// where A_k = P(r_k) / Q'(r_k) (residue formula) /// /// T is assumed to be a floating-point type (double, float) template [[nodiscard]] PartialFractionExpansion partialFractions( const RationalFunction& f, T eps = std::numeric_limits::epsilon() * T(100), size_t maxIter = 1000) { static_assert(std::is_floating_point_v, "partialFractions requires floating-point coefficient type"); PartialFractionExpansion result; const auto& P = f.numerator(); const auto& Q = f.denominator(); if (Q.isZero()) { result.success = false; return result; } // When deg(P) >= deg(Q), extract the polynomial part via polynomial division Polynomial remainder = P; if (P.degree() >= Q.degree()) { auto dr = P.divmod(Q); result.quotient = std::move(dr.quotient); remainder = std::move(dr.remainder); } // If the denominator is constant, partial fractions are unnecessary if (Q.degree() <= 0) { result.success = true; return result; } // Find all roots of Q(x) auto roots = solvePolynomial(Q, eps, maxIter); // Compute Q'(x) Polynomial Qprime = Q.derivative(); // For each root r_k, compute the residue A_k = remainder(r_k) / Q'(r_k) result.terms.reserve(roots.size()); for (const auto& r : roots) { Complex numVal = remainder(r); Complex denVal = Qprime(r); if (abs(denVal) < eps) { // Q'(r_k) ~ 0 -> possible multiple root -> unsupported result.success = false; return result; } PartialFraction term; term.pole = r; term.coefficient = numVal / denVal; result.terms.push_back(term); } result.success = true; return result; } /// Convert the partial-fraction expansion to a string template [[nodiscard]] std::string toString(const PartialFractionExpansion& pfe, const std::string& var = "x") { if (!pfe.success) return "(expansion failed)"; std::ostringstream oss; bool first = true; // Polynomial part if (!pfe.quotient.isZero()) { oss << pfe.quotient.toString(var); first = false; } // Each partial-fraction term for (const auto& t : pfe.terms) { if (!first) oss << " + "; oss << "(" << t.coefficient << ")/(" << var << " - (" << t.pole << "))"; first = false; } if (first) oss << "0"; return oss.str(); } // ================================================================ // Enumerate poles (roots of the denominator) // ================================================================ /// Return the poles of the rational function f = P(x)/Q(x) (roots of Q(x) = 0) template [[nodiscard]] std::vector> poles( const RationalFunction& f, T eps = std::numeric_limits::epsilon() * T(100), size_t maxIter = 1000) { static_assert(std::is_floating_point_v, "poles requires floating-point coefficient type"); const auto& Q = f.denominator(); if (Q.degree() <= 0) return {}; return solvePolynomial(Q, eps, maxIter); } // ================================================================ // Enumerate zeros (roots of the numerator) // ================================================================ /// Return the zeros of the rational function f = P(x)/Q(x) (roots of P(x) = 0) template [[nodiscard]] std::vector> zeros( const RationalFunction& f, T eps = std::numeric_limits::epsilon() * T(100), size_t maxIter = 1000) { static_assert(std::is_floating_point_v, "zeros requires floating-point coefficient type"); const auto& P = f.numerator(); if (P.degree() <= 0) return {}; return solvePolynomial(P, eps, maxIter); } // ================================================================ // Residue (compute the residue at a specified pole) // ================================================================ /// Residue at the simple pole `pole` of the rational function f = P(x)/Q(x) /// Res(f, pole) = P(pole) / Q'(pole) template [[nodiscard]] Complex residue( const RationalFunction& f, const Complex& pole) { static_assert(std::is_floating_point_v, "residue requires floating-point coefficient type"); Complex numVal = f.numerator()(pole); Complex denDerVal = f.denominator().derivative()(pole); return numVal / denDerVal; } } // namespace sangi