Skip to content

Commit 2e3078d

Browse files
Read a limit at -oo on the approach it is rewritten to, and simplify without the memo while one is read (#2029)
lim x->-oo ln(x) + ln(x + 1) - ln(x^2 + x) was 0, on 2.5.0 and on master. Both x and x + 1 are negative there, so each of the first two logarithms carries i pi and the limit is 2 pi i. The limit machinery gathers ln(a) + ln(b) into ln(a b) where a and b hold a positive sign on the approach, which it reads from the approach in scope. SolveByRewriting reads a destination of -oo as +oo with -x written for x, and then simplified the rewritten expression while the approach in scope still said -oo. Against -oo, -x and 1 - x are positive, so ln(-x) + ln(1 - x) was gathered, and it cancelled against ln(x^2 - x). The rewriting now runs with +oo in scope, as the Gruntz branch and SimplifyAndComputeLimitToInfinity already did. And a nested Simplify is no longer looked up in what an earlier one answered while an approach is being read. The memo is keyed on the tree, the level and the settings, and on an approach a simplification can gather logarithms it does not gather on its own account, so one tree has an answer per approach. Through Simplify of a limit node master happened to answer 2 ln(-1) here, because the memo carried an answer across; with the memo bypassed and the limit unfixed it came back 0. Measured on builds of v2.5.0, master 6e16aae and this change: the four limits in the new test that were 0 are now 2 pi i (or its square), and the six that were right are unchanged, through both Limit and Simplify. Without the SolveByRewriting change, those four fail and the rest pass. Indefinite integration never reads an approach, so the bypass does not reach it. Fixes #2022. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 47e7362 commit 2e3078d

4 files changed

Lines changed: 104 additions & 3 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7954,6 +7954,27 @@ arctangent, arccotangent or arccosecant whose argument diverges, which is now an
79547954
| `integral(1/(2 + cos(x)), x, 0, pi)` | `integral(1 / (2 + cos(x)), x, 0, pi)` | `sqrt(3) * pi / 3` |
79557955
| `limitleft(arctan(tan(x / 2)), x, pi)` | `limitleft(arctan(tan(x / 2)), x, pi)` | `pi / 2` |
79567956

7957+
### A limit at -oo keeps the `i pi` of each logarithm of a negative operand
7958+
7959+
**Wrong answers fixed.** `lim ln(x) + ln(x + 1) - ln(x^2 + x)` as `x → -∞` was `0`. Both `x` and
7960+
`x + 1` are negative there, so each of the first two logarithms carries `i pi` and the limit is
7961+
`2 pi i`. A limit at `-oo` is read at `+oo` with `-x` written for `x`, and the logarithms of
7962+
operands holding one sign on the approach are gathered into one; the rewritten expression was
7963+
simplified while the approach still said `-oo`, where `-x` and `1 - x` are positive, and its two
7964+
logarithms were gathered. It is now read on the approach it is on. A nested simplification is also
7965+
no longer answered from what an earlier one returned while a limit is being read, since it can
7966+
gather logarithms there that it does not gather on its own account
7967+
([#2022](https://github.com/asc-community/AngouriMath/issues/2022)). Both columns measured on a
7968+
build, `v2.5.0` against this change.
7969+
7970+
| `"….".ToEntity().Limit("x", "-oo")` of | Was (2.5.0) | Is |
7971+
|---|---|---|
7972+
| `ln(x) + ln(x + 1) - ln(x ^ 2 + x)` | `0` | `ln(-1) + ln(-1)` |
7973+
| `ln(x) + ln(x - 1) - ln(x ^ 2 - x)` | `0` | `ln(-1) + ln(-1)` |
7974+
| `(ln(x) + ln(x + 1) - ln(x ^ 2 + x)) * x / (x + 1)` | `0` | `ln(-1) + ln(-1)` |
7975+
| `(ln(x) + ln(x + 1) - ln(x ^ 2 + x)) ^ 2` | `0` | `(ln(-1) + ln(-1)) ^ 2` |
7976+
| `ln(-x) + ln(-x - 1) - ln(x ^ 2 + x)` | `0` | the same |
7977+
79577978
### A limit at infinity whose terms cancel around a slowly varying factor is answered
79587979

79597980
`lim e^x (exp(1/x + e^(-x)) - exp(1/x))` as `x → ∞` is the example Gonnet and Gruntz introduce

‎Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,13 @@ private static int MemoisedSign(Entity expr, Approach approach)
311311

312312
[System.ThreadStatic] private static int approachDepth;
313313

314+
/// <summary>
315+
/// Whether an approach is being read, and so whether a simplification can answer
316+
/// otherwise than it would on its own account: <see cref="MayGatherLogarithmsHere"/> and
317+
/// <see cref="MayTakeLogOfPowerHere"/> fire only then.
318+
/// </summary>
319+
internal static bool IsReadingAnApproach => currentApproach is not null;
320+
314321
/// <summary>
315322
/// States that <paramref name="x"/> is being read on its way to <paramref name="dest"/>,
316323
/// and returns the previous approach for <see cref="LeaveApproach"/> to put back.
@@ -1435,6 +1442,23 @@ private static bool IsIndeterminateQuotient(Entity numerator, Entity denominator
14351442
// direction only.
14361443
if (dest.Evaled is Real { IsNegative: true })
14371444
expr = expr.Substitute(x, -x);
1445+
// Everything below reads the expression on its way to +oo, which after the
1446+
// substitution is where it is going -- while the approach in scope is still the one
1447+
// the limit was asked for. A sign read against -oo of an expression written in -x is
1448+
// the opposite sign: the simplifier took -x and 1 - x for positive, gathered
1449+
// ln(-x) + ln(1 - x) into one logarithm, and lim x->-oo ln(x) + ln(x + 1) - ln(x^2 + x)
1450+
// came back 0 where it is 2 pi i.
1451+
// https://github.com/asc-community/AngouriMath/issues/2022
1452+
var outerApproach = EnterApproach(x, Real.PositiveInfinity);
1453+
try { return SolveByRewritingTowardsInfinity(expr, x); }
1454+
finally { LeaveApproach(outerApproach); }
1455+
}
1456+
1457+
/// <summary>
1458+
/// <see cref="SolveByRewriting"/> once the destination is +oo.
1459+
/// </summary>
1460+
private static Entity? SolveByRewritingTowardsInfinity(Entity expr, Variable x)
1461+
{
14381462
var toInfinity = Real.PositiveInfinity;
14391463
var simplified = expr.Simplify();
14401464
if (simplified is Providedf(var body, _))

‎Sources/AngouriMath/Functions/Simplification/Simplificator.cs‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@ internal static Entity Simplify(Entity expr, int level)
4646
try
4747
{
4848
// A nested run is looked up in what the runs before it answered; the outermost
49-
// one has no runs before it. See simplifications.
50-
if (simplifying == 1 || RewriteRecording.Current is not null || !expr.Nodes.All(IsElementary))
49+
// one has no runs before it. See simplifications. Not while a limit reads an
50+
// approach, on which logarithms gather that do not gather on their own account:
51+
// the same tree simplified on two approaches, or on one and on none, is two answers.
52+
if (simplifying == 1 || RewriteRecording.Current is not null || !expr.Nodes.All(IsElementary)
53+
|| Algebra.LimitFunctional.IsReadingAnApproach)
5154
return SimplifyAtDepth(expr, level);
5255
var kept = simplifications ??= new();
5356
if (kept.TryGetValue((expr, level), out var known) && Convenience.SettingsState.StillHolds(known.Under))
@@ -148,7 +151,7 @@ internal static Entity Expanded(Entity expr)
148151
return expanded;
149152
}
150153

151-
/// <summary>A node whose simplification reads nothing but its children and the settings.</summary>
154+
/// <summary>A node whose simplification reads nothing but its children and the settings, outside a limit's approach.</summary>
152155
private static bool IsElementary(Entity node)
153156
=> node is Number or Variable or Sumf or Minusf or Mulf or Divf or Powf or Logf
154157
or TrigonometricFunction or Absf or Signumf;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// A limit at -oo is read at +oo with -x written for x, and the logarithms a limit gathers are
16+
/// gathered on the signs their operands hold on the approach. Those signs have to be read on
17+
/// the approach the rewritten expression is on: read against -oo, -x and 1 - x are positive,
18+
/// <c>ln(-x) + ln(1 - x)</c> was gathered into one logarithm, and
19+
/// <c>lim x-&gt;-oo ln(x) + ln(x + 1) - ln(x^2 + x)</c> came back 0. Both operands are negative
20+
/// there, so each logarithm carries <c>i pi</c> and the limit is <c>2 pi i</c>.
21+
/// <a href="https://github.com/asc-community/AngouriMath/issues/2022">#2022</a>
22+
/// </summary>
23+
[Trait("Area", "Calculus")]
24+
public sealed class LogarithmsGatheredOnTheirOwnApproachTest
25+
{
26+
[Theory]
27+
[InlineData("ln(x) + ln(x + 1) - ln(x^2 + x)", "-oo", "2 * i * pi")]
28+
[InlineData("ln(x) + ln(x - 1) - ln(x^2 - x)", "-oo", "2 * i * pi")]
29+
[InlineData("(ln(x) + ln(x + 1) - ln(x^2 + x)) * x / (x + 1)", "-oo", "2 * i * pi")]
30+
[InlineData("(ln(x) + ln(x + 1) - ln(x^2 + x))^2", "-oo", "-4 * pi^2")]
31+
[InlineData("ln(-x) + ln(1 - x) - ln(x^2 - x)", "+oo", "2 * i * pi")]
32+
// Where the operands are positive the logarithms do gather, on either side.
33+
[InlineData("ln(-x) + ln(-x - 1) - ln(x^2 + x)", "-oo", "0")]
34+
[InlineData("ln(-x) + ln(1 - x) - ln(x^2 - x)", "-oo", "0")]
35+
[InlineData("ln(x) + ln(x + 1) - ln(x^2 + x)", "+oo", "0")]
36+
[InlineData("ln(x^2) - 2 * ln(x)", "-oo", "-2 * i * pi")]
37+
[InlineData("ln(x - 1) + ln(x - 2) - ln(x^2 - 3 * x + 2)", "0", "2 * i * pi")]
38+
public void IsTheLimitOfTheLogarithmsAsTheyAre(string expression, string dest, string limit)
39+
{
40+
var want = limit.ToEntity().EvalNumerical();
41+
AssertIs(want, expression.ToEntity().Limit("x", dest), expression, dest, "Limit");
42+
AssertIs(want, $"limit({expression}, x, {dest})".ToEntity().Simplify(), expression, dest, "Simplify");
43+
}
44+
45+
private static void AssertIs(Entity.Number.Complex want, Entity limit, string expression, string dest, string how)
46+
{
47+
Assert.DoesNotContain("limit(", limit.Stringize());
48+
var got = limit.EvalNumerical();
49+
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
50+
Assert.True(difference < 1e-9, $"{how}: lim x->{dest} {expression} is {limit} = {got}, where it is {want}");
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)