From 099de6f9df85e5408bd9a82d823c21e51967e268 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 4 Nov 2026 16:17:18 +0000 Subject: [PATCH] Ask the whole limit of a quotient of two infinities in the descent exp(log(1/x, arcsin(x))) at 0 from the right was left unevaluated, where the logarithm alone is -1 and the limit is 1/e. The logarithm reaches the descent as ln(arcsin(x)) / ln(1/x). The limit its derivatives' quotient was taken to came back as the form 0 * +oo / 0, which has no value, and the quotient's descent returned that at once as the exponent's limit. And with parts tending to -oo and +oo, the descent asked the quotient's whole limit only when a substitution of finite parts came back NaN, which two infinite parts never are. A sine of the logarithm was left unevaluated too, and a hyperbolic sine after ten seconds. The same on 2.6.0. A form with no value from the first reading is now kept for the end, where it was answered before, and the parts are read first; and the whole limit is asked where the parts' own limits make oo/oo and the substitution has no value. Only a value is taken from it. The 0 * oo in the product below was not touched: from both sides ln(x) at 0 reads as -oo, and the NaN that 0 * -oo comes back as is what keeps x * ln(x) NaN there under the two-sided convention. Declining it, or asking the product's whole limit, answered 0. limitcheck at DEPTH=3, 15 seeds 14-42, master against this: 22 more compared over 11 seeds, among them tanh(log(x, arcsin(x))) at 0 from the left, which master declines after six seconds and this answers with tanh(1) in one. One row crossed the harness's 5 s cap, (log(pi, x))^sinh(x) * sin(1) at 0 from the left, which declines in 7.05 s here and 7.09 s on master. Suite 18968 passed, AOT and the eight harnesses clean. Co-Authored-By: Claude Opus 5.5 --- BREAKING-CHANGES.md | 17 +++++++ .../Limits/Solvers/Limit.Classes.cs | 27 ++++++++--- .../AFunctionOfAQuotientOfInfinitiesTest.cs | 47 +++++++++++++++++++ 3 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/AFunctionOfAQuotientOfInfinitiesTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index cbbfbde89..707a7092f 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -450,6 +450,23 @@ 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 function of a quotient tending to oo/oo has the value at the quotient's limit + +**`exp(log(1/x, arcsin(x)))` at 0 from the right is `1/e`, where it was left unevaluated.** The logarithm alone is +-1, and it reaches the descent as `ln(arcsin(x)) / ln(1/x)`, a quotient of two infinities. The limit its derivatives' +quotient was taken to came back as the form `0 * +oo / 0`, which has no value, and that was handed on as the +exponent's limit. The whole limit of the quotient was asked only for a 0/0 read off two finite parts. It is now asked +for -oo/+oo too, and a form with no value is kept only where nothing better is found. A sine of the logarithm is +answered as well. A hyperbolic sine took ten seconds to be left unevaluated, and is answered in under three. +`x * ln(x)` at 0 is still `NaN`, since ln(x) has no real values to the left of 0. + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"e^log(1/x, arcsin(x))".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `limitright(e ^ log(1 / x, arcsin(x)), x, 0)` | `1 / e` | +| `"sin(log(1/x, arcsin(x)))".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `limitright(sin(log(1 / x, arcsin(x))), x, 0)` | `sin(-1)` | +| `"sinh(log(1/x, arcsin(x)))".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `limitright((e ^ log(1 / x, arcsin(x)) - e ^ (-log(1 / x, arcsin(x)))) / 2, x, 0)` | `(1 / e - e) / 2` | +| `"tanh(log(x, arcsin(x)))".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `limitleft((e ^ (2 * log(x, arcsin(x))) - 1) / (e ^ (2 * log(x, arcsin(x))) + 1), x, 0)` | `(e ^ 2 - 1) / (e ^ 2 + 1)` | + ### A function of a quotient tending to 0/0 has the value at the quotient's limit **Silent: a different answer.** A function of a quotient took the quotient's limit from the descent alone, which reads diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs index 3f5fe7713..aa0191d69 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs @@ -93,13 +93,20 @@ partial record Divf { internal override Entity? ComputeLimitDivideEtImpera(Variable x, Entity dist, ApproachFrom side) { - if (ComputeLimitImpl(this, x, dist, side) is { } lim) + // A form with no value -- 0 * +oo / 0, which is what -x / (sqrt(1 - x^2) * arcsin(x)) at 0 + // from the right comes back as -- is the claim that there is no limit, and the parts may + // say otherwise. It is kept for the end, where it was answered before, and the parts are + // read first. + var whole = ComputeLimitImpl(this, x, dist, side); + if (whole is { } lim && lim.Evaled != MathS.NaN) return lim; else { + var dividendLimit = Dividend.ComputeLimitDivideEtImpera(x, dist, side); + var divisorLimit = Divisor.ComputeLimitDivideEtImpera(x, dist, side); var (dividend, divisor) = - (Dividend.ComputeLimitDivideEtImpera(x, dist, side), Divisor.ComputeLimitDivideEtImpera(x, dist, side)) switch + (dividendLimit, divisorLimit) switch { ({ } lim1, { } lim2) when IsDeterminate(New(lim1, lim2), x) => (lim1, lim2), ({ } lim1, { } lim2) when lim1.InnerSimplified.IsFinite && lim2.InnerSimplified.IsFinite && lim2.InnerSimplified != 0 => (lim1, lim2), @@ -130,11 +137,19 @@ partial record Divf // written, sent a definite integral astray. Folded, it is 0, and ln(2) * 2^(1/+oo) is // ln(2), not its decimal. Where it has no // value, what was answered is kept. - if (substituted?.Evaled == MathS.NaN && IsAnIndeterminateQuotient(dividend, divisor) - && AsTheWholeLimit(this, x, dist, side) is { } whole && whole.Evaled is Number { IsNaN: false } value) - return whole.InnerSimplified is var folded && folded.Nodes.Any(static node => node is Number { IsFinite: false }) + // Two infinite parts are not put in place of the parts above, so the substitution is not + // of the parts' limits, and the form is read off the parts' own limits instead: + // exp(ln(arcsin(x)) / ln(1/x)) at 0 from the right, whose exponent is -oo / +oo and + // tends to -1, was left unevaluated, and so were a sine of it and, after ten seconds, a + // hyperbolic sine. + if ((substituted?.Evaled == MathS.NaN && IsAnIndeterminateQuotient(dividend, divisor) + || (substituted is null || substituted.Evaled == MathS.NaN) + && dividendLimit is { } partLimit1 && divisorLimit is { } partLimit2 + && IsAnIndeterminateQuotient(partLimit1, partLimit2)) + && AsTheWholeLimit(this, x, dist, side) is { } asked && asked.Evaled is Number { IsNaN: false } value) + return asked.InnerSimplified is var folded && folded.Nodes.Any(static node => node is Number { IsFinite: false }) ? value : folded; - return substituted; + return whole ?? substituted; } } } diff --git a/Sources/Tests/UnitTests/Calculus/AFunctionOfAQuotientOfInfinitiesTest.cs b/Sources/Tests/UnitTests/Calculus/AFunctionOfAQuotientOfInfinitiesTest.cs new file mode 100644 index 000000000..956c6e3f6 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AFunctionOfAQuotientOfInfinitiesTest.cs @@ -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 +{ + /// + /// exp(log(1/x, arcsin(x))) at 0 from the right was left unevaluated, where the logarithm alone is -1 and + /// the limit is 1/e. The logarithm reaches the descent as ln(arcsin(x)) / ln(1/x), a quotient of + /// two infinities, and the limit its derivatives' quotient was taken to came back as the form 0 * +oo / 0, + /// which has no value; the descent handed that on as the exponent's limit rather than asking the quotient's whole + /// limit, which it asked only for a 0/0 read off two finite parts. A sine of it was left unevaluated too, and a + /// hyperbolic sine after ten seconds. The same on 2.6.0. Found by limitcheck at DEPTH=3, SEED=40; + /// no issue was filed. + /// + [Trait("Area", "Calculus")] + public sealed class AFunctionOfAQuotientOfInfinitiesTest + { + [Theory] + [InlineData("e^log(1/x, arcsin(x))", "1/e")] + [InlineData("sin(log(1/x, arcsin(x)))", "sin(-1)")] + [InlineData("sinh(log(1/x, arcsin(x)))", "(1/e - e)/2")] + [InlineData("log(1/x, arcsin(x))", "-1")] + public void IsTheLimitFromTheRight(string input, string expected) + => Assert.Equal(MathS.FromString(expected).Simplify(), + MathS.FromString(input).Limit("x", 0, ApproachFrom.Right).Simplify()); + + // ln(arcsin(x)) / ln(x) tends to 1 from the left as well, and was left unevaluated there after six seconds. + [Fact] + public void IsTheLimitFromTheLeft() + => Assert.Equal(MathS.FromString("tanh(1)").Simplify(), + MathS.FromString("tanh(log(x, arcsin(x)))").Limit("x", 0, ApproachFrom.Left).Simplify()); + + // The two-sided convention is kept: ln(x) has no real values to the left of 0. + [Theory] + [InlineData("x * ln(x)")] + [InlineData("sin(x) * ln(x)")] + public void HasNoTwoSidedLimit(string input) + => Assert.Equal(MathS.NaN, MathS.FromString(input).Limit("x", 0)); + } +}