From f050aa4b4c1b05c6adcee5b7ec2f39900d4e0b51 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 21 Sep 2026 02:04:35 +0000 Subject: [PATCH] An exponential of a multiple of a logarithm is integrated as the power it is e^(k ln(q)) is q^k, the definition of the principal power for every complex q other than zero, and that is the spelling the parser gives every inverse hyperbolic function: acoth(a x) is 1/2 ln((a x + 1)/(a x - 1)). So e^acoth(a x) x^3 arrived as an exponential of a logarithm, which no exponential rule reads, and is x^3 sqrt((a x + 1)/(a x - 1)), a root of a quotient of linears, which the radical substitution answers. The simplifier folds the shape since #1430; the integrand is not simplified before the rules see it, so the fold is a route of its own, asked as a question of its own so that the closed rules meet the folded integrand at the top. The multiplier is gathered from the whole product in the exponent, since e^(3 acoth(a x)) arrives as e^(3 (1/2 ln q)). Rubi's 7.4.2 (exponentials of the inverse hyperbolic cotangent): of fifteen rows the probe declined, seven are answered and verified. https://github.com/asc-community/AngouriMath/issues/718 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 16 +++++ Sources/.editorconfig | 3 + .../Integration/IndefiniteIntegralSolver.cs | 40 ++++++++++++ .../Integration/Integration.Definition.cs | 3 + .../ExponentialOfALogarithmIntegralTest.cs | 65 +++++++++++++++++++ 5 files changed, 127 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/ExponentialOfALogarithmIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index d7ddd2dce..7e3662f98 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -603,6 +603,22 @@ too ([#1409](https://github.com/asc-community/AngouriMath/issues/1409)). `subset | `{1, 3} in powerset(ZZ)` | `UnrecognizedFunctionParseException` | `True` | | `card({1, {}})` | `#{ 1, { } }` — left as written | `2` | +### An exponential of a multiple of a logarithm is integrated as the power it is + +`e^(k ln(q))` is `q^k` — the definition of the principal power, for every complex `q` other than +zero — and that is the spelling the parser gives every inverse hyperbolic function: `acoth(a x)` +is `1/2 ln((a x + 1)/(a x - 1))`. So `e^acoth(a x) x^3` arrived at the integrator as an +exponential of a logarithm, which no exponential rule reads, and is `x^3 sqrt((a x + 1)/(a x - 1))`, +which the radical substitution answers. The integrand is not simplified before the rules see it +(the simplifier has folded this shape since #1430), so the fold is a route of the integrator now, +with the multiplier gathered from the whole product in the exponent +([#718](https://github.com/asc-community/AngouriMath/issues/718), Rubi's 7.4.2). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"e^(2*acoth(2*x))*(3 - 12*x^2)^2".Integrate("x")` | `integral(…)` — left unevaluated | `(144 * x ^ 5 / 5 + 144 * x ^ 4 / 4 + (-36) * x ^ 2 / 2 + (-9) * x provided not 2 * x + -1 = 0) + C` | +| `"e^(1/3*acoth(x))*x^2".Integrate("x")` | `integral(…)` — left unevaluated | an antiderivative in `((x + 1)/(x - 1))^(1/6)` | + ### `binomial(n, k)` is a function **Addition, not silent.** The binomial coefficient is a node, `Entity.Binomialf`, spelled diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 4374c6b05..acc9cd406 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -291,6 +291,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [AngouriMath/Functions/NumberTheory/ResidueClasses.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Calculus/ExponentialOfALogarithmIntegralTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [AngouriMath/Core/Entity/Omni/Sets/SetOperators.Subset.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 1b61a3c75..f1a2c2134 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -9675,6 +9675,46 @@ node is Powf(Powf(var @base, var inner), var outer) return flattened == expr ? null : Integration.ComputeAsAQuestionOfItsOwn(flattened, x, integrateByParts); } + /// + /// An exponential of a multiple of a logarithm is a power of the argument: + /// e^(k ln(q)) is q^k, since e^(k ln q) is the definition of the + /// principal power for every complex q other than zero. That is the spelling the + /// parser gives every inverse hyperbolic function -- acoth(a x) is + /// 1/2 ln((a x + 1)/(a x - 1)) -- so e^acoth(a x) x^3 arrives as + /// e^(1/2 ln((a x + 1)/(a x - 1))) x^3, which no exponential rule reads, and is + /// x^3 sqrt((a x + 1)/(a x - 1)), a root of a quotient of linears, which the + /// radical substitution answers. Rubi's 7.4.2. The simplifier folds the same shape + /// since #1430; the integrand is not simplified before the rules see it, so the fold + /// is a rule here, asked as a question of its own so the closed rules meet it at the top. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByFoldingAnExponentialOfALogarithm(Entity expr, Entity.Variable x, bool integrateByParts) + { + var folded = expr.Replace(node => + { + if (node is not Powf(var @base, var exponent) || @base != MathS.e || !exponent.ContainsNode(x)) + return node; + // The exponent as a product with one natural logarithm of x among its factors + // and nothing else of x: `3 * (1/2 * ln(q))` is how `e^(3 acoth(a x))` arrives. + Entity? argument = null; + Entity k = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(exponent)) + { + if (factor is Logf(var logBase, var inner) && logBase == MathS.e && argument is null) + argument = inner; + else if (factor.ContainsNode(x)) + return node; + else + k = k * factor; + } + if (argument is null) + return node; + var power = k.InnerSimplified; + return power == Number.Integer.One ? argument : MathS.Pow(argument, power); + }); + return folded == expr ? null : Integration.ComputeAsAQuestionOfItsOwn(folded, x, integrateByParts); + } + internal static Entity? SolveAPolynomialTimesARationalFunctionOfAnExponential(Entity expr, Entity.Variable x, bool integrateByParts) { // The polynomial factors above the bar, and the rest, which holds x in exponents only. diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 6a861608d..2e34871d9 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -456,6 +456,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // and by parts n times where the exponent is a symbol was not taken. if ((answer = IndefiniteIntegralSolver.SolveAPowerTimesAPowerOfTheLogarithm(expr, x)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveByFlatteningAPowerOfAnExponential(expr, x, integrateByParts)) is { }) return answer; + // An exponential of a multiple of a logarithm is a power of the argument, which is + // how every inverse hyperbolic function under an exponential arrives. + if ((answer = IndefiniteIntegralSolver.SolveByFoldingAnExponentialOfALogarithm(expr, x, integrateByParts)) is { }) return answer; // A polynomial times a rational function of exponentials, by parts against the // whole rational function, before anything splits the sum: the general parts rule // takes each term on its own, and each term's antiderivative keeps a logarithm the diff --git a/Sources/Tests/UnitTests/Calculus/ExponentialOfALogarithmIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ExponentialOfALogarithmIntegralTest.cs new file mode 100644 index 000000000..8b08bd904 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ExponentialOfALogarithmIntegralTest.cs @@ -0,0 +1,65 @@ +// +// 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 +{ + /// + /// e^(k ln(q)) is q^k, the definition of the principal power, and that is the + /// spelling the parser gives every inverse hyperbolic function: acoth(a x) is + /// 1/2 ln((a x + 1)/(a x - 1)). So e^acoth(a x) x^3 arrives as an exponential + /// of a logarithm, which no exponential rule reads, and is x^3 sqrt((a x + 1)/(a x - 1)), + /// which the radical substitution answers. Rubi's 7.4.2, exponentials of the inverse + /// hyperbolic cotangent, where every row was declined. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ExponentialOfALogarithmIntegralTest + { + /// Beyond a x = 1 for a = 2, where acoth(2x) is real. + private static readonly double[] Points = { 0.6, 0.8, 1.1, 1.5, 2.2 }; + + private static void DifferentiatesBack(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + var compared = 0; + foreach (var at in Points) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + if (got.IsNaN || want.IsNaN) + continue; + compared++; + 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)); + Assert.True(difference / scale < 1e-9, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + Assert.True(compared >= 4, $"only {compared} of {Points.Length} points were comparable for {integrand}"); + } + + [Theory] + [InlineData("e^(2*acoth(2*x))*(3 - 4*3*x^2)^2")] + [InlineData("e^acoth(2*x)/(3 - 3/(2*x))")] + [InlineData("e^(1/3*acoth(x))*x^2")] + [InlineData("(3 - 3/(4*x^2))^3/e^(2*acoth(2*x))")] + public void AnExponentialOfAnInverseHyperbolicCotangent(string integrand) => DifferentiatesBack(integrand); + + /// The fold is the identity it is: e^(k ln q) with any multiplier, nested or not. + [Theory] + [InlineData("e^(3*ln(x + 1))")] + [InlineData("e^(ln(x^2 + 1)/2) * x")] + [InlineData("e^(2*(1/2*ln(x + 2)))")] + public void AnExponentialOfAMultipleOfALogarithm(string integrand) => DifferentiatesBack(integrand); + } +}