From 115872b0ed644bbaf5367e54983ccd164018e6e6 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 18 Oct 2026 05:22:34 +0000 Subject: [PATCH 1/2] Do not divide by a leading coefficient that is zero spelled otherwise Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 4 ++ ...dingCoefficientThatVanishesIntegralTest.cs | 47 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/ALeadingCoefficientThatVanishesIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index b4e4d7be8..0b704ffad 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -2510,6 +2510,10 @@ Entity InU(Dictionary polynomial) else if (factor is Powf(var polynomial, Number.Integer power) && power.EInteger.CompareTo(EInteger.FromInt32(2)) >= 0 && TreeAnalyzer.TryGetPolynomial(polynomial, x, out var read) && read.Count > 1 && read.Keys.Max() is { } top && read[top] is var leading && !leading.ContainsNode(x) && leading.Evaled is not Number + // A leading coefficient that is zero spelled otherwise, `-a^2 + a^2` in + // `(a (1 + u^2))^2 - (a (1 - u^2))^2`, is not one: divided by, every other + // coefficient was over zero, and the answer `NaN + C`. + && Functions.PartialFractions.InLowestTermsOverTheSymbols(leading) is not Number.Complex { IsZero: true } && !numerator.Nodes.Concat(denominator.Nodes).Any(node => node is Powf(var radicand, Number.Rational r) && r is not Number.Integer && radicand.Nodes.Contains(polynomial))) { diff --git a/Sources/Tests/UnitTests/Calculus/ALeadingCoefficientThatVanishesIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ALeadingCoefficientThatVanishesIntegralTest.cs new file mode 100644 index 000000000..aa97b2a2e --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ALeadingCoefficientThatVanishesIntegralTest.cs @@ -0,0 +1,47 @@ +// +// 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 power of a polynomial whose leading coefficient is zero spelled otherwise is not made monic by + /// dividing by it: the radical substitution of Rubi's 7.4.2 1/(e^(arccoth(a x)) (c - a^2 c x^2)^2) + /// writes (a (1 + u^2))^2 - (a (1 - u^2))^2, whose fourth power cancels, and the answer was + /// NaN + C. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ALeadingCoefficientThatVanishesIntegralTest + { + [Theory] + [InlineData("1/(e^acoth(a*x)*(c-a^2*c*x^2)^2)")] + [InlineData("1/(e^acoth(a*x)*(c-a^2*c*x^2)^5)")] + public void IsNotANaN(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("NaN", integral.Stringize()); + if (integral.Stringize().Contains("integral(")) + return; + var pinned = integral.Substitute("C", 0).Substitute("a", 1.3).Substitute("c", 0.7); + Entity original = integrand.ToEntity().Substitute("a", 1.3).Substitute("c", 0.7); + var derivative = pinned.Differentiate("x"); + foreach (var at in new[] { -2.3, 1.4, 1.9 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.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) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(difference / scale < 1e-8, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +} From df55adf4534a939a4940dd101dcec289fdbf5494 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 18 Oct 2026 05:33:39 +0000 Subject: [PATCH 2/2] Test the vanishing leading coefficient directly, and record it in BREAKING-CHANGES Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 13 ++++++++++++ ...dingCoefficientThatVanishesIntegralTest.cs | 20 +++++++++---------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 936316186..c711bd047 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -534,6 +534,19 @@ power, and the integrand is answered in a fraction of a second | `"csch(c+d*x)*(a+b*sinh(c+d*x)^4)^2".ToEntity().Integrate("x")` | past fifteen minutes | 426 characters | | `"csch(c+d*x)*(a+b*sinh(c+d*x)^4)^3".ToEntity().Integrate("x")` | past fifteen minutes | 646 characters | +### A power of a polynomial whose leading coefficient is zero spelled otherwise is not divided by it + +**Answers where there were none.** `1/((a (1 + x^2))^2 - (a (1 - x^2))^2)^3` was left unevaluated. That is +`1/(64 a^6 x^6)`: the polynomial's fourth power cancels, and its leading coefficient as read, `a^2 - a^2` spelled +otherwise, was divided by to make it monic. The radical substitution writes this shape for an exponential of an +inverse hyperbolic cotangent ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/((a*(1+x^2))^2-(a*(1-x^2))^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 133 characters | +| `"x^2/((a*(1+x^2))^2-(a*(1-x^2))^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 235 characters | +| `"1/((-1/a^2+((1+x^2)/(a*(1-x^2)))^2)^2*(1-x^2)^2)".ToEntity().Integrate("x")` | `integral(...)` | 518 characters | + ### An exponential of a whole multiple of an inverse hyperbolic tangent is written algebraically first **Answers where there were none.** `e^(n artanh(u))` with u linear is written as powers of `1 + u` and `1 - u`, and then diff --git a/Sources/Tests/UnitTests/Calculus/ALeadingCoefficientThatVanishesIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ALeadingCoefficientThatVanishesIntegralTest.cs index aa97b2a2e..d1d8ad0f2 100644 --- a/Sources/Tests/UnitTests/Calculus/ALeadingCoefficientThatVanishesIntegralTest.cs +++ b/Sources/Tests/UnitTests/Calculus/ALeadingCoefficientThatVanishesIntegralTest.cs @@ -13,27 +13,27 @@ namespace AngouriMath.Tests.Calculus { /// /// A power of a polynomial whose leading coefficient is zero spelled otherwise is not made monic by - /// dividing by it: the radical substitution of Rubi's 7.4.2 1/(e^(arccoth(a x)) (c - a^2 c x^2)^2) - /// writes (a (1 + u^2))^2 - (a (1 - u^2))^2, whose fourth power cancels, and the answer was - /// NaN + C. + /// dividing by it: (a (1 + x^2))^2 - (a (1 - x^2))^2 is 4 a^2 x^2, its fourth power + /// cancels, and over its cube the answer was NaN + C. The radical substitution writes it for + /// Rubi's 7.4.2 1/(e^(arccoth(a x)) (c - a^2 c x^2)^2). /// #718 /// [Trait("Area", "Calculus")] public sealed class ALeadingCoefficientThatVanishesIntegralTest { [Theory] - [InlineData("1/(e^acoth(a*x)*(c-a^2*c*x^2)^2)")] - [InlineData("1/(e^acoth(a*x)*(c-a^2*c*x^2)^5)")] + [InlineData("1/((a*(1+x^2))^2-(a*(1-x^2))^2)^3")] + [InlineData("x^2/((a*(1+x^2))^2-(a*(1-x^2))^2)^2")] + [InlineData("1/((-1/a^2+((1+x^2)/(a*(1-x^2)))^2)^2*(1-x^2)^2)")] public void IsNotANaN(string integrand) { var integral = integrand.ToEntity().Integrate("x"); Assert.DoesNotContain("NaN", integral.Stringize()); - if (integral.Stringize().Contains("integral(")) - return; - var pinned = integral.Substitute("C", 0).Substitute("a", 1.3).Substitute("c", 0.7); - Entity original = integrand.ToEntity().Substitute("a", 1.3).Substitute("c", 0.7); + Assert.DoesNotContain("integral(", integral.Stringize()); + var pinned = integral.Substitute("C", 0).Substitute("a", 1.3); + Entity original = integrand.ToEntity().Substitute("a", 1.3); var derivative = pinned.Differentiate("x"); - foreach (var at in new[] { -2.3, 1.4, 1.9 }) + foreach (var at in new[] { -2.3, 0.4, 1.9 }) { var got = derivative.Substitute("x", at).EvalNumerical(); var want = original.Substitute("x", at).EvalNumerical();