Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1756,6 +1756,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16015,6 +16015,66 @@ node is Powf(Powf(var @base, var inner), var outer)
return flattened == expr ? null : Integration.ComputeAsAQuestionOfItsOwn(flattened, x, integrateByParts);
}

/// <summary>
/// A power of an exponential the flattening above leaves -- a symbol for the base or the
/// power, <c>(F^(g (e + f x)))^n</c> -- beside a polynomial below the bar. Its logarithmic
/// derivative is the constant <c>n g f ln F</c>, so it is <c>K e^(n g f ln(F) x)</c> with
/// <c>K</c> constant wherever it is differentiable; with a symbol for <c>K</c> the integrand
/// is one the exponential integral's rules read, and the answer is written back with
/// <c>K = (F^(g (e + f x)))^n e^(-n g f ln(F) x)</c>. A whole power is the exponential of
/// the product exactly, whatever the base, and is written so. Rubi's 2.2,
/// <c>(a + b (F^(g (e + f x)))^n)^p/(c + d x)^m</c>.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </summary>
/// <remarks>
/// <c>(F^u)^n</c> is <c>F^(n u)</c> only where <c>F^u</c> is on the principal branch, which
/// it is for a positive <c>F</c> and need not be for a symbol; <c>K</c> carries the
/// difference, and the answer holds for every <c>F</c>. 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.
/// </remarks>
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;
}

/// <summary>
/// An exponential of a multiple of a logarithm is a power of the argument:
/// <c>e^(k ln(q))</c> is <c>q^k</c>, since <c>e^(k ln q)</c> is the definition of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// A power of an exponential beside a polynomial below the bar: <c>(F^(g (k + f x)))^n</c> has
/// the constant logarithmic derivative <c>n g f ln F</c>, so it is a constant times
/// <c>e^(n g f ln(F) x)</c> wherever it is differentiable, and over a linear it is an
/// exponential integral. Rubi's 2.2, <c>(a + b (F^(g (e + f x)))^n)^p/(c + d x)^m</c>, all
/// declined before.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[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);

/// <summary>
/// With a negative base, <c>(F^x)^n</c> is not <c>F^(n x)</c>: the two differ by a power of
/// <c>e^(2 pi i n)</c> that changes where <c>F^x</c> crosses the negative axis. The answer
/// holds between those crossings, compared as complex numbers.
/// </summary>
[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}");
}
}
}
Loading