diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index a14f230c9..38c1dc016 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -129,6 +129,19 @@ since semantic versioning admits it. Measured on the suite: **7 of 8,525 tests** referenced the old reading — five boolean-solver inputs written `A | B`, and two that existed to record this spelling as at risk. +### A power of x times `atanh(sqrt(e) x/sqrt(d + e x^2))` is integrated by parts against the root + +**Answers where there were none.** `x atanh(sqrt(e) x/sqrt(d + e x^2))` was declined, and so were twelve more of Rubi's +7.3.7, every power of `x` from `x^6` to `1/x^9` but `1/x`. The logarithm `atanh` is written as has the derivative +`sqrt(e)/sqrt(d + e x^2)`, and by parts what is left is `x^(m + 1)/sqrt(d + e x^2)`; differentiated as written it was a +quotient of two sums with a root in each, which the search never brought down to the root +([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"atanh(x*sqrt(k)/sqrt(d+k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | `x atanh(...) - sqrt(d + k x^2)/sqrt(k)`, written in the logarithm | +| `"atanh(x*sqrt(k)/sqrt(d+k*x^2))/x^5".ToEntity().Integrate("x")` | `integral(...)` | by parts onto `1/(x^4 sqrt(d + k x^2))` | + ### A power of x is taken out of a bracket to find the derivative of a product of powers **Answers where there were none.** `x^(2n) (c + d x)^n (2 c x + 3 d x^2)`, Rubi's 1.3.1, was declined. A bracket that diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 148c4e3f3..127392b67 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -639,6 +639,76 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab return Integration.ComputeAsTheSameQuestion(written, x, integrateByParts); } + /// + /// A power of x times artanh(A x/sqrt(d + B x^2)) with A^2 = B, by parts: + /// the logarithm ln((1 + u)/(1 - u)) that artanh(u) is written as has the + /// derivative 2 u'/(1 - u^2), and for this u, 1 - u^2 = d/(d + B x^2) and + /// u' = A d/(d + B x^2)^(3/2), so it is 2 A/sqrt(d + B x^2). So + /// integral x^m ln(...) = x^(m + 1)/(m + 1) ln(...) - 2 A/(m + 1) integral x^(m + 1)/sqrt(d + B x^2), + /// for every whole m but -1. Rubi's 7.3.7, x^m artanh(sqrt(e) x/sqrt(d + e x^2)). + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// + /// The search takes it by parts too, and differentiates the logarithm as written: a quotient + /// of two sums with a root in each, which it never brought down to the root alone, and the + /// remainder it asked about ran past the budget. The identity is algebraic, with no branch in + /// it: it holds wherever d is not 0 and the root is. + /// + internal static Entity? SolveByPartsAgainstAnArtanhWhoseDerivativeIsARoot(Entity expr, Entity.Variable x, bool integrateByParts) + { + Entity constant = Number.Integer.One; + Logf? logarithm = null; + var power = 0; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (!factor.ContainsNode(x)) + { + constant = underneath ? constant / factor : constant * factor; + continue; + } + if (!underneath && logarithm is null && factor is Logf ln && ln.Base == MathS.e) + { + logarithm = ln; + continue; + } + if (APowerOfTheVariable(factor, x) is not { } k) + return null; + power += underneath ? -k : k; + } + if (logarithm is null || power == -1 || System.Math.Abs(power) > 24 + || logarithm.Antilogarithm is not Divf(Sumf(var one, var u), Minusf(var alsoOne, var alsoU)) + || one != Number.Integer.One || alsoOne != Number.Integer.One || u != alsoU) + return null; + // u read as A x/sqrt(Q): a constant, x once above the bar, and the root of Q. + Entity slope = Number.Integer.One; + Entity? radicand = null; + var sawX = false; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(u)) + { + if (!factor.ContainsNode(x)) + slope = underneath ? slope / factor : slope * factor; + else if (factor == x && !underneath && !sawX) + sawX = true; + else if (radicand is null && factor is Powf(var @base, Number.Rational half) + && (underneath ? half == Number.Rational.Create(1, 2) : half == Number.Rational.Create(-1, 2))) + radicand = @base; + else + return null; + } + if (!sawX || radicand is null + || !TreeAnalyzer.TryGetPolyQuadratic(radicand, x, out var square, out var linear, out var free) + || !TreeAnalyzer.IsZero(linear) || TreeAnalyzer.IsZero(free) || !VanishesIdentically(slope * slope - square)) + return null; + var raised = power + 1; + var rest = raised == 0 ? 1 / MathS.Sqrt(radicand) + : raised > 0 ? (raised == 1 ? x : MathS.Pow(x, raised)) / MathS.Sqrt(radicand) + : 1 / ((raised == -1 ? x : MathS.Pow(x, -raised)) * MathS.Sqrt(radicand)); + if (Integration.ComputeAsAQuestionOfItsOwn(rest, x, integrateByParts) is not { } integral + || integral.Nodes.Any(node => node is Integralf || node == MathS.NaN)) + return null; + return (constant * (MathS.Pow(x, raised) * logarithm - 2 * slope * integral) / raised).InnerSimplified; + } + /// /// A quotient by the square of a base, by parts against the base's reciprocal, where the /// base's derivative makes the rest exact: N/(M v^2) is g v'/v^2 with diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index f6c14bc2a..f5645285e 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -782,6 +782,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) if ((answer = IndefiniteIntegralSolver.SolveAPowerTimesAnExponentialOfAQuadraticInALogarithm(expr, x)) is { }) return answer; // And a negative whole power of one, onto the exponential integral by t = A + B ln(c x^r). if ((answer = IndefiniteIntegralSolver.SolveAPowerOverAPowerOfALogarithm(expr, x)) is { }) return answer; + // And x^m artanh(A x/sqrt(d + A^2 x^2)), by parts, the logarithm's derivative read as the root it is. + if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstAnArtanhWhoseDerivativeIsARoot(expr, x, integrateByParts)) is { }) return answer; // A power of an exponential with a positive base is the exponential of the product, // exactly, and only that spelling is one the exponential rules read. if ((answer = IndefiniteIntegralSolver.SolveByFlatteningAPowerOfAnExponential(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/APowerTimesAnArtanhWhoseDerivativeIsARootIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerTimesAnArtanhWhoseDerivativeIsARootIntegralTest.cs new file mode 100644 index 000000000..dbfa5fad8 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerTimesAnArtanhWhoseDerivativeIsARootIntegralTest.cs @@ -0,0 +1,53 @@ +// +// 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 +{ + /// + /// x^m artanh(sqrt(k) x/sqrt(d + k x^2)), whose derivative is sqrt(k)/sqrt(d + k x^2), + /// by parts. Differentiated as written, the logarithm is a quotient of two sums with a root in + /// each, and the search ran past its budget on what was left. Rubi's 7.3.7. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class APowerTimesAnArtanhWhoseDerivativeIsARootIntegralTest + { + [Theory] + [InlineData("atanh(x*sqrt(k)/sqrt(d + k*x^2))")] + [InlineData("x*atanh(x*sqrt(k)/sqrt(d + k*x^2))")] + [InlineData("x^4*atanh(x*sqrt(k)/sqrt(d + k*x^2))")] + [InlineData("atanh(x*sqrt(k)/sqrt(d + k*x^2))/x^2")] + [InlineData("atanh(x*sqrt(k)/sqrt(d + k*x^2))/x^5")] + [InlineData("atanh(x/sqrt(1 + x^2))")] + public void ByPartsAgainstTheRoot(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Entity Pinned(Entity e) => e.Substitute("d", 1.3).Substitute("k", 0.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -2.1, -0.4, 0.3, 0.8, 1.3, 2.9 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } +}