diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs index 42eb982b6..c0130ad6e 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs @@ -34,9 +34,30 @@ internal static partial class LimitFunctional finally { LeaveApproach(outerApproach); } } + /// + /// A power of the reciprocal of the variable as a power of the variable: (1/x)^p as + /// x^(-p), and (-1/x)^p as (-1)^p * x^(-p). + /// + /// + /// Exact on the way to +oo, where x is real and positive, and on the principal branch + /// for any exponent. A one-sided limit at 0 is taken by putting 0 + 1/x or 0 - 1/x for + /// x, so these are what the root of the variable turns into, and nothing rewrites them: + /// x^3 * sqrt(1/x) / (x^2 * (1/x)^(-1/2)), which is 1, took 1.6 s, l'Hopital's rule + /// differentiating it sixteen deep, and lim x->0+ ln(x) / ln(sqrt(x)) asked it eight + /// times on the way to 2 and took 16 s. + /// + private static Entity AsAPowerOfTheVariable(Entity node, Variable x) + => node switch + { + Powf(Divf(Integer one, var v), var p) when one == 1 && v == x && !p.ContainsNode(x) => MathS.Pow(x, -p), + Powf(Divf(Integer minusOne, var v), var p) when minusOne == -1 && v == x && !p.ContainsNode(x) + => MathS.Pow(-1, p) * MathS.Pow(x, -p), + _ => node + }; + private static Entity? SimplifyAndComputeLimitToInfinityCore(Entity expr, Variable x) { - expr = expr.Simplify(); + expr = expr.Replace(node => AsAPowerOfTheVariable(node, x)).Simplify(); if (expr is Providedf(var expression, _)) expr = expression; // limits operate assuming a continuous expression even though some points may be undefined. // The second remarkable limit reads a 1^oo, and it ran once, at the top of diff --git a/Sources/Tests/UnitTests/Calculus/AReciprocalsPowerAtInfinityTest.cs b/Sources/Tests/UnitTests/Calculus/AReciprocalsPowerAtInfinityTest.cs new file mode 100644 index 000000000..b12343c82 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AReciprocalsPowerAtInfinityTest.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 System; +using System.Threading.Tasks; +using AngouriMath.Core; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A one-sided limit at 0 is taken at infinity by putting 1/x for x, so a root of the variable arrives + /// there as (1/x)^p, which nothing rewrote. x^3 * sqrt(1/x) / (x^2 * (1/x)^(-1/2)), which is 1, then went + /// to l'Hopital's rule sixteen deep, and ln(x) / ln(sqrt(x)) from the right at 0 asked it eight times on the way + /// to 2: 13 s, and ln(sin(x)) / ln(sqrt(x)) over 20 s. Found by limitcheck at DEPTH=3, + /// SEED=26, as log(sqrt(x), floor(x) + arctan(x)); no issue was filed. + /// + [Trait("Area", "Calculus")] + public sealed class AReciprocalsPowerAtInfinityTest + { + // The cap is far from both: each takes under 0.2 s, and took 13 to over 20 s. + [Theory] + [InlineData("ln(x) / ln(sqrt(x))", "0", ApproachFrom.Right, "2")] + [InlineData("ln(sin(x)) / ln(sqrt(x))", "0", ApproachFrom.Right, "2")] + [InlineData("log(sqrt(x), floor(x) + arctan(x))", "0", ApproachFrom.Right, "2")] + [InlineData("ln(1/x) / ln(sqrt(1/x))", "+oo", ApproachFrom.BothSides, "2")] + public void IsAnsweredPromptly(string input, string destination, ApproachFrom side, string expected) + { + var task = Task.Factory.StartNew(() => MathS.FromString(input).Limit("x", destination, side), + TaskCreationOptions.LongRunning); + Assert.True(task.Wait(TimeSpan.FromSeconds(5)), $"lim {side} x->{destination} {input} did not finish in five seconds"); + Assert.Equal(MathS.FromString(expected), task.Result); + } + + // The reciprocal's power from the left side, which the substitution writes as (-1/x)^p: (-1)^p x^(-p) for x > 0. + [Theory] + [InlineData("sqrt(-x) / x", "0", ApproachFrom.Left, "-oo")] + [InlineData("sqrt(x) * ln(x)", "0", ApproachFrom.Right, "0")] + [InlineData("x^(1/3) / sqrt(x)", "0", ApproachFrom.Right, "+oo")] + public void IsTheLimit(string input, string destination, ApproachFrom side, string expected) + => Assert.Equal(MathS.FromString(expected), MathS.FromString(input).Limit("x", destination, side)); + } +}