From 921b4e672e50b5b874d457cca7a8649763818bf2 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 30 Oct 2026 20:28:10 +0000 Subject: [PATCH] A quotient of logarithms tends to the logarithms of its arguments' limits limit(ln(y + x) / ln(z + x), x, 0) was y / z, where it is ln(y) / ln(z), while the same limit with numbers, ln(x + 2) / ln(x + 3), was ln(2) / ln(3). SolveAsLogarithmDivision builds the quotient of the limits' logarithms, and returns it where it evaluates to a number; its last arm, reached for a symbolic limit, returned the quotient of the limits themselves. It now returns the quotient of their logarithms. LogarithmsOfSymbolicLimitsTest takes six symbolic quotients and one numeric one; the six fail on master. ln(1 + x) / ln(y + x) at 0, which was 1 / y, is 0 provided not ln(y) = 0. The suite, the trio, F#, AOT and the eight harnesses pass. Fixes #2271. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 13 ++++++++ Sources/.editorconfig | 3 ++ .../Limits/Solvers/Limit.Solvers.cs | 5 ++- .../LogarithmsOfSymbolicLimitsTest.cs | 33 +++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 Sources/Tests/UnitTests/Calculus/LogarithmsOfSymbolicLimitsTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e2d1048ee..953b82550 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -73,6 +73,19 @@ definite integral, a sum, a product and a limit bind their variable, and are unc | `"derivative(a x^2, x)".ToEntity().Substitute("a", "x").Evaled` | `2 * x_1 * x` | `2 * x * x` | | `"derivative(x^3, x)".ToEntity().Substitute("x", 1)` | `derivative(x ^ 3, x)` | `apply(lambda(x, derivative(x ^ 3, x)), 1)` | +### A quotient of logarithms tends to the logarithms of its arguments' limits + +**Silent: a limit of a quotient of logarithms whose arguments tend to symbolic values answered the quotient of those +values.** The logarithm is continuous, so the quotient tends to the quotient of the values' logarithms. With numbers it +did, and `ln(x + 2) / ln(x + 3)` at 0 was `ln(2) / ln(3)`. With symbols it was not: +`ln(y + x) / ln(z + x)` at 0 was `y / z` ([#2271](https://github.com/asc-community/AngouriMath/issues/2271)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"limit(ln(y + x) / ln(z + x), x, 0)".ToEntity().Simplify()` | `y / z` | `ln(y) / ln(z)` | +| `"limit(ln(a + 1/x) / ln(b + 1/x), x, +oo)".ToEntity().Simplify()` | `a / b` | `ln(a) / ln(b)` | +| `"limit(ln(1 + x) / ln(y + x), x, 0)".ToEntity().Simplify()` | `1 / y` | `0 provided not ln(y) = 0` | + ### A quadratic whose discriminant is a square is answered without the root **Silent: the same roots, written as what they are.** The quadratic formula wrote `sqrt(b^2 - 4ac)` whatever the diff --git a/Sources/.editorconfig b/Sources/.editorconfig index a846f2a78..5239079c4 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -75,6 +75,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Calculus/ModulusPhaseTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Calculus/LogarithmsOfSymbolicLimitsTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [Tests/UnitTests/Core/DomainConditionInAReadingTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs index 06a5ab1db..96e2960c2 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs @@ -285,7 +285,10 @@ private static bool IsRealValued(Entity expr, Variable x) { Evaled.IsNaN: true } => null, { } res when res.ContainsNode("+oo") || res.ContainsNode("-oo") => div.InnerSimplified, { Evaled: Complex } limit => limit, - _ => upperLogLimit / lowerLogLimit, + // The logarithm is continuous, so the quotient tends to the quotient of the + // limits' logarithms, not of the limits: ln(y + x) / ln(z + x) at 0 answered + // y / z -- https://github.com/asc-community/AngouriMath/issues/2271. + _ => div.InnerSimplified, }; } } diff --git a/Sources/Tests/UnitTests/Calculus/LogarithmsOfSymbolicLimitsTest.cs b/Sources/Tests/UnitTests/Calculus/LogarithmsOfSymbolicLimitsTest.cs new file mode 100644 index 000000000..40629ce6d --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/LogarithmsOfSymbolicLimitsTest.cs @@ -0,0 +1,33 @@ +// +// 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.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A quotient of logarithms whose arguments tend to symbolic values tends to the quotient of those values' logarithms, + /// the logarithm being continuous. It answered the quotient of the values themselves: ln(y + x) / ln(z + x) at 0 was + /// y / z, while the same limit with numbers, ln(x + 2) / ln(x + 3), was ln(2) / ln(3). + /// #2271 + /// + [Trait("Area", "Calculus")] + public sealed class LogarithmsOfSymbolicLimitsTest + { + [Theory] + [InlineData("limit(ln(y + x) / ln(z + x), x, 0)", "ln(y) / ln(z)")] + [InlineData("limitright(ln(a + x) / ln(b + x), x, 0)", "ln(a) / ln(b)")] + [InlineData("limit(ln(a + 1/x) / ln(b + 1/x), x, +oo)", "ln(a) / ln(b)")] + [InlineData("limit(ln(a + e^(-x)) / ln(b + e^(-x)), x, +oo)", "ln(a) / ln(b)")] + [InlineData("limit(log(b + 1/x, a + 1/x), x, +oo)", "ln(a) / ln(b)")] + [InlineData("limit(ln(1 + x) / ln(y + x), x, 0)", "0 provided not ln(y) = 0")] + [InlineData("limit(ln(x + 2) / ln(x + 3), x, 0)", "ln(2) / ln(3)")] + public void ItIsTheQuotientOfTheLogarithms(string limit, string value) + => Assert.Equal(value.ToEntity(), limit.ToEntity().Simplify()); + } +}