diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 531381a24..397bfefdd 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -565,6 +565,22 @@ the working digits, or `MathS.Settings.PrecisionErrorZeroRange` where a caller h still that integer, so `e^(-123.456)` is 0 as it was. [#1338](https://github.com/asc-community/AngouriMath/issues/1338). +### A logarithm of nested powers of one linear is named as a multiple of the linear's logarithm + +**Answers where there were none, and shorter ones.** Rubi's 3.3 `(g + h x)^2 (a + b ln(c (d (e + f x)^p)^q))^3` was declined +after eleven seconds and twelve gigabytes. The logarithm's derivative is `p q f/(e + f x)` on any branch, so it is +`p q ln(e + f x) + K`, with `K` constant wherever it is continuous. Named so, the integrand is a polynomial times a power +of `ln(e + f x)`, and the answer writes the logarithm back as it was. The square and the cube beside a linear come out +shorter. The first power beside a quadratic comes out a little longer +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(g+h*x)^2*(a+b*ln(c*(d*(k+f*x)^p)^q))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,660 characters, where before this change master declined it after 11 s and 12 GB | +| `"(g+h*x)^2*(a+b*ln(c*(d*(k+f*x)^p)^q))^2".ToEntity().Integrate("x")` | `integral(...)` | 1,051 characters, where before this change master gave 2,048 | +| `"(g+h*x)*(a+b*ln(c*(d*(k+f*x)^p)^q))^3".ToEntity().Integrate("x")` | `integral(...)` | 702 characters, where before this change master gave 2,569 | +| `"(g+h*x)^2*(a+b*ln(c*(d*(k+f*x)^p)^q))".ToEntity().Integrate("x")` | `integral(...)` | 631 characters, where before this change master gave 527 | + ### A power of a power of a logarithm over x is integrated below the question asked too **Answers where there were none.** Rubi's 3.5 `(ln(a x^n)^2)^p/x` was declined. Under `u = ln(a x^n)` it is diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 91b4b1ad6..ccc133424 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -30090,6 +30090,69 @@ Entity Named(Entity value) return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; } + /// + /// A logarithm of constants and nested powers of one linear L, ln(c (d L^p)^q), is + /// p q ln(L) + K with K constant wherever it is continuous, since its derivative is + /// p q L'/L by the chain rule on any branch: named so, Rubi's 3.3 + /// (g + h x)^2 (a + b ln(c (d (e + f x)^p)^q))^3 is a polynomial times a power of + /// ln(e + f x), answered in a quarter of a second, where as written it was declined after + /// eleven and twelve gigabytes. The answer is the generic case's, as + /// 's is. Only a nested power, which the + /// rules for ln(c L^n) do not read. Asked as a question of its own. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByNamingALogarithmOfNestedPowersOfALinear(Entity expr, Entity.Variable x, bool integrateByParts) + { + Logf? named = null; + Entity? linear = null; + Entity? times = null; + foreach (var node in expr.Nodes) + { + if (node is not Logf(var logBase, var argument) log || logBase != MathS.e || !argument.ContainsNode(x) + || !argument.Nodes.Any(inner => inner is Powf(var raised, _) && raised.ContainsNode(x) && raised.Nodes.Any(deeper => deeper != raised && deeper is Powf(var b, _) && b.ContainsNode(x)))) + continue; + Entity? found = null; + Entity? Exponent(Entity part) + { + if (!part.ContainsNode(x)) + return Number.Integer.Zero; + if (TreeAnalyzer.TryGetPolyLinear(part, x, out var slope, out _) && !TreeAnalyzer.IsZero(slope)) + { + if (found is null) + found = part; + return found == part ? Number.Integer.One : null; + } + switch (part) + { + case Mulf(var left, var right): + return Exponent(left) is { } l && Exponent(right) is { } r ? l + r : null; + case Divf(var above, var below): + return Exponent(above) is { } a && Exponent(below) is { } b ? a - b : null; + case Powf(var @base, var power) when !power.ContainsNode(x): + return Exponent(@base) is { } e ? power * e : null; + default: + return null; + } + } + if (Exponent(argument) is not { } exponent || found is null) + continue; + (named, linear, times) = (log, found, exponent.InnerSimplified); + break; + } + if (named is null || linear is null || times is null || TreeAnalyzer.IsZero(times)) + return null; + var constant = Variable.CreateUnique(expr, "K"); + var inTheLinear = times * MathS.Ln(linear) + constant; + var rewritten = expr.Substitute(named, inTheLinear); + if (rewritten == expr || rewritten.ContainsNode(named)) + return null; + if (Integration.ComputeAsAQuestionOfItsOwn(rewritten, x, integrateByParts) is not { } answer) + return null; + // The sum as it was written stands for the logarithm again, where term by term it would be + // `p q ln(L) + (ln(c (d L^p)^q) - p q ln(L))`; K anywhere else is the difference. + return answer.Replace(node => node == inTheLinear ? named : node).Substitute(constant, named - times * MathS.Ln(linear)); + } + /// /// The sign of a real-valued function of among the factors of the /// integrand is constant on every interval between its zeros, and goes in front of the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 537bf55da..d63281117 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -695,6 +695,9 @@ internal static bool ASumOverRootsWouldAnswer // And a squared sine beside the squared cosine of its argument, the one they add up to: // `e^(cos(x)^2 + sin(x)^2)` was declined, written with an x its value does not hold. if ((answer = IndefiniteIntegralSolver.SolveByFoldingAPythagoreanPair(expr, x, integrateByParts)) is { }) return answer; + // A logarithm of nested powers of one linear, `ln(c (d (e + f x)^p)^q)`, named as `p q ln(e + f x)` + // plus a constant, before the rules that declined its cube beside a quadratic after twelve gigabytes. + if ((answer = IndefiniteIntegralSolver.SolveByNamingALogarithmOfNestedPowersOfALinear(expr, x, integrateByParts)) is { }) return answer; // An arctangent over a power of d (1 +- i c x), under x = tan(u)/c, where 1 + i tan(u) is // e^(i u)/cos(u): the rules below ran out of time on `(a + b atan(c x))^2/(d + i c d x)^2`. if ((answer = IndefiniteIntegralSolver.SolveAnArctangentOverAPowerOfOnePlusICXUnderTheTangent(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/ALogarithmOfNestedPowersOfALinearIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ALogarithmOfNestedPowersOfALinearIntegralTest.cs new file mode 100644 index 000000000..b766ed7bb --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ALogarithmOfNestedPowersOfALinearIntegralTest.cs @@ -0,0 +1,56 @@ +// +// 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 System.Threading; +using System.Threading.Tasks; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A logarithm of nested powers of one linear, ln(c (d (e + f x)^p)^q), named as + /// p q ln(e + f x) + K, K constant where it is continuous: Rubi's 3.3 + /// (g + h x)^2 (a + b ln(c (d (e + f x)^p)^q))^3 was declined after eleven seconds and twelve + /// gigabytes, where in the linear's logarithm it is a polynomial times a power of it. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ALogarithmOfNestedPowersOfALinearIntegralTest + { + [Theory] + // Rubi 3.3:620, with k for Rubi's e. + [InlineData("(g + h*x)^2*(a + b*ln(c*(d*(k + f*x)^p)^q))^3")] + [InlineData("(g + h*x)*(a + b*ln(c*(d*(k + f*x)^p)^q))^3")] + [InlineData("(g + h*x)^2*(a + b*ln(c*(d*(k + f*x)^p)^q))^2")] + public void IsAnsweredWithTheLogarithmAsWritten(string integrand) + { + Entity? integral = null; + // A thread of its own, so that the guard is not spent waiting behind other tests' work. + var task = Task.Factory.StartNew(() => integral = integrand.ToEntity().Integrate("x"), + CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); + Assert.True(task.Wait(LimitTermination.Guard), $"{integrand} did not finish within {LimitTermination.Guard}"); + var text = integral!.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.Contains("ln(c * (d * (k + f * x) ^ p) ^ q)", text); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9) + .Substitute("f", 0.8).Substitute("g", 1.7).Substitute("h", 0.6).Substitute("k", 0.2).Substitute("p", 1.5).Substitute("q", 0.75); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { 0.3, 0.9, 1.4, 2.1, 3.3 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}"); + 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}"); + } + } + } +}