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
18 changes: 18 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,24 @@ mentions answers it as a parameter, as a short system does, where it was `null`
| `MathS.Equations("x - 1", "x^2 - 1").Solve("x", "y")` | `null` | `[[1, t_1]]` |
| `MathS.Equations("a - 1", "b * 0").Solve("a", "b")` | `null` | `[[1, t_1]]` |

### A limit on a logarithm's cut is not read off the principal value

**Silent: a limit of a logarithm, or of a power to an exponent that is not whole, whose argument arrives on the negative
real line other than along it is left unevaluated.** The principal logarithm is continuous on its cut from above only, and
its value there was given as the limit from any side: `x^x` comes to -1 from below as x comes down to -1, so
`limitright(ln(x^x), x, -1)` is `-i pi`, and it was `ln(-1)`, which is `i pi`. Which side an argument off the real line comes
from is not decided, so a limit from above, whose answer was right, is left unevaluated too. Along the real line the value
on the cut is the limit, and `limit(ln(x), x, -1)` is still `ln(-1)`
([#2266](https://github.com/asc-community/AngouriMath/issues/2266)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"limitright(ln(x^x), x, -1)".ToEntity().Simplify()` | `ln(-1)`, which is `i pi` | unevaluated; the limit is `-i pi` |
| `"limit(ln(x^x), x, -1)".ToEntity().Simplify()` | `ln(-1)` | unevaluated; there is no limit |
| `"limitleft(ln(-1 + i*x), x, 0)".ToEntity().Simplify()` | `ln(-1)` | unevaluated; the limit is `-i pi` |
| `"limitright(sqrt(x^x), x, -1)".ToEntity().Simplify()` | `i` | unevaluated; the limit is `-i` |
| `"limitleft(ln(x^x), x, -1)".ToEntity().Simplify()` | `ln(-1)`, which is right | unevaluated |

### `Simplify` does not add a condition the input does not have

**Silent: an answer defined at fewer points is defined at all of the input's.** A polynomial division in a
Expand Down
3 changes: 3 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[Tests/UnitTests/Calculus/PowerSubstitutionIntegralTest.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/LimitOnABranchCutTest.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/ProductOfPowersDerivativeTest.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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ private static void Trace(string what, object? value)
{
if (depth > 0)
return null; // already inside; the caller is the entry
// The algorithm is for real functions, and its series takes the logarithm of a
// negative leading coefficient as the principal one, which is the limit from above
// the cut. ln(x^x) at -1 from the right is ln((1/x - 1)^(1/x - 1)) here, whose
// argument comes to -1 from below, and it answered i pi where the limit is -i pi.
// https://github.com/asc-community/AngouriMath/issues/2266
if (expr.Nodes.Any(node => LimitFunctional.TakesItsValueOnACut(node, x, Real.PositiveInfinity)))
return null;
expr = AsExponentials(expr, x);
return LimitInf(expr, x);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,15 @@ partial record Logf
return lim;
else
{
var @base = Base.ComputeLimitDivideEtImpera(x, dist, side) is { IsFinite: true } lim1 && lim1 != 0 ? lim1 : Base;
var power = Antilogarithm.ComputeLimitDivideEtImpera(x, dist, side) is { IsFinite: true } lim2 && lim2 != 0 ? lim2 : Antilogarithm;
var baseLimit = Base.ComputeLimitDivideEtImpera(x, dist, side);
var antilogarithmLimit = Antilogarithm.ComputeLimitDivideEtImpera(x, dist, side);
// The logarithm of a limit on the cut is the limit only from above or along the
// real line. https://github.com/asc-community/AngouriMath/issues/2266
if (ArrivesOnTheCutOffTheRealLine(Base, baseLimit, x, dist)
|| ArrivesOnTheCutOffTheRealLine(Antilogarithm, antilogarithmLimit, x, dist))
return null;
var @base = baseLimit is { IsFinite: true } lim1 && lim1 != 0 ? lim1 : Base;
var power = antilogarithmLimit is { IsFinite: true } lim2 && lim2 != 0 ? lim2 : Antilogarithm;
MultithreadingFunctional.ExitIfCancelled();
return ComputeLimitImpl(New(@base, power), x, dist, side);
}
Expand All @@ -256,8 +263,14 @@ partial record Powf
{
if (ComputeLimitImpl(this, x, dist, side) is { } lim)
return lim;
var baseLimit = Base.ComputeLimitDivideEtImpera(x, dist, side);
var exponentLimit = Exponent.ComputeLimitDivideEtImpera(x, dist, side);
// A power to an exponent that is not whole has the logarithm's cut, for the reason
// the logarithm above declines it. https://github.com/asc-community/AngouriMath/issues/2266
if (exponentLimit?.Evaled is not Integer && ArrivesOnTheCutOffTheRealLine(Base, baseLimit, x, dist))
return null;
var substituted =
(Base.ComputeLimitDivideEtImpera(x, dist, side), Exponent.ComputeLimitDivideEtImpera(x, dist, side))
(baseLimit, exponentLimit)
switch {
({ IsFinite: true } lim1, { IsFinite: true } lim2) => New(lim1, lim2),
(_, { } l2) when !Base.ContainsNode(x) => New(Base, l2),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ internal static class LimitSolvers
{
MultithreadingFunctional.ExitIfCancelled();
if (limit == Real.NaN) return null;
// Nor where a logarithm's argument, or a power's base, arrives on the cut off the
// real line, where the value there is the limit from above and not from below.
// https://github.com/asc-community/AngouriMath/issues/2266
if (expr.Nodes.Any(node => LimitFunctional.TakesItsValueOnACut(node, x, Infinity))) return null;
// Reading the value at the destination is the limit only where the expression
// is continuous there, and an indeterminate form is exactly where it is not.
// Every other one of them already declines on the line above, because this
Expand Down Expand Up @@ -160,6 +164,7 @@ static bool OnAJump(Entity argument, System.Func<Real, bool> jump)
MultithreadingFunctional.ExitIfCancelled();
var innerLimit = LimitFunctional.ComputeLimit(logArgument, x, Real.PositiveInfinity);
if (innerLimit is null) return null;
if (LimitFunctional.ArrivesOnTheCutOffTheRealLine(logArgument, innerLimit, x, Real.PositiveInfinity)) return null;
// do same as wolframalpha: https://www.wolframalpha.com/input/?i=ln%28-inf%29
if (innerLimit == Real.NegativeInfinity) return Real.PositiveInfinity;
if (innerLimit == Real.PositiveInfinity) return Real.PositiveInfinity;
Expand Down Expand Up @@ -327,6 +332,9 @@ private static bool LogarithmDiverges(Entity argumentLimit)
else

{
if (LimitFunctional.ArrivesOnTheCutOffTheRealLine(upperLogArgument, upperLogLimit, x, Real.PositiveInfinity)
|| LimitFunctional.ArrivesOnTheCutOffTheRealLine(lowerLogArgument, lowerLogLimit, x, Real.PositiveInfinity))
return null;
var div = (MathS.Ln(upperLogLimit) / MathS.Ln(lowerLogLimit));
var divEvaled = div.Evaled;
return divEvaled switch
Expand Down
70 changes: 70 additions & 0 deletions Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,76 @@ internal static bool HoldsAnIndeterminatePowerForm(Entity expr)
=> expr.Nodes.Any(node => node is Powf(var @base, var exponent)
&& IsIndeterminatePowerForm(@base, exponent));

/// <summary>
/// Whether <paramref name="argument"/>, a logarithm's or the base of a power, tends to
/// <paramref name="limit"/> on the negative real line, where the logarithm has its cut, and
/// is not known to arrive there along the real line.
/// </summary>
/// <remarks>
/// The principal logarithm is continuous on its cut from above only. Its value there,
/// <c>ln(-1) = i pi</c>, is the limit along the line and from above, and from below the limit
/// is <c>-i pi</c>. So a limit read off a logarithm of an argument's limit is that limit only
/// where the argument does not come from below: <c>x^x</c> tends to -1 from below as x comes
/// down to -1, and <c>limitright(ln(x^x), x, -1)</c> answered <c>i pi</c>. A power to an
/// exponent that is not whole is <c>e^(exponent * ln(base))</c> and has the same cut. Which
/// side an argument off the real line comes from is not decided here, so a limit there is
/// declined, including one from above, whose answer was right.
/// <a href="https://github.com/asc-community/AngouriMath/issues/2266">#2266</a>
/// </remarks>
internal static bool ArrivesOnTheCutOffTheRealLine(Entity argument, Entity? limit, Variable x, Entity dest)
=> argument.ContainsNode(x)
&& limit?.Evaled is Real { IsFinite: true, IsNegative: true }
&& !IsRealOnTheApproach(argument, x, dest);

/// <summary>
/// Whether a node of an expression that is about to be read at <paramref name="dest"/> is a
/// logarithm or a power whose argument arrives on the cut off the real line, by
/// <see cref="ArrivesOnTheCutOffTheRealLine"/>, with the argument's value at the destination
/// as its limit. A power to an exponent whose value there is whole is continuous across the
/// cut, where each of its values is reached from both sides.
/// </summary>
internal static bool TakesItsValueOnACut(Entity node, Variable x, Entity dest)
{
return node switch
{
Logf(var @base, var antilogarithm) => OnTheCut(@base) || OnTheCut(antilogarithm),
Powf(var @base, var exponent) => exponent.Substitute(x, dest).Evaled is not Integer && OnTheCut(@base),
_ => false
};
bool OnTheCut(Entity argument) => ArrivesOnTheCutOffTheRealLine(argument, argument.Substitute(x, dest), x, dest);
}

/// <summary>
/// Whether <paramref name="expr"/> is real as <paramref name="x"/> runs along the real line to
/// <paramref name="dest"/>, decided structurally, as <see cref="IsRealAlong"/> is. A power's
/// base and a logarithm's argument count as positive where their values at the destination
/// are, which they then are near it. Anything else is
/// <see cref="TreeAnalyzer.IsRealValued"/>'s, and unlisted is not real, which costs
/// coverage and never correctness.
/// </summary>
private static bool IsRealOnTheApproach(Entity expr, Variable x, Entity dest)
{
if (dest.Evaled is not Real)
return false;
if (!expr.ContainsNode(x))
return expr.Evaled is Real { IsFinite: true };
return expr switch
{
Variable variable => variable == x,
Sumf(var augend, var addend) => Along(augend) && Along(addend),
Minusf(var minuend, var subtrahend) => Along(minuend) && Along(subtrahend),
Mulf(var multiplier, var multiplicand) => Along(multiplier) && Along(multiplicand),
Divf(var dividend, var divisor) => Along(dividend) && Along(divisor),
Powf(var @base, Integer) => Along(@base),
Powf(var @base, var exponent) => Along(@base) && Along(exponent) && PositiveThere(@base),
Logf(var @base, var antilogarithm) => Along(@base) && Along(antilogarithm)
&& PositiveThere(@base) && PositiveThere(antilogarithm),
_ => TreeAnalyzer.IsRealValued(expr, x)
};
bool Along(Entity part) => IsRealOnTheApproach(part, x, dest);
bool PositiveThere(Entity part) => part.Substitute(x, dest).Evaled is Real { IsPositive: true };
}

/// <summary>
/// Whether the exponent grows without bound, which is all the second remarkable
/// limit needs.
Expand Down
68 changes: 68 additions & 0 deletions Sources/Tests/UnitTests/Calculus/LimitOnABranchCutTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// 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.Linq;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// A limit of a logarithm, or of a power to an exponent that is not whole, whose argument arrives on the negative real
/// line other than along it is not read off the principal value there. The principal logarithm is continuous on its cut
/// from above only: x^x comes to -1 from below as x comes down to -1, so limitright(ln(x^x), x, -1) is -i pi, and it
/// answered ln(-1), which is i pi. Each expected value is the expression's near the destination.
/// <a href="https://github.com/asc-community/AngouriMath/issues/2266">#2266</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class LimitOnABranchCutTest
{
/// <summary>From below the cut, or from both sides of it, where the principal value is not the limit.</summary>
[Theory]
[InlineData("limitright(ln(x^x), x, -1)", "-i pi")]
[InlineData("limitright(sqrt(x^x), x, -1)", "-i")]
[InlineData("limitright(log(2, x^x), x, -1)", "-i pi / ln(2)")]
[InlineData("limitright(ln(x^x) * x, x, -1)", "i pi")]
[InlineData("limitleft(ln(-1 + i x), x, 0)", "-i pi")]
[InlineData("limitleft(sqrt(-1 + i x), x, 0)", "-i")]
[InlineData("limitleft(ln(-1 - i x^2), x, 0)", "-i pi")]
[InlineData("limitright(ln(-e^(i x)), x, 0)", "-i pi")]
public void ItIsTheLimitOrNothing(string limit, string value)
{
var answer = limit.ToEntity().Simplify();
Assert.True(IsUnevaluated(answer) || answer.Evaled == value.ToEntity().Evaled, $"{limit} is {answer}, where it is {value}");
}

/// <summary>Where the sides disagree there is no limit, and the principal value was given for it.</summary>
[Theory]
[InlineData("limit(ln(x^x), x, -1)")]
[InlineData("limit(ln(-1 + i x), x, 0)")]
[InlineData("limit(ln(e^(i x) - 2), x, pi)")]
public void ThereIsNone(string limit)
{
var answer = limit.ToEntity().Simplify();
Assert.True(IsUnevaluated(answer) || answer == MathS.NaN, $"{limit} is {answer}");
}

/// <summary>A limit left as a limit, where ln(-1) is an answer like any number.</summary>
private static bool IsUnevaluated(Entity answer) => answer.Nodes.Any(node => node is Entity.Limitf);

/// <summary>Along the real line the value on the cut is the limit, and is answered as before.</summary>
[Theory]
[InlineData("limit(ln(x), x, -1)", "i pi")]
[InlineData("limitright(ln(x), x, -1)", "i pi")]
[InlineData("limit(ln(x - 2), x, 0)", "ln(-2)")]
[InlineData("limit(sqrt(x - 2), x, 0)", "sqrt(-2)")]
[InlineData("limit(ln(-1 + x^2), x, 0)", "i pi")]
[InlineData("limit(x^(1/3), x, -8)", "-2")]
[InlineData("limitright(x^x, x, -1)", "-1")]
[InlineData("limitright(ln(x^x), x, -1/2)", "ln((-1/2)^(-1/2))")]
[InlineData("limit(ln(sin(x) - 2), x, 0)", "ln(-2)")]
public void AlongTheRealLineItIsTheValue(string limit, string value)
=> Assert.Equal(value.ToEntity().Evaled, limit.ToEntity().Simplify().Evaled);
}
}
Loading