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
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ private static Entity ExpandLogarithm(Entity expr)
// it is what answers (1 - cos(x)) / x^2 at 0+ with 1/2 rather than NaN, and
// csc(x) * x with 1: the csc rewrite leaves a product, which the descent does
// not take apart but which the rule reads back as the quotient x / sin(x).
if (SolveAsIndeterminatePower(expr, x, dest, side) is { } byExponent)
return byExponent;
if (ApplylHopitalRule(expr, x, dest, side) is { } lhopital && lhopital.Evaled != MathS.NaN)
return lhopital;
// The one-sided path is the only one with nothing behind it, and the descent
Expand Down Expand Up @@ -127,6 +129,8 @@ private static Entity ExpandLogarithm(Entity expr)
// were left with nothing to catch them. The rule is only allowed to improve
// on what is already there: sqrt(x) / sqrt(x + 1) merely turns into its own
// reciprocal, and a NaN from it would claim the limit does not exist.
if (SolveAsIndeterminatePower(expr, x, dest, ApproachFrom.Left) is { } byExponent)
return byExponent;
if (ApplylHopitalRule(expr, x, dest) is { } lhopital && lhopital.Evaled != MathS.NaN)
return lhopital;
// The rewrites are worth their cost only where there is no answer without
Expand Down
125 changes: 122 additions & 3 deletions Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,65 @@ private static Entity ApplySecondRemarkable(Entity expr, Variable x, Entity dest
_ => expr
};

/// <summary>
/// The limit of f(x)^g(x) where the pair is indeterminate and the second remarkable
/// limit does not already cover it -- that is, 0^0 and oo^0 -- or <see langword="null"/>
/// where that is not the shape or the exponent settles nothing.
/// </summary>
/// <remarks>
/// The descent substitutes each part's own limit, so both of these arrive as 0^0, which
/// is NaN. Written over as e^(g * ln f), the same question is the limit of a product of
/// something vanishing with something diverging, which the rules below can take apart.
/// <para/>
/// The exponent is asked as a limit of its own rather than rewritten in place, because a
/// rewrite would only hand the descent a product it reads no better than the power: the
/// descent substitutes the parts' limits and does not apply l'Hopital's rule to a part.
/// Asking outright is what puts the whole machinery behind the exponent.
/// <para/>
/// 1^oo is left to <see cref="ApplySecondRemarkable"/>, which answers it more directly,
/// and 0^oo and oo^oo are not indeterminate at all.
/// </remarks>
private static Entity? SolveAsIndeterminatePower(Entity expr, Variable x, Entity dest, ApproachFrom side)
{
if (expr is not Powf(var @base, var power)
|| !@base.ContainsNode(x) || !power.ContainsNode(x)
|| indeterminatePowerDepth >= MaxIndeterminatePowerDepth)
return null;
if (EvalAssumingContinuous(power.Limit(x, dest, side)) != 0)
return null;
var baseLimit = EvalAssumingContinuous(@base.Limit(x, dest, side));
if (baseLimit != 0 && !IsInfiniteNode(baseLimit))
return null;
// Every route out of ln(f) runs through differentiating f, so a base this library
// cannot differentiate is one the rewrite cannot finish on: it would only hand the
// rules an expression with a hole in it and let them work at it. A factorial is the
// case that matters -- its derivative wants the digamma function, which is not here,
// and comes back as NaN -- and lim x->+oo ((x!) / x^x)^(1/x) is the expression. It
// has no answer either way, and without this it takes a long time not to find one.
var derivative = @base.Differentiate(x).InnerSimplified;
if (derivative.Nodes.Any(node => node is Derivativef || node == MathS.NaN))
return null;

indeterminatePowerDepth++;
try
{
if (ComputeLimit((power * MathS.Ln(@base)).InnerSimplified, x, dest, side) is not { } exponent
|| exponent.Evaled == MathS.NaN)
return null;
return MathS.e.Pow(exponent).InnerSimplified;
}
finally { indeterminatePowerDepth--; }
}

/// <summary>
/// How deep the rewriting of one power into another may go. The exponent it asks about
/// is a limit in its own right and may hold a power of the same shape, so without a
/// bound the work would multiply.
/// </summary>
private const int MaxIndeterminatePowerDepth = 3;

[ThreadStatic] private static int indeterminatePowerDepth;

private static bool IsInfiniteNode(Entity expr)
=> expr.ContainsNode("+oo") || expr.ContainsNode("-oo"); // TODO: is it correct?

