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
16 changes: 16 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,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
Expand Down
3 changes: 3 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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

[Tests/UnitTests/Core/SignedZeroBranchTest.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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9675,6 +9675,46 @@ node is Powf(Powf(var @base, var inner), var outer)
return flattened == expr ? null : Integration.ComputeAsAQuestionOfItsOwn(flattened, x, integrateByParts);
}

/// <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
/// principal power for every complex <c>q</c> other than zero. That is the spelling the
/// parser gives every inverse hyperbolic function -- <c>acoth(a x)</c> is
/// <c>1/2 ln((a x + 1)/(a x - 1))</c> -- so <c>e^acoth(a x) x^3</c> arrives as
/// <c>e^(1/2 ln((a x + 1)/(a x - 1))) x^3</c>, which no exponential rule reads, and is
/// <c>x^3 sqrt((a x + 1)/(a x - 1))</c>, 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
/// </summary>
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// <c>e^(k ln(q))</c> is <c>q^k</c>, the definition of the principal power, and that is the
/// spelling the parser gives every inverse hyperbolic function: <c>acoth(a x)</c> is
/// <c>1/2 ln((a x + 1)/(a x - 1))</c>. So <c>e^acoth(a x) x^3</c> arrives as an exponential
/// of a logarithm, which no exponential rule reads, and is <c>x^3 sqrt((a x + 1)/(a x - 1))</c>,
/// which the radical substitution answers. Rubi's 7.4.2, exponentials of the inverse
/// hyperbolic cotangent, where every row was declined.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ExponentialOfALogarithmIntegralTest
{
/// <summary>Beyond <c>a x = 1</c> for <c>a = 2</c>, where <c>acoth(2x)</c> is real.</summary>
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);

/// <summary>The fold is the identity it is: <c>e^(k ln q)</c> with any multiplier, nested or not.</summary>
[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);
}
}
Loading