Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7914,6 +7914,27 @@ arctangent, arccotangent or arccosecant whose argument diverges, which is now an
| `integral(1/(2 + cos(x)), x, 0, pi)` | `integral(1 / (2 + cos(x)), x, 0, pi)` | `sqrt(3) * pi / 3` |
| `limitleft(arctan(tan(x / 2)), x, pi)` | `limitleft(arctan(tan(x / 2)), x, pi)` | `pi / 2` |

### A limit at -oo keeps the `i pi` of each logarithm of a negative operand

**Wrong answers fixed.** `lim ln(x) + ln(x + 1) - ln(x^2 + x)` as `x → -∞` was `0`. 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`. A limit at `-oo` is read at `+oo` with `-x` written for `x`, and the logarithms of
operands holding one sign on the approach are gathered into one; the rewritten expression was
simplified while the approach still said `-oo`, where `-x` and `1 - x` are positive, and its two
logarithms were gathered. It is now read on the approach it is on. A nested simplification is also
no longer answered from what an earlier one returned while a limit is being read, since it can
gather logarithms there that it does not gather on its own account
([#2022](https://github.com/asc-community/AngouriMath/issues/2022)). Both columns measured on a
build, `v2.5.0` against this change.

| `"….".ToEntity().Limit("x", "-oo")` of | Was (2.5.0) | Is |
|---|---|---|
| `ln(x) + ln(x + 1) - ln(x ^ 2 + x)` | `0` | `ln(-1) + ln(-1)` |
| `ln(x) + ln(x - 1) - ln(x ^ 2 - x)` | `0` | `ln(-1) + ln(-1)` |
| `(ln(x) + ln(x + 1) - ln(x ^ 2 + x)) * x / (x + 1)` | `0` | `ln(-1) + ln(-1)` |
| `(ln(x) + ln(x + 1) - ln(x ^ 2 + x)) ^ 2` | `0` | `(ln(-1) + ln(-1)) ^ 2` |
| `ln(-x) + ln(-x - 1) - ln(x ^ 2 + x)` | `0` | the same |

### A limit at infinity whose terms cancel around a slowly varying factor is answered

`lim e^x (exp(1/x + e^(-x)) - exp(1/x))` as `x → ∞` is the example Gonnet and Gruntz introduce
Expand Down
24 changes: 24 additions & 0 deletions Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,13 @@ private static int MemoisedSign(Entity expr, Approach approach)

[System.ThreadStatic] private static int approachDepth;

/// <summary>
/// Whether an approach is being read, and so whether a simplification can answer
/// otherwise than it would on its own account: <see cref="MayGatherLogarithmsHere"/> and
/// <see cref="MayTakeLogOfPowerHere"/> fire only then.
/// </summary>
internal static bool IsReadingAnApproach => currentApproach is not null;

/// <summary>
/// States that <paramref name="x"/> is being read on its way to <paramref name="dest"/>,
/// and returns the previous approach for <see cref="LeaveApproach"/> to put back.
Expand Down Expand Up @@ -1435,6 +1442,23 @@ private static bool IsIndeterminateQuotient(Entity numerator, Entity denominator
// direction only.
if (dest.Evaled is Real { IsNegative: true })
expr = expr.Substitute(x, -x);
// Everything below reads the expression on its way to +oo, which after the
// substitution is where it is going -- while the approach in scope is still the one
// the limit was asked for. A sign read against -oo of an expression written in -x is
// the opposite sign: the simplifier took -x and 1 - x for positive, gathered
// ln(-x) + ln(1 - x) into one logarithm, and lim x->-oo ln(x) + ln(x + 1) - ln(x^2 + x)
// came back 0 where it is 2 pi i.
// https://github.com/asc-community/AngouriMath/issues/2022
var outerApproach = EnterApproach(x, Real.PositiveInfinity);
try { return SolveByRewritingTowardsInfinity(expr, x); }
finally { LeaveApproach(outerApproach); }
}

/// <summary>
/// <see cref="SolveByRewriting"/> once the destination is +oo.
/// </summary>
private static Entity? SolveByRewritingTowardsInfinity(Entity expr, Variable x)
{
var toInfinity = Real.PositiveInfinity;
var simplified = expr.Simplify();
if (simplified is Providedf(var body, _))
Expand Down
9 changes: 6 additions & 3 deletions Sources/AngouriMath/Functions/Simplification/Simplificator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ internal static Entity Simplify(Entity expr, int level)
try
{
// A nested run is looked up in what the runs before it answered; the outermost
// one has no runs before it. See simplifications.
if (simplifying == 1 || RewriteRecording.Current is not null || !expr.Nodes.All(IsElementary))
// one has no runs before it. See simplifications. Not while a limit reads an
// approach, on which logarithms gather that do not gather on their own account:
// the same tree simplified on two approaches, or on one and on none, is two answers.
if (simplifying == 1 || RewriteRecording.Current is not null || !expr.Nodes.All(IsElementary)
|| Algebra.LimitFunctional.IsReadingAnApproach)
return SimplifyAtDepth(expr, level);
var kept = simplifications ??= new();
if (kept.TryGetValue((expr, level), out var known) && Convenience.SettingsState.StillHolds(known.Under))
Expand Down Expand Up @@ -148,7 +151,7 @@ internal static Entity Expanded(Entity expr)
return expanded;
}

/// <summary>A node whose simplification reads nothing but its children and the settings.</summary>
/// <summary>A node whose simplification reads nothing but its children and the settings, outside a limit's approach.</summary>
private static bool IsElementary(Entity node)
=> node is Number or Variable or Sumf or Minusf or Mulf or Divf or Powf or Logf
or TrigonometricFunction or Absf or Signumf;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// 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 AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// A limit at -oo is read at +oo with -x written for x, and the logarithms a limit gathers are
/// gathered on the signs their operands hold on the approach. Those signs have to be read on
/// the approach the rewritten expression is on: read against -oo, -x and 1 - x are positive,
/// <c>ln(-x) + ln(1 - x)</c> was gathered into one logarithm, and
/// <c>lim x-&gt;-oo ln(x) + ln(x + 1) - ln(x^2 + x)</c> came back 0. Both operands are negative
/// there, so each logarithm carries <c>i pi</c> and the limit is <c>2 pi i</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/2022">#2022</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class LogarithmsGatheredOnTheirOwnApproachTest
{
[Theory]
[InlineData("ln(x) + ln(x + 1) - ln(x^2 + x)", "-oo", "2 * i * pi")]
[InlineData("ln(x) + ln(x - 1) - ln(x^2 - x)", "-oo", "2 * i * pi")]
[InlineData("(ln(x) + ln(x + 1) - ln(x^2 + x)) * x / (x + 1)", "-oo", "2 * i * pi")]
[InlineData("(ln(x) + ln(x + 1) - ln(x^2 + x))^2", "-oo", "-4 * pi^2")]
[InlineData("ln(-x) + ln(1 - x) - ln(x^2 - x)", "+oo", "2 * i * pi")]
// Where the operands are positive the logarithms do gather, on either side.
[InlineData("ln(-x) + ln(-x - 1) - ln(x^2 + x)", "-oo", "0")]
[InlineData("ln(-x) + ln(1 - x) - ln(x^2 - x)", "-oo", "0")]
[InlineData("ln(x) + ln(x + 1) - ln(x^2 + x)", "+oo", "0")]
[InlineData("ln(x^2) - 2 * ln(x)", "-oo", "-2 * i * pi")]
[InlineData("ln(x - 1) + ln(x - 2) - ln(x^2 - 3 * x + 2)", "0", "2 * i * pi")]
public void IsTheLimitOfTheLogarithmsAsTheyAre(string expression, string dest, string limit)
{
var want = limit.ToEntity().EvalNumerical();
AssertIs(want, expression.ToEntity().Limit("x", dest), expression, dest, "Limit");
AssertIs(want, $"limit({expression}, x, {dest})".ToEntity().Simplify(), expression, dest, "Simplify");
}

private static void AssertIs(Entity.Number.Complex want, Entity limit, string expression, string dest, string how)
{
Assert.DoesNotContain("limit(", limit.Stringize());
var got = limit.EvalNumerical();
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
Assert.True(difference < 1e-9, $"{how}: lim x->{dest} {expression} is {limit} = {got}, where it is {want}");
}
}
}
Loading