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
13 changes: 13 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,19 @@ Two-sided, each side is now taken with its own value, and `floor(x)^x` at 0 is 1
| `"floor(x)^tanh(x)".ToEntity().Limit("x", 0, ApproachFrom.Right)` | unevaluated | `0` |
| `"floor(x)^x".ToEntity().Limit("x", 0)` | unevaluated | `NaN`, the sides differ |

### A two-sided limit of an expression real on both sides is where its sides agree

**Silent: a two-sided limit that was `NaN` although both sides agree has their value, where the expression is real on
both sides.** Under the complex codomain, the default, two one-sided limits that agree were not taken to be the
two-sided limit: `x^x` has a limit from the left only through its complex values, and it has no two-sided limit over the
reals. Now they are taken to be where every power to an exponent that is not whole, and every logarithm, has a base
positive on both sides. `x^x` is still `NaN` at 0.

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"(x^2)^x".ToEntity().Limit("x", 0)` | `NaN` | `1` |
| `"(x*x)^(x - 1)".ToEntity().Limit("x", 0)` | `NaN` | `+oo` |

### A limit of a logarithm that is not decided is left unevaluated

**Not silent: what overflowed the stack now returns.** A limit of a logarithm whose argument has no limit, as
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ node is Floorf or Ceilf or Roundf or Signumf
/// </remarks>
private static Entity? WhereBothSidesAgree(Entity expr, Variable x, Entity dest)
{
if (MathS.Settings.Codomain.Value is not AngouriMath.Core.Domain.Real
|| bothSidesDepth >= MaxBothSidesDepth)
if (bothSidesDepth >= MaxBothSidesDepth
|| MathS.Settings.Codomain.Value is not AngouriMath.Core.Domain.Real && !IsRealOnBothSides(expr, x, dest))
return null;
bothSidesDepth++;
try
Expand All @@ -168,6 +168,45 @@ node is Floorf or Ceilf or Roundf or Signumf
finally { bothSidesDepth--; }
}

/// <summary>
/// Whether <paramref name="expr"/> is real on both sides of a real <paramref name="dest"/>,
/// close enough to it, decided structurally: every power to an exponent that is not whole,
/// and every logarithm, has a base and an argument positive on both sides, by
/// <see cref="SignNear"/>. Where it is, its one-sided limits are limits of real values from
/// either side, and their agreeing is the two-sided limit under any codomain. <c>x^x</c>
/// is not, its base being negative on the left, and stays without one; <c>(x^2)^x</c> is,
/// and was NaN at 0 where each side is 1. Anything else unlisted is not real, which costs
/// coverage and never correctness.
/// </summary>
private static bool IsRealOnBothSides(Entity expr, Variable x, Entity dest)
{
if (dest.Evaled is not Real { IsFinite: true })
return false;
return RealThere(expr);
bool RealThere(Entity node)
{
if (!node.ContainsNode(x))
return node.Evaled is Real { IsFinite: true };
return node switch
{
Variable variable => variable == x,
Sumf(var augend, var addend) => RealThere(augend) && RealThere(addend),
Minusf(var minuend, var subtrahend) => RealThere(minuend) && RealThere(subtrahend),
Mulf(var multiplier, var multiplicand) => RealThere(multiplier) && RealThere(multiplicand),
Divf(var dividend, var divisor) => RealThere(dividend) && RealThere(divisor),
Powf(var @base, Integer) => RealThere(@base),
Powf(var @base, var exponent) => RealThere(@base) && RealThere(exponent) && PositiveOnBothSides(@base),
Logf(var @base, var antilogarithm) => RealThere(@base) && RealThere(antilogarithm)
&& PositiveOnBothSides(@base) && PositiveOnBothSides(antilogarithm),
_ => false
};
}
bool PositiveOnBothSides(Entity node)
=> !node.ContainsNode(x)
? node.Evaled is Real { IsPositive: true }
: SignNear(node, x, dest, ApproachFrom.Left) is 1 && SignNear(node, x, dest, ApproachFrom.Right) is 1;
}

/// <summary>
/// How deep <see cref="WhereBothSidesAgree"/> may nest. Each one-sided limit it asks
/// for may itself reach a two-sided limit of a subexpression, so without a bound the
Expand Down
37 changes: 37 additions & 0 deletions Sources/Tests/UnitTests/Calculus/ALimitRealOnBothSidesTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// 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;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// Under the complex codomain two agreeing one-sided limits were not taken for the two-sided one, since <c>x^x</c> has a
/// limit from the left only through its complex values. <c>(x^2)^x</c> is real on both sides of 0 and tends to 1 from
/// each, and its two-sided limit was NaN, the claim that there is none. No issue was filed; found by work/limitcheck at
/// seed 11.
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ALimitRealOnBothSidesTest
{
[Theory]
[InlineData("(x^2)^x", "1")]
[InlineData("(x*x)^(x - 1)", "+oo")]
[InlineData("(x^4)^(x - 1)", "+oo")]
public void IsWhereItsSidesAgree(string expression, string limit)
=> Assert.Equal(limit.ToEntity().Evaled, expression.ToEntity().Limit("x", 0).Evaled);

// Negative on the left, where the values are complex: still no two-sided limit.
[Theory]
[InlineData("x^x")]
[InlineData("(x/pi)^x")]
public void IsNotWhereOneSideIsComplex(string expression)
=> Assert.Equal(MathS.NaN, expression.ToEntity().Limit("x", 0).Evaled);
}
}
Loading