From 513d53638e179f21cd6839deeb0fbeb8b55c84eb Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 5 Aug 2026 00:19:49 +0000 Subject: [PATCH] Put a leftover reciprocal under the line, not on top of it The rewrite that turns a vanishing factor against a diverging one into a quotient multiplied every remaining factor into the numerator. The children of a product arrive flattened through any division in it -- tan(x) * ln(x) comes apart into sin(x), cos(x)^(-1) and ln(x) -- so a leftover could itself be a reciprocal, and putting that on top rebuilt the very product the rewrite exists to take apart. The quotient simplified straight back to its own input and the rule worked at it until something else stopped it. Split into a numerator and a denominator instead. lim x->0+ (x * ln(x) / cos(x)) went from not terminating inside thirty seconds to 0. Suite 4675 passed, 0 failed; corpus unchanged at 111/117 with 0 wrong, 0 error and 0 timeout. --- .../Continuous/Limits/Transformations.cs | 14 +++-- .../Calculus/VanishingTimesDivergingTest.cs | 52 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/VanishingTimesDivergingTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs index 667aed301..e6a12cf6f 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs @@ -391,9 +391,17 @@ private static bool IsIndeterminateQuotient(Entity numerator, Entity denominator else rest *= factor; } - return vanishing is { } zero && diverging is { } infinity - ? rest * infinity / (1 / zero).InnerSimplified - : null; + if (vanishing is not { } zero || diverging is not { } infinity) + return null; + // The leftover factors are split rather than multiplied on top, because the children + // of a product arrive flattened through any division in it: tan(x) * ln(x) comes + // apart into sin(x), cos(x)^(-1) and ln(x), and putting that cos(x)^(-1) into the + // numerator would rebuild the very product this is meant to take apart -- the + // quotient would simplify straight back to it and the rule would be handed its own + // input. Into the denominator it gives ln(x) / (cos(x) / sin(x)), which is oo/oo + // and which the rule settles at 0. + var (restNumerator, restDenominator) = SplitProduct(rest); + return restNumerator * infinity / (restDenominator * (1 / zero)).InnerSimplified; } /// diff --git a/Sources/Tests/UnitTests/Calculus/VanishingTimesDivergingTest.cs b/Sources/Tests/UnitTests/Calculus/VanishingTimesDivergingTest.cs new file mode 100644 index 000000000..20d50d7cb --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/VanishingTimesDivergingTest.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) 2019-2022 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.Core; +using AngouriMath.Extensions; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A product of something vanishing with something diverging, where the product also carries + /// a factor that is neither. The children of a product arrive flattened through any division + /// in it, so those leftovers can include a reciprocal, and multiplying one of those back on + /// top rebuilt the very product the rewrite exists to take apart -- the quotient simplified + /// straight back and the rule was handed its own input. Split into the denominator instead, + /// x * ln(x) / cos(x) is answered rather than run at until it times out. + /// + public sealed class VanishingTimesDivergingTest + { + private static void AssertLimit(string expression, string expected) + { + var task = Task.Run(() => + expression.ToEntity().Limit("x", 0, ApproachFrom.Right).Simplify()); + Assert.True(task.Wait(TimeSpan.FromSeconds(30)), "the limit did not terminate"); + Assert.Equal(expected.ToEntity().Evaled, task.Result.Evaled); + } + + [Theory] + [InlineData("x * ln(x) / cos(x)", "0")] + [InlineData("x * ln(x) / (1 + x)", "0")] + [InlineData("sqrt(x) * ln(x) / (2 + x)", "0")] + public void ALeftoverReciprocalGoesUnderTheLine(string expression, string expected) => + AssertLimit(expression, expected); + + /// The plain forms, which have no leftover at all, are unaffected. + [Theory] + [InlineData("x * ln(x)", "0")] + [InlineData("sin(x) * ln(x)", "0")] + [InlineData("sqrt(x) * ln(x)", "0")] + [InlineData("x * cotan(x)", "1")] + [InlineData("(1 - cos(x)) * cotan(x)", "0")] + public void ThePlainFormsAreUnaffected(string expression, string expected) => + AssertLimit(expression, expected); + } +}