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
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,21 @@ positive on both sides. `x^x` is still `NaN` at 0.
| `"(x^2)^x".ToEntity().Limit("x", 0)` | `NaN` | `1` |
| `"(x*x)^(x - 1)".ToEntity().Limit("x", 0)` | `NaN` | `+oo` |

### A factor continuous at the point is taken out before l'Hopital's rule

**`sin(x)^tanh(x)` at 0 from the right is `1`, where it was left unevaluated.** Read through its exponent, the power is
a quotient l'Hopital's rule takes. The rule differentiated every factor of both parts, including the ones that tend to
a number other than 0, such as the `1 - tanh(x)^2` the derivative of tanh brings. With tanh written through
exponentials, each such factor doubled the derivative, and the result outgrew the rule's bound. A factor that is
continuous at a finite point and has a non-zero real value there is now taken out as that value, kept exact:
lim c(x) f(x) / g(x) is c(a) lim f(x) / g(x).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"sin(x)^tanh(x)".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `limitright(sin(x) ^ ((e ^ (2 * x) - 1) / (e ^ (2 * x) + 1)), x, 0)` | `1` |
| `"tan(x)^sin(tanh(x))".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `limitright(tan(x) ^ sin((e ^ (2 * x) - 1) / (e ^ (2 * x) + 1)), x, 0)` | `1` |
| `"tan(2*x)^sin(tanh(x))".ToEntity().Limit("x", 0, ApproachFrom.Right)` | did not finish in 2 minutes; unevaluated after 13 s on the previous master | `1` |

### A 0^0 limit whose l'Hopital chain doubles its powers has its value

**Silent: a different answer.** l'Hopital's rule was turned away where a
Expand Down
52 changes: 49 additions & 3 deletions Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1573,6 +1573,42 @@ bool DoesNotSettle(Entity argument) => argument.ContainsNode(x)
&& EvalAssumingContinuous(argument.Limit(x, dest, side)) is not Real { IsFinite: true };
}

/// <summary>
/// The factors of <paramref name="expr"/> that are continuous at a finite destination with a
/// finite non-zero value there, as that value, and the product of the rest.
/// </summary>
/// <remarks>
/// l'Hopital's rule differentiates both parts of the quotient, and a factor that tends to a
/// number other than 0 only makes the derivative larger: lim c(x) f(x) / g(x) is c(a) lim
/// f(x) / g(x). The product rule doubles it each time round, and with tanh written through
/// exponentials (1 - tanh(x)^2) * x * (1 + x^2) / tanh(x) at 0 from the right did not finish
/// in a minute, where x * (1 + x^2) / tanh(x) takes a tenth of a second. Only a factor real
/// for real x and with no step in it, so that its value at the point is its limit from
/// either side.
/// </remarks>
private static (Entity Value, Entity Others) TakeOutContinuousFactors(Entity expr, Variable x, Entity dest)
{
if (expr is not Mulf || !dest.IsFinite)
return (Integer.One, expr);
Entity value = Integer.One, rest = Integer.One;
var any = false;
foreach (var factor in Mulf.LinearChildren(expr))
{
if (factor.ContainsNode(x)
&& !factor.Nodes.Any(static node => node is Floorf or Ceilf or Roundf or Signumf or Piecewise)
&& TreeAnalyzer.IsRealValued(factor, x)
&& factor.Substitute(x, dest) is var there
&& there.Evaled is Real { IsFinite: true, IsNaN: false, IsZero: false })
{
value *= there;
any = true;
}
else
rest *= factor;
}
return any && rest.ContainsNode(x) ? (value.InnerSimplified, rest.InnerSimplified) : (Integer.One, expr);
}

private static Entity? ApplylHopitalRuleImpl(Entity expr, Variable x, Entity dest, ApproachFrom side)
{
if (expr is not Divf && AsQuotient(expr, x, dest, side) is { } quotient)
Expand All @@ -1585,12 +1621,15 @@ bool DoesNotSettle(Entity argument) => argument.ContainsNode(x)
if (num.ContainsNode(x) && den.ContainsNode(x)
&& !HasAStepThatJumpsNear(expr, x, dest, side))
{
var (numValue, numRest) = TakeOutContinuousFactors(num, x, dest);
var (denValue, denRest) = TakeOutContinuousFactors(den, x, dest);
var taken = (numValue / denValue).InnerSimplified;
// Simplified, because the shape of the quotient is what the
// machinery below matches on and differentiation does not
// produce it: d/dx ln(x)^2 is 2 * ln(x) * (1 / x), a product
// with a quotient inside rather than the quotient
// 2 * ln(x) / x, and only the second has a limit here.
var applied = (num.Differentiate(x) / den.Differentiate(x)).Simplify();
var applied = (numRest.Differentiate(x) / denRest.Differentiate(x)).Simplify();
// The domain condition simplification leaves behind is what the
// derivative of a logarithm or a root carries, and a Providedf is
// not a continuous node, so the quotient would be turned away
Expand All @@ -1614,18 +1653,25 @@ bool DoesNotSettle(Entity argument) => argument.ContainsNode(x)
// was turned away, so cos(x) * sinh(x) / sin(x) from the right at 0 was
// left unevaluated, and ln(sin(x)) / ln(sinh(x)) with it.
if (GrewTooMuch(expr, applied))
return ItsValueAt(applied, x, dest);
return ItsValueAt(applied, x, dest) is { } value ? Times(taken, value) : null;
lHopitalApplications++;
MultithreadingFunctional.ExitIfCancelled();
lHopitalChain!.Add(applied);
try
{
if (ComputeLimit(applied, x, dest, side) is { } resLim)
return resLim;
return Times(taken, resLim);
}
finally { lHopitalChain.RemoveAt(lHopitalChain.Count - 1); }
}
return null;
// A limit times the value taken out: unchanged where nothing was, exact where the limit is
// finite, and the infinity itself where it is one, since a finite non-zero real times +oo
// is +oo or -oo and a caller compares against that.
static Entity Times(Entity taken, Entity limit)
=> taken == Integer.One ? limit
: limit.Evaled is Real { IsFinite: false, IsNaN: false } && (taken * limit).Evaled is Real { IsNaN: false } infinity ? infinity
: (taken * limit).InnerSimplified;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// 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 AngouriMath.Core;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// <c>sin(x)^tanh(x)</c> at 0 from the right was left unevaluated, where it is 1, and <c>tan(2x)^sin(tanh(x))</c>
/// declined after thirteen seconds. Read through its exponent, the power is a quotient l'Hopital's rule takes, and the
/// rule differentiated every factor of both parts, including those that tend to a number other than 0, such as the
/// <c>1 - tanh(x)^2</c> the derivative of tanh brings. With tanh written through exponentials, each such factor
/// doubled the derivative, which outgrew the rule's bound. A factor that is continuous at a finite point with a non-zero
/// value there is now taken out as that value: lim c(x) f(x) / g(x) is c(a) lim f(x) / g(x). Found by limitcheck at
/// <c>DEPTH=3</c>, <c>SEED=42</c>; no issue was filed.
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AContinuousFactorIsNotDifferentiatedTest
{
[Theory]
[InlineData("sin(x)^tanh(x)")]
[InlineData("tan(x)^sin(tanh(x))")]
[InlineData("tan(2*x)^sin(tanh(x))")]
public void IsOneFromTheRight(string input)
=> Assert.Equal(MathS.FromString("1"), MathS.FromString(input).Limit("x", 0, ApproachFrom.Right));

// The value taken out is kept exact, and an infinity it multiplies is still that infinity.
[Theory]
[InlineData("tanh(1 + x) * (x - 1) / (x^2 - 1)", "1", "tanh(2) / 2")]
[InlineData("(2 + cos(x)) * x / sin(x)", "0", "3")]
[InlineData("(2 + cos(x)) / sin(x)^2", "0", "+oo")]
public void KeepsTheValueTakenOut(string input, string destination, string expected)
{
var limit = MathS.FromString(input).Limit("x", destination);
var wanted = MathS.FromString(expected);
if (wanted.Evaled is Entity.Number { IsFinite: false })
Assert.Equal(wanted, limit);
else
Assert.Equal(Entity.Number.Integer.Zero, (limit - wanted).Simplify());
}
}
}
Loading