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
23 changes: 23 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,29 @@ did, and `ln(x + 2) / ln(x + 3)` at 0 was `ln(2) / ln(3)`. With symbols it was n
| `"limit(ln(a + 1/x) / ln(b + 1/x), x, +oo)".ToEntity().Simplify()` | `a / b` | `ln(a) / ln(b)` |
| `"limit(ln(1 + x) / ln(y + x), x, 0)".ToEntity().Simplify()` | `1 / y` | `0 provided not ln(y) = 0` |

### A limit over a vanishing absolute value is not claimed not to exist

**Silent: `1/abs(x)` at 0 is `+oo`, where it was `NaN`, the claim that the limit does not exist.** The sign a
vanishing divisor keeps was read off its derivatives, and the derivative of `abs(x)` is `sgn(x)`, 0 at 0, with
nothing after it: each side was left unevaluated, and the two-sided limit was `NaN`. An absolute value, and a real
power, product or quotient of absolute values, now vanishes from above. And two sides that agree are the two-sided
limit for an expression real on both sides, which now includes an absolute value, a sine, a cosine and an
arctangent of something real, and an arcsine and an arccosine inside (-1, 1). Where the sides differ, as for
`x / abs(x)`, the limit is still `NaN`.

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"1/abs(x)".ToEntity().Limit("x", 0)` | `NaN` | `+oo` |
| `"1/abs(x)".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `limitright(1 / abs(x), x, 0)` | `+oo` |
| `"-2/abs(x)".ToEntity().Limit("x", 0)` | `NaN` | `-oo` |
| `"1/abs(sin(x))".ToEntity().Limit("x", 0)` | `NaN` | `+oo` |
| `"(x + 1)/abs(x)".ToEntity().Limit("x", 0)` | `NaN` | `+oo` |
| `"1/abs(x - 1)".ToEntity().Limit("x", 1)` | `NaN` | `+oo` |
| `"abs(x)^(-3/2)".ToEntity().Limit("x", 0)` | `NaN` | `+oo` |
| `"abs(x)^(-3/2)".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `limitright(abs(x) ^ (-3/2), x, 0)` | `+oo` |
| `"sqrt(x^2)^(-3/2)".ToEntity().Limit("x", 0)` | `NaN` | `+oo` |
| `"sin(arcsin(x)) * (tan(-1) + 2/x)".ToEntity().Limit("x", 0)` | `NaN` | `2` |

### An equation in two different square roots is solved exactly

**Silent: an equation `m_1 sqrt(A) + m_2 sqrt(B) + P = 0` in x alone, with two different square roots, is answered in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,13 @@ partial record Powf
// the logarithm above declines it. https://github.com/asc-community/AngouriMath/issues/2266
if (exponentLimit?.Evaled is not Integer && ArrivesOnTheCutOffTheRealLine(Base, baseLimit, x, dist))
return null;
// A base that is never negative and vanishes, to an exponent tending to a negative
// number, tends to +oo: 0 to a negative power has no value, and abs(x)^(-3/2) at 0
// was left unevaluated from each side for it. Where the base vanishes, it does from
// above.
if (baseLimit?.Evaled is Real { IsZero: true } && exponentLimit?.Evaled is Real { IsNegative: true, IsFinite: true }
&& IsNeverNegative(Base))
return Real.PositiveInfinity;
var substituted =
(baseLimit, exponentLimit)
switch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,34 @@ bool RealThere(Entity node)
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),
Powf(var @base, var exponent) => RealThere(@base) && RealThere(exponent)
&& (NotNegativeThere(@base) || PositiveOnBothSides(@base)),
Logf(var @base, var antilogarithm) => RealThere(@base) && RealThere(antilogarithm)
&& PositiveOnBothSides(@base) && PositiveOnBothSides(antilogarithm),
// An absolute value is real whatever it is of, and a sine, a cosine and an
// arctangent of a real argument are real. An arcsine and an arccosine are real
// on [-1, 1], so near the point only where their argument is inside it there.
// 1/abs(x)^3 at 0 and sin(arcsin(x)) * (c + 2/x) were NaN for want of these,
// where each side agrees with the other.
Absf => true,
Sinf(var argument) => RealThere(argument),
Cosf(var argument) => RealThere(argument),
Arctanf(var argument) => RealThere(argument),
Arcsinf(var argument) => RealThere(argument) && StrictlyInsideTheUnitInterval(argument),
Arccosf(var argument) => RealThere(argument) && StrictlyInsideTheUnitInterval(argument),
_ => false
};
}
// Not negative on either side, so that a real power of it is real: what IsNeverNegative
// reads off the form, an even power of something real, and a real power that is itself
// real -- sqrt(x^2), whose base vanishes at 0 and is not positive there.
bool NotNegativeThere(Entity node)
=> IsNeverNegative(node)
|| node is Powf(var @base, Integer { EInteger.IsEven: true }) && RealThere(@base)
|| node is Powf(_, not Integer) && RealThere(node);
bool StrictlyInsideTheUnitInterval(Entity node)
=> node.Substitute(x, dest).Evaled is Real { IsFinite: true } value
&& value.EDecimal.Abs().CompareTo(PeterO.Numbers.EDecimal.One) < 0;
bool PositiveOnBothSides(Entity node)
=> !node.ContainsNode(x)
? node.Evaled is Real { IsPositive: true }
Expand Down Expand Up @@ -485,7 +507,14 @@ internal static T WithinABudget<T>(int limits, System.Func<T> compute)
return byRule;
// Asking each side properly, which is what the two above were not asked.
// https://github.com/asc-community/AngouriMath/issues/719
if (WhereBothSidesAgree(expr, x, dest) is { } agreed)
// Only where a side came back NaN. A side the descent answered with a value is
// what asking that side outright answers too, since the one-sided path returns
// the descent's value before anything else, so two values that differ differ
// again: tan(x/2) at pi, +oo and -oo, asked both sides through the whole of the
// machinery each time, and a definite integral over [0, pi] spent its budget of
// limits on that.
if ((fromLeft.Evaled == MathS.NaN || fromRight.Evaled == MathS.NaN)
&& WhereBothSidesAgree(expr, x, dest) is { } agreed)
return agreed;
return MathS.NaN; // A two-sided limit cannot exist if the limit from left and right don't match.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1102,13 +1102,35 @@ internal static bool IsDeterminate(Entity combined, Variable x)
// https://github.com/asc-community/AngouriMath/issues/2274
if (EvalAssumingContinuous(dividend.Limit(x, dest, side)) is not Real { IsZero: false, IsNaN: false } dividendLimit)
return null;
if (SignFromVanishingOrder(divisor, x, dest, side) is not { } divisorSign)
if ((IsNeverNegative(divisor) ? 1 : SignFromVanishingOrder(divisor, x, dest, side)) is not { } divisorSign)
return null;
return dividendLimit.IsNegative == divisorSign > 0
? Real.NegativeInfinity
: Real.PositiveInfinity;
}

/// <summary>
/// Whether an expression is real and not negative wherever it has a value, by its
/// form: an absolute value, a power of such a thing to a real exponent, a product or a
/// quotient of them, a positive constant.
/// </summary>
/// <remarks>
/// The sign a vanishing divisor keeps is otherwise read off its derivatives, which an
/// absolute value does not have where it vanishes: the derivative of abs(x) is sgn(x),
/// 0 at 0, and the next one has no value there. So 1/abs(x) at 0 was left unevaluated
/// from each side and NaN from both, where it is +oo. Where such an expression vanishes
/// it does so from above, whatever its argument does.
/// </remarks>
internal static bool IsNeverNegative(Entity expr) => expr switch
{
Absf => true,
Powf(var @base, var exponent) => IsNeverNegative(@base) && exponent.Evaled is Real { IsNaN: false },
Mulf(var multiplier, var multiplicand) => IsNeverNegative(multiplier) && IsNeverNegative(multiplicand),
Divf(var dividend, var divisor) => IsNeverNegative(dividend) && IsNeverNegative(divisor),
_ when expr.Evaled is Real { IsNegative: false, IsNaN: false } => !expr.FreeVariables.Any(),
_ => false
};

/// <summary>
/// The sign an expression that vanishes at the destination keeps on one side of it,
/// or <see langword="null"/> where it cannot be read off.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// 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>1/abs(x)</c> at 0 was NaN -- the claim that the limit does not exist -- where it is +oo from both sides. The sign
/// a vanishing divisor keeps was read off its derivatives, and the derivative of <c>abs(x)</c> is <c>sgn(x)</c>, 0 at
/// 0, with nothing after it; so each side was left unevaluated, and the two-sided limit was NaN for the descent's two
/// readings not matching. And where both sides were answered, they were promoted to the two-sided limit only for an
/// expression shown real on both sides, which knew nothing of an absolute value, a sine or an arcsine:
/// <c>sin(arcsin(x)) * (tan(-1) + 2/x)</c> is 2 from each side and was NaN. The same on 2.6.0. Found by limitcheck at
/// <c>DEPTH=3</c>, <c>SEED=29</c>; no issue was filed.
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AnAbsoluteValueVanishesFromAboveTest
{
[Theory]
[InlineData("1/abs(x)", "0", "+oo")]
[InlineData("1/abs(x)^3", "0", "+oo")]
[InlineData("-2/abs(x)", "0", "-oo")]
[InlineData("1/abs(x - 1)", "1", "+oo")]
[InlineData("1/abs(sin(x))", "0", "+oo")]
[InlineData("(x + 1)/abs(x)", "0", "+oo")]
[InlineData("abs(x)^(-3/2)", "0", "+oo")]
[InlineData("sqrt(x^2)^(-3/2)", "0", "+oo")]
[InlineData("sin(arcsin(x)) * (tan(-1) + 2/x)", "0", "2")]
public void IsTheLimitFromEachSideAndBoth(string input, string destination, string expected)
{
foreach (var side in new[] { ApproachFrom.Left, ApproachFrom.Right, ApproachFrom.BothSides })
Assert.Equal(MathS.FromString(expected), MathS.FromString(input).Limit("x", destination, side));
}

// Where the two sides differ, or one of them is not a limit of real values, there is still no two-sided limit.
[Theory]
[InlineData("x/abs(x)")]
[InlineData("sqrt(x^2)/x")]
[InlineData("abs(x)^(1/2)/x")]
[InlineData("arcsin(x + 1)/x")]
[InlineData("x^x")]
public void HasNoTwoSidedLimit(string input)
=> Assert.Equal(MathS.NaN, MathS.FromString(input).Limit("x", 0));
}
}
Loading