Expand Down Expand Up @@ -248,12 +307,22 @@ private static (Entity Numerator, Entity Denominator) SplitProduct(Entity expr)
/// <c>(sin(x) - x) / (x * sin(x))</c>, which is 0/0 and which the rule settles at 0 in
/// three steps.
/// </summary>
private static Entity? AsQuotient(Entity expr, Variable x)
private static Entity? AsQuotient(Entity expr, Variable x, Entity dest, ApproachFrom side)
{
if (expr is Mulf)
{
// Taking the reciprocal factors out is the better reading where it gives the
// rule something it can use -- x * e^(-x) comes out as the clean x / e^x -- so
// it is tried first. But it can also hide the indeterminacy rather than expose
// it: tan(x) * ln(x) has been written sin(x) / cos(x) * ln(x) by the time it
// arrives, and splitting on the reciprocal gives sin(x) * ln(x) / cos(x), whose
// divisor tends to 1. That is no longer a quotient the rule reads, so the other
// arrangement is tried in its place rather than after it.
var (numerator, denominator) = SplitProduct(expr);
return denominator == 1 ? null : numerator / denominator;
if (denominator != 1 && IsIndeterminateQuotient(numerator, denominator, x, dest, side))
return numerator / denominator;
return AsQuotientOfVanishingAndDiverging(expr, x, dest, side)
?? (denominator == 1 ? null : numerator / denominator);
}
if (expr is not (Sumf or Minusf))
return null;
Expand All @@ -277,6 +346,56 @@ private static (Entity Numerator, Entity Denominator) SplitProduct(Entity expr)
return worthIt && combined is { } && common is { } ? (combined / common).InnerSimplified : null;
}

/// <summary>
/// Whether a quotient is one of the two forms l'Hopital's rule reads: 0/0 or oo/oo.
/// </summary>
private static bool IsIndeterminateQuotient(Entity numerator, Entity denominator, Variable x, Entity dest, ApproachFrom side)
{
var above = EvalAssumingContinuous(numerator.Limit(x, dest, side));
var below = EvalAssumingContinuous(denominator.Limit(x, dest, side));
return above == 0 && below == 0 || IsInfiniteNode(above) && IsInfiniteNode(below);
}

/// <summary>
/// A product of something vanishing with something diverging, written as the quotient
/// of the diverging factor by the reciprocal of the vanishing one, or
/// <see langword="null"/> where it is not that shape.
/// </summary>
/// <remarks>
/// This is the other way a product can be indeterminate without being written as a
/// quotient, and the split above cannot see it: <c>sin(x) * ln(x)</c> has no reciprocal
/// factor at all, so both halves go into the numerator and it comes back unchanged.
/// <para/>
/// The diverging factor goes on top and the vanishing one is inverted underneath, not
/// the other way round, though both give an indeterminate quotient. Differentiating
/// <c>ln(x) / csc(x)</c> gets rid of the logarithm and arrives at an answer; the other
/// arrangement, <c>sin(x) / (1 / ln(x))</c>, differentiates into a product of the same
/// shape as the one it started from and goes round.
/// </remarks>
private static Entity? AsQuotientOfVanishingAndDiverging(Entity expr, Variable x, Entity dest, ApproachFrom side)
{
Entity? vanishing = null, diverging = null;
Entity rest = 1;
foreach (var factor in Mulf.LinearChildren(expr))
{
if (!factor.ContainsNode(x))
{
rest *= factor;
continue;
}
var limit = EvalAssumingContinuous(factor.Limit(x, dest, side));
if (vanishing is null && limit == 0)
vanishing = factor;
else if (diverging is null && IsInfiniteNode(limit))
diverging = factor;
else
rest *= factor;
}
return vanishing is { } zero && diverging is { } infinity
? rest * infinity / (1 / zero).InnerSimplified
: null;
}

/// <remarks>
/// The side is carried through because the rule is stated one-sidedly to begin with --
/// the two-sided case is the two one-sided ones agreeing -- so it is the same rule
Expand Down Expand Up @@ -304,7 +423,7 @@ private static (Entity Numerator, Entity Denominator) SplitProduct(Entity expr)

