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 @@ -539,6 +539,22 @@ zero-discriminant arm, which gives the right answer.
Each of these was checked by differentiating it back with the parameters pinned and comparing at
four points. Nothing that already had an antiderivative changes.

### A radical function of whole powers of `x` written through its logarithm says where its answer holds

**Answers where there were none, each with the condition it holds under.** `1/csch(2 ln(x))^(1/2)`
is `sqrt(sinh(2 ln(x)))`, which is `sqrt((x^2 - x^(-2))/2)` and real on both sides of zero; 2.5.0
declined it. On the unreleased master it was integrated under `t = ln(x)`, where the rules take
`e^t` to be positive, and the answer was right for a positive `x` and wrong for every negative one.
Such an answer is given `provided x > 0` now, and `provided c x^n > 0` for a logarithm of `c x^n`
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/csch(2*ln(x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided x > 0` |
| `"csch(2*ln(x))^(-3/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided x > 0` |
| `"1/csch(2*ln(c*x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided c x > 0` |
| `"1/sech(2*ln(x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided x > 0` |

### `e^(k acoth(a x))` beside a power of `c - a c x` is integrated

**Improvement, not silent.** `e^(3 acoth(a x))/(c - a c x)^3` and `e^(2 acoth(a x)) sqrt(c - a c x)/x`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7993,8 +7993,14 @@ private static bool IsTheSameQuadratic(Entity candidate, Entity wanted, Entity.V
/// <para>
/// <b>Where the answer holds.</b> <c>u = ln(x)</c> is a bijection from the positive reals
/// to the whole line, so the answer is an antiderivative for <c>x &gt; 0</c> — which is
/// where an integrand built from <c>ln(x)</c> is real in the first place. Nothing is
/// assumed that the integrand did not already assume.
/// where an integrand built from <c>ln(x)</c> is real, with one kind of exception. A
/// radical function of whole powers of <c>x = e^u</c>, <c>sqrt(sinh(2 ln(x)))</c> or
/// <c>1/csch(2 ln(x))^(1/2)</c>, is real for a negative <c>x</c> too, where <c>ln(x)</c>
/// is <c>ln(-x) + i pi</c>; and the rules that integrate it in <c>u</c> take <c>e^u</c> to
/// be positive, which it is for every real <c>u</c>. That answer is given
/// <c>provided x &gt; 0</c>. Written in powers of <c>x</c>, which
/// <see cref="SolveByFoldingAnExponentialOfALogarithm"/> does before this, the same
/// integrand is answered on both sides of zero where the rules read it.
/// </para>
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
Expand All @@ -8020,19 +8026,46 @@ node is Logf(var @base, var argument) && !@base.ContainsNode(x) && @base != Math
if (integrand is Providedf(var inner, _))
integrand = inner;

return Integration.ComputeIndefiniteIntegral(integrand, u, integrateByParts) is { } result
? result.Substitute(u, MathS.Ln(x))
: null;
if (Integration.ComputeIndefiniteIntegral(integrand, u, integrateByParts) is not { } result)
return null;
var back = result.Substitute(u, MathS.Ln(x));
return IsARadicalFunctionOfWholePowersOfTheExponential(inU, u) ? back.Provided(x > Number.Integer.Zero) : back;
}

/// <summary>
/// Whether <paramref name="expr"/> holds <paramref name="t"/> only in exponentials
/// <c>e^(k t + a)</c> with a whole <c>k</c>, some of them under a power that is not
/// whole: a radical function of whole powers of <c>e^t</c>. At <c>t = s + i pi</c>,
/// where a substitution <c>t = ln(c x^n)</c> lands for a negative <c>c x^n</c>, it is the
/// same function of <c>-e^s</c>, real again wherever its radicands are positive; and an
/// integral of it in <c>t</c> is found for a real <c>t</c>, under which <c>e^t</c> is
/// positive and gives up its powers from under a root.
/// </summary>
private static bool IsARadicalFunctionOfWholePowersOfTheExponential(Entity expr, Entity.Variable t)
{
var exponential = Variable.CreateUnique(expr, "w_exp");
var inExponentials = expr.Replace(node =>
node is Powf(var @base, var exponent) && @base == MathS.e && exponent.ContainsNode(t)
&& TreeAnalyzer.TryGetPolyLinear(exponent, t, out var slope, out var offset)
&& slope.Evaled is Number.Integer && !offset.ContainsNode(t)
? exponential
: node);
return !inExponentials.ContainsNode(t)
&& inExponentials.Nodes.Any(node => node is Powf(var @base, var power) && @base.ContainsNode(exponential) && power.Evaled is not Number.Integer);
}

/// <summary>
/// A power of <c>x</c> times a function of one logarithm of a monomial, <c>x^m G(ln(c x^n))</c>,
/// under <c>t = ln(c x^n)</c>: <c>dx = x dt/n</c>, and <c>x^(m + 1)</c> is
/// <c>K e^((m + 1) t/n)</c> with <c>K = x^(m + 1) (c x^n)^(-(m + 1)/n)</c>, whose derivative
/// is 0 wherever it is defined. So the answer is <c>K/n</c> times the integral of
/// <c>e^((m + 1) t/n) G(t)</c> at <c>t = ln(c x^n)</c>, an antiderivative on the whole of
/// the real line where the integrand is real -- for an even <c>n</c> that includes negative
/// <c>x</c>, where <c>ln(c x^n)</c> is not <c>ln(c) + n ln(x)</c>. A power of a monomial,
/// <c>e^((m + 1) t/n) G(t)</c> at <c>t = ln(c x^n)</c>, an antiderivative wherever
/// <c>c x^n</c> is positive -- for an even <c>n</c> and a positive <c>c</c> the whole line,
/// negative <c>x</c> included, where <c>ln(c x^n)</c> is not <c>ln(c) + n ln(x)</c>. Where
/// <c>c x^n</c> is negative, <c>t</c> is <c>ln(-c x^n) + i pi</c>, and a function of whole
/// powers of <c>e^t</c>, <c>sqrt(sinh(2 t))</c> say, is real there as well; a radical one
/// has its integral in <c>t</c> found for a real <c>t</c>, under which <c>e^t</c> is
/// positive, so that answer is given <c>provided c x^n &gt; 0</c>. A power of a monomial,
/// <c>(e x)^p</c>, is <c>x^p</c> times a factor of the same kind. What by parts leaves of
/// <c>(e x)^m Si(d (a + b ln(c x^n)))</c> is this, with <c>G(t)</c> a sine over a linear in
/// <c>t</c>. Rubi's answers to 8.3 to 8.5 are written in exactly this <c>K</c>.
Expand Down Expand Up @@ -8089,7 +8122,14 @@ node is Logf(var @base, var argument) && !@base.ContainsNode(x) && @base != Math
var k = logarithms[0].Antilogarithm == x ? Number.Integer.One
: MathS.Pow(x, mPlusOne) * MathS.Pow(logarithms[0].Antilogarithm, (-mPlusOne / n).InnerSimplified);
var back = constant * locallyConstant * k / n * inTIntegral.Substitute(t, logarithms[0]);
return back.Nodes.Any(node => node == MathS.NaN) ? null : back;
if (back.Nodes.Any(node => node == MathS.NaN))
return null;
// Positive for every x other than 0 when n is even and c a positive number, and
// nothing to say then.
var positiveEverywhere = n.Evaled is Number.Integer whole && whole.EInteger.IsEven && c.Evaled is Number.Real { IsPositive: true };
return !positiveEverywhere && IsARadicalFunctionOfWholePowersOfTheExponential(inT, t)
? back.Provided(logarithms[0].Antilogarithm > Number.Integer.Zero)
: back;
}

/// <summary>
Expand Down
44 changes: 43 additions & 1 deletion Sources/Tests/UnitTests/Calculus/LogarithmSubstitutionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

using System;
using System.Linq;
using AngouriMath.Extensions;
using Xunit;

Expand All @@ -30,7 +31,9 @@ namespace AngouriMath.Tests.Calculus
/// <para>
/// The sample points are positive, which is where an integrand built from <c>ln(x)</c> is
/// real: <c>u = ln(x)</c> is a bijection from the positive reals onto the whole line, so the
/// answer holds wherever the integrand does and nothing further is assumed.
/// answer holds wherever the integrand does and nothing further is assumed. A radical
/// function of whole powers of <c>x</c> is real on both sides of zero, and is compared on
/// both.
/// </para>
/// </remarks>
[Trait("Area", "Calculus")]
Expand Down Expand Up @@ -115,6 +118,45 @@ public void ATrigonometricFunctionOfTheLogarithm(string integrand)
[InlineData("ln(x) + x")]
public void TheNeighboursAreUntouched(string integrand) => DifferentiatesBack(integrand);

/// <summary>
/// A radical function of whole powers of <c>x</c> written through its logarithm, which is
/// real on both sides of zero: <c>sinh(2 ln(x))</c> is <c>(x^2 - x^(-2))/2</c> for a
/// negative <c>x</c> as well. Each answer is compared at three negative points and three
/// positive ones, wherever it does not say it holds only elsewhere. Integrated under
/// <c>t = ln(x)</c>, the first four say they hold where the logarithm's argument is
/// positive; the last two are answered in powers of <c>x</c> and hold on both sides.
/// Rubi's 6.5.3, 226, and 6.6.3, 188.
/// </summary>
[Theory]
[InlineData("1/csch(2*ln(x))^(1/2)", false)]
[InlineData("csch(2*ln(x))^(-3/2)", false)]
[InlineData("1/csch(2*ln(13/10*x))^(1/2)", false)]
[InlineData("1/sech(2*ln(x))^(1/2)", false)]
[InlineData("sqrt(sinh(2*ln(x)))", true)]
[InlineData("sqrt(cosh(2*ln(13/10*x)))", true)]
public void ARadicalFunctionOfAWholePowerOfX(string integrand, bool onBothSides)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
var derivative = integral.Substitute("C", 0).Differentiate("x");
var original = integrand.ToEntity();
var conditions = integral.Nodes.OfType<Entity.Providedf>().Select(provided => provided.Predicate).ToList();
foreach (var at in new[] { -2.7, -1.9, -1.3, 1.3, 1.9, 2.7 })
{
if (conditions.Any(condition => condition.Substitute("x", at).Evaled == Entity.Boolean.False))
{
Assert.False(onBothSides, $"the antiderivative of {integrand} says it does not hold at x = {at}");
Assert.True(at < 0, $"the antiderivative of {integrand} says it does not hold at x = {at}");
continue;
}
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
Assert.True(difference / Math.Max(1.0, Math.Abs((double)want.RealPart)) < 1e-9,
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}

/// <summary>
/// What the read must refuse: an <c>x</c> that survives the rewrite, so the integrand is
/// not a function of the logarithm alone. <c>ln(x)*sin(x)</c> is the plain case, and
Expand Down
Loading