From 6cb29bb6c44d6873804acfd3907d51ded2c611ed Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 02:31:41 +0000 Subject: [PATCH] A power of an exponential over a polynomial is integrated to the exponential integral (F^(g (e + f x)))^n has the constant logarithmic derivative n g f ln F, so it is a constant times e^(n g f ln(F) x) wherever it is differentiable; with a symbol for the constant, over a polynomial the integrand is the exponential integral's, and the constant is written back as the power times e^(-n g f ln(F) x). That holds for every F, where F^(n u) holds only for a positive one. Rubi's 2.2. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 18 +++++ .../Integration/IndefiniteIntegralSolver.cs | 60 ++++++++++++++++ .../Integration/Integration.Definition.cs | 3 + .../PowerOfAnExponentialIntegralTest.cs | 68 +++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/PowerOfAnExponentialIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e9ae2c6a1..7843185ac 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1529,6 +1529,24 @@ now, and a closed interval at one point meets the other in that point or in noth |---|---|---| | `"(3; 1) /\ [0; 5]".ToEntity().InnerSimplified` | `[0; 5]` — wrong | `{ }` | +### A power of an exponential over a polynomial is integrated to the exponential integral + +**Answers where there were none.** `(F^(g (e + f x)))^n` with a symbol for `F` or `n` was read by no rule +below a polynomial: `(F^x)^n/x` and Rubi's 2.2, `(a + b (F^(g (e + f x)))^n)^p/(c + d x)^m`, were +declined. Its logarithmic derivative is the constant `n g f ln F`, so it is a constant times +`e^(n g f ln(F) x)` wherever it is differentiable, and the answer is the exponential integral's with +that constant written back as `(F^(g (e + f x)))^n e^(-n g f ln(F) x)`. It holds for every `F`: a +negative one makes `(F^x)^n` differ from `F^(n x)` past each point where `F^x` crosses the negative +axis, and the constant carries that. A whole power is `F^(n u)` exactly and is written so +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(F^x)^2/x".ToEntity().Integrate("x")` | `integral(...)` | `Ei(2 ln(F) x)` | +| `"(F^x)^n/x".ToEntity().Integrate("x")` | `integral(...)` | `(F^x)^n e^(-n ln(F) x) Ei(n ln(F) x)` | +| `"(a + b*(F^(g*(e + f*x)))^n)/(c + d*x)".ToEntity().Integrate("x")` | `integral(...)` | a logarithm and an exponential integral | +| `"(a + b*(F^(g*(e + f*x)))^n)^3/(c + d*x)^3".ToEntity().Integrate("x")` | `integral(...)` | powers of `c + d x` and exponential integrals | + ### 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 diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 9965d1608..10d5586d1 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -15615,6 +15615,66 @@ node is Powf(Powf(var @base, var inner), var outer) return flattened == expr ? null : Integration.ComputeAsAQuestionOfItsOwn(flattened, x, integrateByParts); } + /// + /// A power of an exponential the flattening above leaves -- a symbol for the base or the + /// power, (F^(g (e + f x)))^n -- beside a polynomial below the bar. Its logarithmic + /// derivative is the constant n g f ln F, so it is K e^(n g f ln(F) x) with + /// K constant wherever it is differentiable; with a symbol for K the integrand + /// is one the exponential integral's rules read, and the answer is written back with + /// K = (F^(g (e + f x)))^n e^(-n g f ln(F) x). A whole power is the exponential of + /// the product exactly, whatever the base, and is written so. Rubi's 2.2, + /// (a + b (F^(g (e + f x)))^n)^p/(c + d x)^m. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + /// + /// (F^u)^n is F^(n u) only where F^u is on the principal branch, which + /// it is for a positive F and need not be for a symbol; K carries the + /// difference, and the answer holds for every F. Only beside a polynomial below the + /// bar, where nothing else reads the power: above it, the rules for a polynomial times an + /// exponential answer it as written. + /// + internal static Entity? SolveByWritingAPowerOfAnExponentialAsAMultipleOfOne(Entity expr, Entity.Variable x, bool integrateByParts) + { + bool IsAnExponential(Entity node) => node is Powf(var b, var p) && !b.ContainsNode(x) && p.ContainsNode(x); + var powers = expr.Nodes.Where(node => node is Powf(Powf(var @base, var inner), var outer) + && !@base.ContainsNode(x) && inner.ContainsNode(x) && !outer.ContainsNode(x) + && !((@base == MathS.e || @base.Evaled is Number.Real { IsPositive: true }) && outer.Evaled is Number.Real)) + .Distinct().ToList(); + if (powers.Count == 0) + return null; + // A polynomial below the bar, or under a negative whole power. + if (!FactorsOfTheIntegrand(expr).Any(pair => + pair.Factor.ContainsNode(x) && !pair.Factor.Nodes.Any(IsAnExponential) + && (pair.Underneath || pair.Factor is Powf(_, Number.Integer { IsNegative: true })) + && TreeAnalyzer.TryGetPolynomial(pair.Factor is Powf(var raised, Number.Integer) ? raised : pair.Factor, x, out var read) + && read.Keys.All(degree => degree.Sign >= 0))) + return null; + var back = new List<(Entity.Variable Constant, Entity Value)>(); + var rewritten = expr; + foreach (var node in powers) + { + if (node is not Powf(Powf(var @base, var inner), var outer) + || !TreeAnalyzer.TryGetPolyLinear(inner, x, out var slope, out _) || TreeAnalyzer.IsZero(slope)) + return null; + Entity replacement; + if (outer.Evaled is Number.Integer) + replacement = MathS.Pow(@base, (outer * inner).InnerSimplified); + else + { + var constant = Variable.CreateUnique(rewritten, "k_pow"); + var rate = (outer * slope * (@base == MathS.e ? Number.Integer.One : MathS.Ln(@base))).InnerSimplified; + replacement = constant * MathS.Pow(MathS.e, rate * x); + back.Add((constant, node * MathS.Pow(MathS.e, -rate * x))); + } + rewritten = rewritten.Replace(inside => inside == node ? replacement : inside); + } + if (Integration.ComputeAsAQuestionOfItsOwn(rewritten, x, integrateByParts) is not { } answer || answer.Nodes.Any(inside => inside is Integralf)) + return null; + foreach (var (constant, value) in back) + answer = answer.Substitute(constant, value); + return answer; + } + /// /// 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 diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 8cae2ee5f..b2a599374 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -633,6 +633,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // 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; + // And any other power of an exponential beside a polynomial below the bar, as a constant + // multiple of an exponential wherever it is differentiable. + if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfAnExponentialAsAMultipleOfOne(expr, x, integrateByParts)) is { }) return answer; // An exponential of a quadratic in 1/L beside a power of L, onto the Gaussian under u = 1/L. if ((answer = IndefiniteIntegralSolver.SolveAGaussianInAReciprocal(expr, x)) is { }) return answer; // An exponential of a multiple of a logarithm is a power of the argument, which is diff --git a/Sources/Tests/UnitTests/Calculus/PowerOfAnExponentialIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/PowerOfAnExponentialIntegralTest.cs new file mode 100644 index 000000000..79ebade80 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/PowerOfAnExponentialIntegralTest.cs @@ -0,0 +1,68 @@ +// +// 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 an exponential beside a polynomial below the bar: (F^(g (k + f x)))^n has + /// the constant logarithmic derivative n g f ln F, so it is a constant times + /// e^(n g f ln(F) x) wherever it is differentiable, and over a linear it is an + /// exponential integral. Rubi's 2.2, (a + b (F^(g (e + f x)))^n)^p/(c + d x)^m, all + /// declined before. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class PowerOfAnExponentialIntegralTest + { + [Theory] + [InlineData("(F^x)^2/x")] + [InlineData("(F^x)^n/x")] + [InlineData("(exp(x))^n/x")] + [InlineData("(a + b*(F^(g*(k + f*x)))^n)/(c + d*x)")] + [InlineData("(a + b*(F^(g*(k + f*x)))^n)^2/(c + d*x)^2")] + [InlineData("(a + b*(F^(g*(k + f*x)))^n)^3/(c + d*x)^3")] + public void OverALinear(string integrand) => DifferentiatesBack(integrand, 3, realOnly: true); + + /// + /// With a negative base, (F^x)^n is not F^(n x): the two differ by a power of + /// e^(2 pi i n) that changes where F^x crosses the negative axis. The answer + /// holds between those crossings, compared as complex numbers. + /// + [Theory] + [InlineData("(F^x)^n/x")] + [InlineData("(a + b*(F^(g*(k + f*x)))^n)/(c + d*x)")] + public void OverALinearWithANegativeBase(string integrand) => DifferentiatesBack(integrand, -3, realOnly: false); + + private static void DifferentiatesBack(string integrand, double @base, bool realOnly) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Assert.DoesNotContain("NaN", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 2.3).Substitute("b", 0.7).Substitute("c", 1.3).Substitute("d", 1.7) + .Substitute("f", 0.9).Substitute("g", 1.2).Substitute("k", 0.4).Substitute("n", 2.5).Substitute("F", @base); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -1.7, -0.9, 0.3, 0.8, 1.6, 2.9 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + if (want.IsNaN || realOnly && Math.Abs((double)want.ImaginaryPart) > 1e-12) + continue; + compared++; + var got = derivative.Substitute("x", at).EvalNumerical(); + 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}"); + } + } +}