From e253a421d36ec3f768baa8aae658e8e6a3a749ea Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 4 Nov 2026 18:05:55 +0000 Subject: [PATCH 1/2] Take a factor continuous at the point out before l'Hopital's rule sin(x)^tanh(x) at 0 from the right was left unevaluated, where it is 1, and tan(2x)^sin(tanh(x)) declined after thirteen seconds (2.6.0 does not finish it in two minutes). Read through its exponent, the power is a quotient the rule takes, and the rule differentiated every factor of both parts, including those that tend to a number other than 0, such as the 1 - tanh(x)^2 that tanh's derivative brings. With tanh written through exponentials, each such factor doubled the derivative, and it outgrew the rule's bound. A factor that is continuous at a finite destination and has a finite, 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). 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. A limit that is an infinity stays that infinity. limitcheck at DEPTH=3, 18 seeds 14-45, master against this: 10 more compared over 6 seeds, 4 fewer that did not finish in 5 s, and nothing moved to another value or to NaN. Suite 18982 passed, AOT and the eight harnesses clean. Co-Authored-By: Claude Opus 5.5 --- BREAKING-CHANGES.md | 15 ++++++ .../Continuous/Limits/Transformations.cs | 52 +++++++++++++++++-- ...ContinuousFactorIsNotDifferentiatedTest.cs | 47 +++++++++++++++++ 3 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/AContinuousFactorIsNotDifferentiatedTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 5f1c2ff46..7cf8b322b 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -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 diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs index 2db75a3b9..69faf2f5a 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs @@ -1573,6 +1573,42 @@ bool DoesNotSettle(Entity argument) => argument.ContainsNode(x) && EvalAssumingContinuous(argument.Limit(x, dest, side)) is not Real { IsFinite: true }; } + /// + /// The factors of that are continuous at a finite destination with a + /// finite non-zero value there, as that value, and the product of the rest. + /// + /// + /// 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. + /// + 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) @@ -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 @@ -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; } /// diff --git a/Sources/Tests/UnitTests/Calculus/AContinuousFactorIsNotDifferentiatedTest.cs b/Sources/Tests/UnitTests/Calculus/AContinuousFactorIsNotDifferentiatedTest.cs new file mode 100644 index 000000000..f8a10aa60 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AContinuousFactorIsNotDifferentiatedTest.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 +{ + /// + /// sin(x)^tanh(x) at 0 from the right was left unevaluated, where it is 1, and tan(2x)^sin(tanh(x)) + /// 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 + /// 1 - tanh(x)^2 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 + /// DEPTH=3, SEED=40; no issue was filed. + /// + [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()); + } + } +} From 4a4e752bdb1a1843b445f1d2d12b145b83b9134f Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 4 Nov 2026 18:06:25 +0000 Subject: [PATCH 2/2] Name the seed that found tan(2x)^sin(tanh(x)): 42 Co-Authored-By: Claude Opus 5.5 --- .../Calculus/AContinuousFactorIsNotDifferentiatedTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Tests/UnitTests/Calculus/AContinuousFactorIsNotDifferentiatedTest.cs b/Sources/Tests/UnitTests/Calculus/AContinuousFactorIsNotDifferentiatedTest.cs index f8a10aa60..4f9506131 100644 --- a/Sources/Tests/UnitTests/Calculus/AContinuousFactorIsNotDifferentiatedTest.cs +++ b/Sources/Tests/UnitTests/Calculus/AContinuousFactorIsNotDifferentiatedTest.cs @@ -17,7 +17,7 @@ namespace AngouriMath.Tests.Calculus /// 1 - tanh(x)^2 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 - /// DEPTH=3, SEED=40; no issue was filed. + /// DEPTH=3, SEED=42; no issue was filed. /// [Trait("Area", "Calculus")] public sealed class AContinuousFactorIsNotDifferentiatedTest