diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 77824a4f1..5e7e4a173 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/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 941345a65..b82a9959c 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..d1d8ad0f2 --- /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: (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/((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()); + 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, 0.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}"); + } + } + } +}