private static Entity? ApplylHopitalRuleImpl(Entity expr, Variable x, Entity dest, ApproachFrom side)
{
if (expr is not Divf && AsQuotient(expr, x) is { } quotient)
if (expr is not Divf && AsQuotient(expr, x, dest, side) is { } quotient)
expr = quotient;
if (expr is Divf(var num, var den))
if (EvalAssumingContinuous(num.Limit(x, dest, side)) is var numLimit && EvalAssumingContinuous(den.Limit(x, dest, side)) is var denLimit)
Expand Down
115 changes: 115 additions & 0 deletions Sources/Tests/UnitTests/Calculus/IndeterminatePowerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//
// Copyright (c) 2019-2022 Angouri.
// AngouriMath is licensed under MIT.
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
// Website: https://am.angouri.org.
//

using AngouriMath;
using AngouriMath.Core;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// The indeterminate forms that are not quotients. Of the three powers, only 1^oo had a
/// rule -- the second remarkable limit -- so 0^0 and oo^0 arrived at the descent, which
/// substitutes each part's own limit and hands back 0^0, that is NaN. And a product of
/// something vanishing with something diverging had no reading either unless one of the
/// factors happened to be written as a reciprocal.
/// </summary>
public sealed class IndeterminatePowerTest
{
private static Entity Limit(string expression, string destination, ApproachFrom side) =>
expression.ToEntity().Limit("x", destination.ToEntity(), side).Simplify();

private static void AssertLimit(string expression, string destination, ApproachFrom side, string expected) =>
Assert.Equal(expected.ToEntity().Evaled, Limit(expression, destination, side).Evaled);

/// <summary>
/// 0^0. Over to the exponent, each of these is the limit of g * ln(f), which the rules
/// for a vanishing-against-diverging product can take apart.
/// </summary>
[Theory]
[InlineData("x ^ x", "0", ApproachFrom.Right, "1")]
[InlineData("x ^ sin(x)", "0", ApproachFrom.Right, "1")]
[InlineData("sin(x) ^ x", "0", ApproachFrom.Right, "1")]
[InlineData("(1/x) ^ (1/x)", "+oo", ApproachFrom.Left, "1")]
public void ZeroToTheZero(string expression, string destination, ApproachFrom side, string expected) =>
AssertLimit(expression, destination, side, expected);

/// <summary>
/// And 0^0 is not always 1, which is the whole reason it is indeterminate: the exponent
/// decides, and here it decides on e.
/// </summary>
[Theory]
[InlineData("x ^ (1 / ln(x))", "0", ApproachFrom.Right, "e")]
[InlineData("x ^ (2 / ln(x))", "0", ApproachFrom.Right, "e ^ 2")]
public void ZeroToTheZeroIsNotAlwaysOne(string expression, string destination, ApproachFrom side, string expected) =>
AssertLimit(expression, destination, side, expected);

[Theory]
[InlineData("x ^ (1/x)", "+oo", ApproachFrom.Left, "1")]
[InlineData("x ^ (1 / ln(x))", "+oo", ApproachFrom.Left, "e")]
[InlineData("(1/x) ^ x", "0", ApproachFrom.Right, "1")]
public void InfinityToTheZero(string expression, string destination, ApproachFrom side, string expected) =>
AssertLimit(expression, destination, side, expected);

/// <summary>
/// A product of something vanishing with something diverging, written as the diverging
/// factor over the reciprocal of the vanishing one. sin(x) * ln(x) is the one worth
/// naming: it has no reciprocal factor at all, so the split that handles x * e^(-x) sees
/// nothing in it.
/// </summary>
[Theory]
[InlineData("sin(x) * ln(x)", "0", ApproachFrom.Right, "0")]
[InlineData("x * ln(x)", "0", ApproachFrom.Right, "0")]
[InlineData("sqrt(x) * ln(x)", "0", ApproachFrom.Right, "0")]
[InlineData("x * cotan(x)", "0", ApproachFrom.Right, "1")]
[InlineData("(1 - cos(x)) * cotan(x)", "0", ApproachFrom.Right, "0")]
public void AVanishingFactorAgainstADivergingOne(string expression, string destination, ApproachFrom side, string expected) =>
AssertLimit(expression, destination, side, expected);

/// <summary>
/// The forms that already had a reading must keep it. 1^oo belongs to the second
/// remarkable limit, which answers it more directly than going through the exponent
/// would; x * e^(-x) is read by taking the reciprocal factor out, which gives the
/// tidier x / e^x than inverting the other half would; and a base that tends to
/// anything else raised to a vanishing power is not indeterminate at all.
/// </summary>
[Theory]
[InlineData("(1 + x) ^ (1/x)", "0", ApproachFrom.Right, "e")]
[InlineData("(1 + 2 * x) ^ (1/x)", "0", ApproachFrom.Right, "e ^ 2")]
[InlineData("(1 + 1/x) ^ x", "+oo", ApproachFrom.Left, "e")]
[InlineData("x * e ^ (-x)", "+oo", ApproachFrom.Left, "0")]
[InlineData("x ^ 4 * e ^ (-x)", "+oo", ApproachFrom.Left, "0")]
[InlineData("(2 + x) ^ (1/x)", "0", ApproachFrom.Right, "+oo")]
[InlineData("x ^ x", "0", ApproachFrom.Left, "1")]
[InlineData("ln(x) / x", "+oo", ApproachFrom.Left, "0")]
public void EstablishedLimitsAreUnaffected(string expression, string destination, ApproachFrom side, string expected) =>
AssertLimit(expression, destination, side, expected);

/// <summary>
/// At an infinite destination there is only one direction to come from, so these need no
/// side of their own.
/// </summary>
[Theory]
[InlineData("x ^ (1 / ln(x))", "+oo", "e")]
[InlineData("x ^ (1/x)", "+oo", "1")]
public void TheFormsAtInfinityNeedNoSide(string expression, string destination, string expected) =>
Assert.Equal(expected.ToEntity().Evaled,
expression.ToEntity().Limit("x", destination.ToEntity()).Simplify().Evaled);

/// <summary>
/// x^x at 0 is left with no two-sided limit, and that is right rather than a gap: x^x is
/// not real for negative x, so there is no left-hand limit to agree with the right-hand
/// one. The 1 that comes back from the left is the complex continuation, not a real
/// limit, and the suite has pinned the two-sided answer as non-existent all along.
/// </summary>
[Fact]
public void APowerThatIsNotRealOnOneSideHasNoTwoSidedLimit() =>
Assert.Equal(MathS.NaN.Evaled,
"x ^ x".ToEntity().Limit("x", 0).Simplify().Evaled);
}
}
Loading