From 4676d3dc6928bf7ab67ca22ee2768114fe9a49de Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 4 Nov 2026 19:50:00 +0000 Subject: [PATCH] Read the limit of a modulus as the quotient or product of moduli abs(1/x^pi) at 0 was NaN, the claim that the limit does not exist, where it is +oo from both sides, as 1/abs(x)^pi is. The modulus took its argument's limit and nothing else. To the left of 0, x^pi is complex, so the sign 1/x^pi's divisor vanishes with could not be read: that side had no limit, and the two-sided limit was NaN. The same on 2.6.0. Where the argument's limit gives the modulus no value, the modulus of a quotient is now read as the quotient of the moduli, and of a product as the product. Both hold for complex values. Read that way, the divisor vanishes from above, which the vanishing-divisor reading already answers. abs(log(cos(x), -1)) at 0, which is pi / abs(ln(cos(x))), is +oo for the same reason. limitcheck at DEPTH=3, 21 seeds 14-51, master against this: 3 more compared over 2 seeds, the two NaNs above answered, and nothing else changed. Suite 18988 passed, AOT and the eight harnesses clean. Co-Authored-By: Claude Opus 5.5 --- BREAKING-CHANGES.md | 17 +++++++ .../Limits/Solvers/Limit.Classes.cs | 19 +++++++- .../Calculus/AModulusOfAQuotientTest.cs | 44 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 Sources/Tests/UnitTests/Calculus/AModulusOfAQuotientTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 7cf8b322b..060459006 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -499,6 +499,23 @@ gives is taken. No issue was filed. | `limit(exp((cos(x) - 1)/x^2), x, 0)` | `NaN` | `e ^ (-1/2)` | | `limitright(exp(sin(x)/x), x, 0)` | unevaluated | `e` | +### A limit of a modulus reads it as the quotient or product of moduli + +**Silent: `abs(1/x^pi)` at 0 is `+oo`, where it was `NaN`, the claim that the limit does not exist.** The modulus took its +argument's limit and nothing else. To the left of 0, `x^pi` is complex, so the sign `1/x^pi`'s divisor vanishes with +could not be read: that side had no limit, and the two-sided limit was `NaN`. Where the argument's limit gives the +modulus no value, the modulus of a quotient is now read as the quotient of the moduli, and of a product as the +product. This holds for complex values too, and read that way the divisor vanishes from above. + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"abs(1/x^pi)".ToEntity().Limit("x", 0)` | `NaN` | `+oo` | +| `"abs(1/x^pi)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `limitleft(abs(1 / x ^ pi), x, 0)` | `+oo` | +| `"abs(1/sqrt(x))".ToEntity().Limit("x", 0)` | `NaN` | `+oo` | +| `"abs(2/x^(3/2))".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `limitleft(abs(2 / x ^ (3/2)), x, 0)` | `+oo` | +| `"abs(log(cos(x), -1))".ToEntity().Limit("x", 0)` | `NaN` | `+oo` | +| `"abs(log(cos(x), -1))".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `limitright(abs(log(cos(x), -1)), x, 0)` | `+oo` | + ### 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 diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs index d3cecdb59..e0f889f54 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs @@ -539,7 +539,24 @@ partial record Signumf partial record Absf { internal override Entity? ComputeLimitDivideEtImpera(Variable x, Entity dist, ApproachFrom side) - => Argument.ComputeLimitDivideEtImpera(x, dist, side)?.Abs(); + { + var direct = Argument.ComputeLimitDivideEtImpera(x, dist, side)?.Abs(); + if (direct is { } found && found.Evaled != MathS.NaN) + return found; + // The modulus of a quotient is the quotient of the moduli, and of a product the product, + // for complex values as for real ones. Read that way, a divisor that vanishes vanishes from + // above: abs(1/x^pi) at 0 from the left had no limit, x^pi being complex there and its + // sign unreadable, so the two-sided limit was NaN where it is +oo, as 1/abs(x)^pi is. + Entity? split = Argument switch + { + Divf(var dividend, var divisor) => dividend.Abs() / divisor.Abs(), + Mulf(var multiplier, var multiplicand) => multiplier.Abs() * multiplicand.Abs(), + _ => null + }; + return split?.ComputeLimitDivideEtImpera(x, dist, side) is { } bySplitting && bySplitting.Evaled != MathS.NaN + ? bySplitting + : direct; + } } /// diff --git a/Sources/Tests/UnitTests/Calculus/AModulusOfAQuotientTest.cs b/Sources/Tests/UnitTests/Calculus/AModulusOfAQuotientTest.cs new file mode 100644 index 000000000..37d79aed4 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AModulusOfAQuotientTest.cs @@ -0,0 +1,44 @@ +// +// 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 +{ + /// + /// abs(1/x^pi) at 0 was NaN -- the claim that the limit does not exist -- where it is +oo from both sides, as + /// 1/abs(x)^pi is. The modulus took its argument's limit and nothing else, and to the left of 0 x^pi is + /// complex, so the sign 1/x^pi's divisor vanishes with could not be read: that side had no limit, and the + /// two-sided limit was NaN. The modulus of a quotient is the quotient of the moduli, and of a product the product, + /// for complex values too, and read that way the divisor vanishes from above. The same on 2.6.0. Found by limitcheck + /// at DEPTH=3, SEED=51, and abs(log(cos(x), -1)), which is pi / abs(ln(cos(x))), at + /// SEED=41; no issue was filed. + /// + [Trait("Area", "Calculus")] + public sealed class AModulusOfAQuotientTest + { + [Theory] + [InlineData("abs(1/x^pi)")] + [InlineData("abs(2/x^(3/2))")] + [InlineData("abs(1/sqrt(x))")] + [InlineData("abs(log(cos(x), -1))")] + public void IsInfiniteFromEachSideAndBoth(string input) + { + foreach (var side in new[] { ApproachFrom.Left, ApproachFrom.Right, ApproachFrom.BothSides }) + Assert.Equal(MathS.FromString("+oo"), MathS.FromString(input).Limit("x", 0, side)); + } + + // A modulus that has a limit as written keeps it. + [Theory] + [InlineData("abs(sin(x)/x)", "0", "1")] + [InlineData("abs(x/abs(x))", "0", "1")] + [InlineData("abs((x^2 - 1)/(x - 1))", "1", "2")] + public void KeepsWhatItHad(string input, string destination, string expected) + => Assert.Equal(MathS.FromString(expected), MathS.FromString(input).Limit("x", destination)); + } +}