diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs
index 0fd3881e5..6e2a33ec2 100644
--- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs
@@ -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
@@ -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
diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
index 8ce803ff5..667aed301 100644
--- a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
@@ -52,6 +52,65 @@ private static Entity ApplySecondRemarkable(Entity expr, Variable x, Entity dest
_ => expr
};
+ ///
+ /// 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
+ /// where that is not the shape or the exponent settles nothing.
+ ///
+ ///
+ /// 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.
+ ///
+ /// 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.
+ ///
+ /// 1^oo is left to , which answers it more directly,
+ /// and 0^oo and oo^oo are not indeterminate at all.
+ ///
+ 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--; }
+ }
+
+ ///
+ /// 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.
+ ///
+ 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?
@@ -248,12 +307,22 @@ private static (Entity Numerator, Entity Denominator) SplitProduct(Entity expr)
/// (sin(x) - x) / (x * sin(x)), which is 0/0 and which the rule settles at 0 in
/// three steps.
///
- 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;
@@ -277,6 +346,56 @@ private static (Entity Numerator, Entity Denominator) SplitProduct(Entity expr)
return worthIt && combined is { } && common is { } ? (combined / common).InnerSimplified : null;
}
+ ///
+ /// Whether a quotient is one of the two forms l'Hopital's rule reads: 0/0 or oo/oo.
+ ///
+ 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);
+ }
+
+ ///
+ /// A product of something vanishing with something diverging, written as the quotient
+ /// of the diverging factor by the reciprocal of the vanishing one, or
+ /// where it is not that shape.
+ ///
+ ///
+ /// This is the other way a product can be indeterminate without being written as a
+ /// quotient, and the split above cannot see it: sin(x) * ln(x) has no reciprocal
+ /// factor at all, so both halves go into the numerator and it comes back unchanged.
+ ///
+ /// 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
+ /// ln(x) / csc(x) gets rid of the logarithm and arrives at an answer; the other
+ /// arrangement, sin(x) / (1 / ln(x)), differentiates into a product of the same
+ /// shape as the one it started from and goes round.
+ ///
+ 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;
+ }
+
///
/// 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
@@ -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)
diff --git a/Sources/Tests/UnitTests/Calculus/IndeterminatePowerTest.cs b/Sources/Tests/UnitTests/Calculus/IndeterminatePowerTest.cs
new file mode 100644
index 000000000..0c8d0ea70
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/IndeterminatePowerTest.cs
@@ -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
+{
+ ///
+ /// 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.
+ ///
+ 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);
+
+ ///
+ /// 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.
+ ///
+ [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);
+
+ ///
+ /// And 0^0 is not always 1, which is the whole reason it is indeterminate: the exponent
+ /// decides, and here it decides on e.
+ ///
+ [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);
+
+ ///
+ /// 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.
+ ///
+ [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);
+
+ ///
+ /// 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.
+ ///
+ [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);
+
+ ///
+ /// At an infinite destination there is only one direction to come from, so these need no
+ /// side of their own.
+ ///
+ [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);
+
+ ///
+ /// 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.
+ ///
+ [Fact]
+ public void APowerThatIsNotRealOnOneSideHasNoTwoSidedLimit() =>
+ Assert.Equal(MathS.NaN.Evaled,
+ "x ^ x".ToEntity().Limit("x", 0).Simplify().Evaled);
+ }
+}