diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 0e5303cfa..62f81a466 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -539,6 +539,22 @@ zero-discriminant arm, which gives the right answer. Each of these was checked by differentiating it back with the parameters pinned and comparing at four points. Nothing that already had an antiderivative changes. +### A radical function of whole powers of `x` written through its logarithm says where its answer holds + +**Answers where there were none, each with the condition it holds under.** `1/csch(2 ln(x))^(1/2)` +is `sqrt(sinh(2 ln(x)))`, which is `sqrt((x^2 - x^(-2))/2)` and real on both sides of zero; 2.5.0 +declined it. On the unreleased master it was integrated under `t = ln(x)`, where the rules take +`e^t` to be positive, and the answer was right for a positive `x` and wrong for every negative one. +Such an answer is given `provided x > 0` now, and `provided c x^n > 0` for a logarithm of `c x^n` +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/csch(2*ln(x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided x > 0` | +| `"csch(2*ln(x))^(-3/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided x > 0` | +| `"1/csch(2*ln(c*x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided c x > 0` | +| `"1/sech(2*ln(x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided x > 0` | + ### `e^(k acoth(a x))` beside a power of `c - a c x` is integrated **Improvement, not silent.** `e^(3 acoth(a x))/(c - a c x)^3` and `e^(2 acoth(a x)) sqrt(c - a c x)/x` diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 0f7d33b19..ed7455e51 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -7993,8 +7993,14 @@ private static bool IsTheSameQuadratic(Entity candidate, Entity wanted, Entity.V /// /// Where the answer holds. u = ln(x) is a bijection from the positive reals /// to the whole line, so the answer is an antiderivative for x > 0 — which is - /// where an integrand built from ln(x) is real in the first place. Nothing is - /// assumed that the integrand did not already assume. + /// where an integrand built from ln(x) is real, with one kind of exception. A + /// radical function of whole powers of x = e^u, sqrt(sinh(2 ln(x))) or + /// 1/csch(2 ln(x))^(1/2), is real for a negative x too, where ln(x) + /// is ln(-x) + i pi; and the rules that integrate it in u take e^u to + /// be positive, which it is for every real u. That answer is given + /// provided x > 0. Written in powers of x, which + /// does before this, the same + /// integrand is answered on both sides of zero where the rules read it. /// /// https://github.com/asc-community/AngouriMath/issues/718 /// @@ -8020,9 +8026,32 @@ node is Logf(var @base, var argument) && !@base.ContainsNode(x) && @base != Math if (integrand is Providedf(var inner, _)) integrand = inner; - return Integration.ComputeIndefiniteIntegral(integrand, u, integrateByParts) is { } result - ? result.Substitute(u, MathS.Ln(x)) - : null; + if (Integration.ComputeIndefiniteIntegral(integrand, u, integrateByParts) is not { } result) + return null; + var back = result.Substitute(u, MathS.Ln(x)); + return IsARadicalFunctionOfWholePowersOfTheExponential(inU, u) ? back.Provided(x > Number.Integer.Zero) : back; + } + + /// + /// Whether holds only in exponentials + /// e^(k t + a) with a whole k, some of them under a power that is not + /// whole: a radical function of whole powers of e^t. At t = s + i pi, + /// where a substitution t = ln(c x^n) lands for a negative c x^n, it is the + /// same function of -e^s, real again wherever its radicands are positive; and an + /// integral of it in t is found for a real t, under which e^t is + /// positive and gives up its powers from under a root. + /// + private static bool IsARadicalFunctionOfWholePowersOfTheExponential(Entity expr, Entity.Variable t) + { + var exponential = Variable.CreateUnique(expr, "w_exp"); + var inExponentials = expr.Replace(node => + node is Powf(var @base, var exponent) && @base == MathS.e && exponent.ContainsNode(t) + && TreeAnalyzer.TryGetPolyLinear(exponent, t, out var slope, out var offset) + && slope.Evaled is Number.Integer && !offset.ContainsNode(t) + ? exponential + : node); + return !inExponentials.ContainsNode(t) + && inExponentials.Nodes.Any(node => node is Powf(var @base, var power) && @base.ContainsNode(exponential) && power.Evaled is not Number.Integer); } /// @@ -8030,9 +8059,13 @@ node is Logf(var @base, var argument) && !@base.ContainsNode(x) && @base != Math /// under t = ln(c x^n): dx = x dt/n, and x^(m + 1) is /// K e^((m + 1) t/n) with K = x^(m + 1) (c x^n)^(-(m + 1)/n), whose derivative /// is 0 wherever it is defined. So the answer is K/n times the integral of - /// e^((m + 1) t/n) G(t) at t = ln(c x^n), an antiderivative on the whole of - /// the real line where the integrand is real -- for an even n that includes negative - /// x, where ln(c x^n) is not ln(c) + n ln(x). A power of a monomial, + /// e^((m + 1) t/n) G(t) at t = ln(c x^n), an antiderivative wherever + /// c x^n is positive -- for an even n and a positive c the whole line, + /// negative x included, where ln(c x^n) is not ln(c) + n ln(x). Where + /// c x^n is negative, t is ln(-c x^n) + i pi, and a function of whole + /// powers of e^t, sqrt(sinh(2 t)) say, is real there as well; a radical one + /// has its integral in t found for a real t, under which e^t is + /// positive, so that answer is given provided c x^n > 0. A power of a monomial, /// (e x)^p, is x^p times a factor of the same kind. What by parts leaves of /// (e x)^m Si(d (a + b ln(c x^n))) is this, with G(t) a sine over a linear in /// t. Rubi's answers to 8.3 to 8.5 are written in exactly this K. @@ -8089,7 +8122,14 @@ node is Logf(var @base, var argument) && !@base.ContainsNode(x) && @base != Math var k = logarithms[0].Antilogarithm == x ? Number.Integer.One : MathS.Pow(x, mPlusOne) * MathS.Pow(logarithms[0].Antilogarithm, (-mPlusOne / n).InnerSimplified); var back = constant * locallyConstant * k / n * inTIntegral.Substitute(t, logarithms[0]); - return back.Nodes.Any(node => node == MathS.NaN) ? null : back; + if (back.Nodes.Any(node => node == MathS.NaN)) + return null; + // Positive for every x other than 0 when n is even and c a positive number, and + // nothing to say then. + var positiveEverywhere = n.Evaled is Number.Integer whole && whole.EInteger.IsEven && c.Evaled is Number.Real { IsPositive: true }; + return !positiveEverywhere && IsARadicalFunctionOfWholePowersOfTheExponential(inT, t) + ? back.Provided(logarithms[0].Antilogarithm > Number.Integer.Zero) + : back; } /// diff --git a/Sources/Tests/UnitTests/Calculus/LogarithmSubstitutionTest.cs b/Sources/Tests/UnitTests/Calculus/LogarithmSubstitutionTest.cs index 2e21ea122..10f8bf5c7 100644 --- a/Sources/Tests/UnitTests/Calculus/LogarithmSubstitutionTest.cs +++ b/Sources/Tests/UnitTests/Calculus/LogarithmSubstitutionTest.cs @@ -6,6 +6,7 @@ // using System; +using System.Linq; using AngouriMath.Extensions; using Xunit; @@ -30,7 +31,9 @@ namespace AngouriMath.Tests.Calculus /// /// The sample points are positive, which is where an integrand built from ln(x) is /// real: u = ln(x) is a bijection from the positive reals onto the whole line, so the - /// answer holds wherever the integrand does and nothing further is assumed. + /// answer holds wherever the integrand does and nothing further is assumed. A radical + /// function of whole powers of x is real on both sides of zero, and is compared on + /// both. /// /// [Trait("Area", "Calculus")] @@ -115,6 +118,45 @@ public void ATrigonometricFunctionOfTheLogarithm(string integrand) [InlineData("ln(x) + x")] public void TheNeighboursAreUntouched(string integrand) => DifferentiatesBack(integrand); + /// + /// A radical function of whole powers of x written through its logarithm, which is + /// real on both sides of zero: sinh(2 ln(x)) is (x^2 - x^(-2))/2 for a + /// negative x as well. Each answer is compared at three negative points and three + /// positive ones, wherever it does not say it holds only elsewhere. Integrated under + /// t = ln(x), the first four say they hold where the logarithm's argument is + /// positive; the last two are answered in powers of x and hold on both sides. + /// Rubi's 6.5.3, 226, and 6.6.3, 188. + /// + [Theory] + [InlineData("1/csch(2*ln(x))^(1/2)", false)] + [InlineData("csch(2*ln(x))^(-3/2)", false)] + [InlineData("1/csch(2*ln(13/10*x))^(1/2)", false)] + [InlineData("1/sech(2*ln(x))^(1/2)", false)] + [InlineData("sqrt(sinh(2*ln(x)))", true)] + [InlineData("sqrt(cosh(2*ln(13/10*x)))", true)] + public void ARadicalFunctionOfAWholePowerOfX(string integrand, bool onBothSides) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + var conditions = integral.Nodes.OfType().Select(provided => provided.Predicate).ToList(); + foreach (var at in new[] { -2.7, -1.9, -1.3, 1.3, 1.9, 2.7 }) + { + if (conditions.Any(condition => condition.Substitute("x", at).Evaled == Entity.Boolean.False)) + { + Assert.False(onBothSides, $"the antiderivative of {integrand} says it does not hold at x = {at}"); + Assert.True(at < 0, $"the antiderivative of {integrand} says it does not hold at x = {at}"); + continue; + } + 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); + Assert.True(difference / Math.Max(1.0, Math.Abs((double)want.RealPart)) < 1e-9, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + /// /// What the read must refuse: an x that survives the rewrite, so the integrand is /// not a function of the logarithm alone. ln(x)*sin(x) is the plain case, and