From d7e5cf731fbda63a89671fbb9f8ef8ffcb26e3e5 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 15 Oct 2026 13:14:48 +0000 Subject: [PATCH 1/3] Take a rational function past the twelfth degree by Hermite's reduction A denominator up to the twenty-fourth degree is read, the squarefree part Hermite leaves still held to the twelfth for the resultant; and such a rational function with numbers for coefficients is asked before the search, its terms over the least common multiple of their denominators. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 43 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 3 ++ .../Continuous/Integration/RothsteinTrager.cs | 25 +++++++++-- ...AHighDegreeRationalFunctionIntegralTest.cs | 41 ++++++++++++++++++ 4 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/AHighDegreeRationalFunctionIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 3a7a8a3b4..7ae5ba066 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -2621,6 +2621,49 @@ node is Powf(var radicand, Number.Rational r) && r is not Number.Integer && radi /// . After , so that /// everything that answers keeps the form it gives. /// + internal static Entity? SolveAHighDegreeRationalFunctionByHermite(Entity expr, Entity.Variable x) + { + // A sum of quotients over powers of one denominator, Rubi's spelling of a derivative, + // is one quotient for this. + if (expr.Vars.Any(symbol => symbol != x)) + return null; + // Over the least common multiple of the terms' denominators, each base to its highest + // power, rather than their product: three terms over the fourth, third and second + // powers of one quartic are one quotient over its fourth power. + var terms = Sumf.LinearChildren(expr).Select(term => Functions.SingleQuotient.Of(term)).ToList(); + var highest = new Dictionary(); + var powersOf = new List>(); + foreach (var (_, below) in terms) + { + var powers = new Dictionary(); + foreach (var factor in Mulf.LinearChildren(below)) + { + var (@base, whole) = factor is Powf(var b0, Number.Integer { EInteger: var w }) && w.Sign > 0 && w.CanFitInInt32() + ? (b0, w.ToInt32Unchecked()) : (factor, 1); + powers[@base] = powers.TryGetValue(@base, out var already) ? already + whole : whole; + } + foreach (var (@base, whole) in powers) + highest[@base] = System.Math.Max(highest.TryGetValue(@base, out var already) ? already : 0, whole); + powersOf.Add(powers); + } + Entity denominator = Number.Integer.One; + foreach (var (@base, whole) in highest) + denominator = denominator * MathS.Pow(@base, Number.Integer.Create(whole)); + Entity numerator = Number.Integer.Zero; + for (var i = 0; i < terms.Count; i++) + { + Entity cofactor = Number.Integer.One; + foreach (var (@base, whole) in highest) + { + var missing = whole - (powersOf[i].TryGetValue(@base, out var have) ? have : 0); + if (missing > 0) + cofactor = cofactor * MathS.Pow(@base, Number.Integer.Create(missing)); + } + numerator = numerator + terms[i].Numerator * cofactor; + } + return RothsteinTrager.IntegrateAHighDegree(numerator, denominator, x); + } + internal static Entity? SolveByRothsteinTrager(Entity expr, Entity.Variable x) { if (!TryReadAsQuotient(expr, out var numerator, out var denominator)) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 49f6fbd56..53baf97ea 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -666,6 +666,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // the question asked only, and the polynomial term below writes `1/u` as `u^(-1)` and // asks that a level down: `1/(e^atanh(a x) (c - c/(a x))^(3/2))` reached it nowhere. if ((answer = IndefiniteIntegralSolver.SolveByWritingEachLinearOnce(expr, x, integrateByParts)) is { }) return answer; + // A rational function with numbers for coefficients over a denominator past the twelfth + // degree, by Hermite's reduction before anything searches: nothing below answered one. + if ((answer = IndefiniteIntegralSolver.SolveAHighDegreeRationalFunctionByHermite(expr, x)) is { }) return answer; // A power of a linear beside the derivative of a quadratic over a whole power of it, by // parts once, before anything takes the symbolic quadratic apart. if ((answer = IndefiniteIntegralSolver.SolveAPowerOfALinearBesideTheDerivativeOfAQuadraticOverItsPower(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs b/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs index 6cd535791..05364ab05 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs @@ -63,16 +63,35 @@ internal static class RothsteinTrager /// private const int MaxDegree = 12; + /// + /// The largest denominator degree read at all: Hermite's reduction is a few Diophantine + /// solves over the rationals, and what it leaves for the resultant is the squarefree part, + /// held to . Rubi's 1.3.1 3 (19 x^3 + 120 x^2 + 228 x - 47)/(x^4 + x + 3)^4 + ... + /// is a derivative over a sixteenth-degree denominator with nothing left for the logarithms, + /// and was declined after a minute and a half of searching elsewhere. + /// + private const int MaxReducedDegree = 24; + /// /// The integral of over , /// both polynomials in with rational coefficients and the fraction /// proper; where they are not, or where the answer does not /// differentiate back to the integrand at the sampled points. /// + /// + /// , for a denominator past only: what the + /// rest of the integrator never answered, asked before it searches. + /// + internal static Entity? IntegrateAHighDegree(Entity numerator, Entity denominator, Variable x) + => PolynomialFactoring.TryGetRationalCoefficients(denominator, x, 1, 1, MaxReducedDegree, out var belowCoefficients) + && RationalPolynomial.Create(belowCoefficients).Degree > MaxDegree + ? Integrate(numerator, denominator, x) + : null; + internal static Entity? Integrate(Entity numerator, Entity denominator, Variable x) { - if (!PolynomialFactoring.TryGetRationalCoefficients(numerator, x, 1, 0, MaxDegree, out var aboveCoefficients) - || !PolynomialFactoring.TryGetRationalCoefficients(denominator, x, 1, 1, MaxDegree, out var belowCoefficients)) + if (!PolynomialFactoring.TryGetRationalCoefficients(numerator, x, 1, 0, MaxReducedDegree, out var aboveCoefficients) + || !PolynomialFactoring.TryGetRationalCoefficients(denominator, x, 1, 1, MaxReducedDegree, out var belowCoefficients)) return null; var above = RationalPolynomial.Create(aboveCoefficients); var below = RationalPolynomial.Create(belowCoefficients); @@ -87,7 +106,7 @@ internal static class RothsteinTrager var answer = rationalPart; if (!logAbove.IsZero) { - if (LogarithmicPart(logAbove, logBelow, x) is not { } logarithmic) + if (logBelow.Degree > MaxDegree || LogarithmicPart(logAbove, logBelow, x) is not { } logarithmic) return null; answer = answer + logarithmic; } diff --git a/Sources/Tests/UnitTests/Calculus/AHighDegreeRationalFunctionIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AHighDegreeRationalFunctionIntegralTest.cs new file mode 100644 index 000000000..034095723 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AHighDegreeRationalFunctionIntegralTest.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A rational function with numbers for coefficients over a denominator past the twelfth + /// degree, by Hermite's reduction: Rubi's 1.3.1 derivatives over powers of + /// x^4 + x + 3, written as sums of quotients over different powers of it. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AHighDegreeRationalFunctionIntegralTest + { + [Theory] + [InlineData("3*(-47+228*x+120*x^2+19*x^3)/(3+x+x^4)^4+(42-320*x-75*x^2-8*x^3)/(3+x+x^4)^3+30*x/(3+x+x^4)^2")] + [InlineData("(-3+10*x+4*x^3-30*x^5)/(3+x+x^4)^3-3*(1+4*x^3)*(2-3*x+5*x^2+x^4-5*x^6)/(3+x+x^4)^4")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = integrand.ToEntity().Substitute("x", at).EvalNumerical(); + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + var scale = Math.Max(1.0, Math.Abs((double)want.RealPart)); + Assert.True(difference / scale < 1e-8, $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +} From beec735630474bb3068f07068d799c3c4c12259b Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 15 Oct 2026 15:10:09 +0000 Subject: [PATCH 2/3] Hold the rational integrator's old degree below a substitution, and ask the early route only at the question asked Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 5 +++-- .../Continuous/Integration/RothsteinTrager.cs | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 7ae5ba066..0498eec35 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -2624,8 +2624,9 @@ node is Powf(var radicand, Number.Rational r) && r is not Number.Integer && radi internal static Entity? SolveAHighDegreeRationalFunctionByHermite(Entity expr, Entity.Variable x) { // A sum of quotients over powers of one denominator, Rubi's spelling of a derivative, - // is one quotient for this. - if (expr.Vars.Any(symbol => symbol != x)) + // is one quotient for this. At the question asked only, which is where nothing else + // answered one. + if (!Integration.AnsweringTheQuestionAsked || expr.Vars.Any(symbol => symbol != x)) return null; // Over the least common multiple of the terms' denominators, each base to its highest // power, rather than their product: three terms over the fourth, third and second diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs b/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs index 05364ab05..5c2894e32 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs @@ -85,13 +85,22 @@ internal static class RothsteinTrager internal static Entity? IntegrateAHighDegree(Entity numerator, Entity denominator, Variable x) => PolynomialFactoring.TryGetRationalCoefficients(denominator, x, 1, 1, MaxReducedDegree, out var belowCoefficients) && RationalPolynomial.Create(belowCoefficients).Degree > MaxDegree - ? Integrate(numerator, denominator, x) + ? IntegrateUpToDegree(numerator, denominator, x, MaxReducedDegree) : null; + /// + /// The integral of over , + /// held to unless asked otherwise: below a substitution a + /// denominator past it -- 1/(cos(x) + cos(3x))^5 under the tangent's half angle -- + /// was reduced past the budget where it used to be declined at once. + /// internal static Entity? Integrate(Entity numerator, Entity denominator, Variable x) + => IntegrateUpToDegree(numerator, denominator, x, MaxDegree); + + private static Entity? IntegrateUpToDegree(Entity numerator, Entity denominator, Variable x, int degree) { - if (!PolynomialFactoring.TryGetRationalCoefficients(numerator, x, 1, 0, MaxReducedDegree, out var aboveCoefficients) - || !PolynomialFactoring.TryGetRationalCoefficients(denominator, x, 1, 1, MaxReducedDegree, out var belowCoefficients)) + if (!PolynomialFactoring.TryGetRationalCoefficients(numerator, x, 1, 0, degree, out var aboveCoefficients) + || !PolynomialFactoring.TryGetRationalCoefficients(denominator, x, 1, 1, degree, out var belowCoefficients)) return null; var above = RationalPolynomial.Create(aboveCoefficients); var below = RationalPolynomial.Create(belowCoefficients); From 9fd0fd8c98c7d09032810bd719fa1324ff4913d5 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 15 Oct 2026 16:31:57 +0000 Subject: [PATCH 3/3] Record the answers Hermite's reduction past the twelfth degree gives, and build on netstandard2.0 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 12 ++++++++++++ .../Integration/IndefiniteIntegralSolver.cs | 14 +++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 1bbb3b9a1..8624d1d63 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -447,6 +447,18 @@ case is most of its length ([#718](https://github.com/asc-community/AngouriMath/ | `"x^4/((a + b*x^2 + c*x^4)*sqrt(d + k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 28,551 characters | | `"1/(x^2*(a + b*x^2 + c*x^4)*sqrt(d + k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 10,194 characters | +### A rational function past the twelfth degree is reduced by Hermite's method + +**Answers where there were none.** The integrator read a denominator of degree twelve at most, so a derivative over +powers of `x^4 + x + 3` up to the fourth was declined, after seven to fifteen seconds. A rational function with numbers +for coefficients over a denominator past the twelfth degree now takes Hermite's reduction, as long as what is left for the +logarithms stays within twelve ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"3*(-47+228*x+120*x^2+19*x^3)/(3+x+x^4)^4+(42-320*x-75*x^2-8*x^3)/(3+x+x^4)^3+30*x/(3+x+x^4)^2".ToEntity().Integrate("x")` | `integral(...)` | 142 characters | +| `"(-3+10*x+4*x^3-30*x^5)/(3+x+x^4)^3-3*(1+4*x^3)*(2-3*x+5*x^2+x^4-5*x^6)/(3+x+x^4)^4".ToEntity().Integrate("x")` | `integral(...)` | 142 characters | + ### A power of a linear over a power of a quadratic is reduced a power at a time **Answers where there were none.** `sqrt(d + k x)/(a + b x + c x^2)^3` took thirty seconds for 26,437 characters on the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 16ba7000c..c005eee53 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -2643,22 +2643,22 @@ node is Powf(var radicand, Number.Rational r) && r is not Number.Integer && radi ? (b0, w.ToInt32Unchecked()) : (factor, 1); powers[@base] = powers.TryGetValue(@base, out var already) ? already + whole : whole; } - foreach (var (@base, whole) in powers) - highest[@base] = System.Math.Max(highest.TryGetValue(@base, out var already) ? already : 0, whole); + foreach (var pair in powers) + highest[pair.Key] = System.Math.Max(highest.TryGetValue(pair.Key, out var already) ? already : 0, pair.Value); powersOf.Add(powers); } Entity denominator = Number.Integer.One; - foreach (var (@base, whole) in highest) - denominator = denominator * MathS.Pow(@base, Number.Integer.Create(whole)); + foreach (var pair in highest) + denominator = denominator * MathS.Pow(pair.Key, Number.Integer.Create(pair.Value)); Entity numerator = Number.Integer.Zero; for (var i = 0; i < terms.Count; i++) { Entity cofactor = Number.Integer.One; - foreach (var (@base, whole) in highest) + foreach (var pair in highest) { - var missing = whole - (powersOf[i].TryGetValue(@base, out var have) ? have : 0); + var missing = pair.Value - (powersOf[i].TryGetValue(pair.Key, out var have) ? have : 0); if (missing > 0) - cofactor = cofactor * MathS.Pow(@base, Number.Integer.Create(missing)); + cofactor = cofactor * MathS.Pow(pair.Key, Number.Integer.Create(missing)); } numerator = numerator + terms[i].Numerator * cofactor; }