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); + } +}