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 @@ -240,6 +240,24 @@ compiled `0^(-1/2)` was 0; since that is `NaN`, the root was missed.
| `"sqrt(-4 - 4x) + sqrt(2 + x) = -3 - 4x".ToEntity().Solve("x")` | `{ -1.1951718122545800, -1 }` | `{ -1, … }`, the second root in radicals |
| `"sqrt(x + 5) + sqrt(x) = x".ToEntity().Solve("x")` | `{ 5.6349218075063181 }` | the same root in radicals |

### A limit at a point of continuity is read before it is simplified

**A limit at a finite point takes its value at the point before simplifying the expression, which was the
expensive half: `x / (tanh(1/2) - tan(x))` at 1 took 10 s, and takes 0.1 s.** The value is the same, and its form
can differ: a tangent comes back as the quotient of its sine and cosine. A factorial now declines at its poles, which
reading the value first would otherwise have written down. Some answers move, in both directions, for an expression
whose values are not real on a side. A NaN there follows the convention `x^x` at 0 is pinned to, a side with no real
values making no two-sided limit, where the limit was unevaluated.

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"x / (tanh(1/2) - tan(x))".ToEntity().Limit("x", 1)` | `1 / ((e - 1) / (e + 1) - tan(1))`, in 4.5 s | `1 / ((e - 1) / (e + 1) - sin(1) / cos(1))` |
| `"abs(x + 1 - x^x)".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `NaN` | `limitright(abs(x + 1 - x ^ x), x, 0)` |
| `"abs(x + 1 - x^x)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `NaN` | `limitleft(abs(x + 1 - x ^ x), x, 0)` |
| `"sin(x)^(log(x, pi)^arcsin(x))".ToEntity().Limit("x", 1)` | `limit(sin(x) ^ log(x, pi) ^ arcsin(x), x, 1)` | `NaN` |
| `"(2*(x - pi))^ln(-x)".ToEntity().Limit("x", 0)` | `limit((2 * (x - pi)) ^ ln(-x), x, 0)` | `NaN` |
| `"arctan((-1)^x)^(pi/x/cos(2))".ToEntity().Limit("x", 1/2)` | `limit(arctan((-1) ^ x) ^ (pi / x / cos(2)), x, 1/2)` | `NaN` |

### A radical times a power of its variable is solved exactly

**Silent: an equation in `sqrt(x)` or a cube root of `x` beside powers of `x` is answered in radicals and with all its
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,11 +419,18 @@ partial record Arccotanf

partial record Factorialf
{
internal override Entity? ComputeLimitDivideEtImpera(Variable x, Entity dist, ApproachFrom side) =>
ComputeLimitImpl(this, x, dist, side) is { } lim ? lim
: ComputeLimitImpl(New(
Argument.ComputeLimitDivideEtImpera(x, dist, side) is { IsFinite: true } lim1 ? lim1 : Argument),
x, dist, side);
// Not at a pole: the factorial of a negative integer has no value, and the factorial tends
// to an infinity there whose sign depends on the side, so putting the argument's limit in
// would only write the pole down. x! at -1 came back (-1)! once that limit arrived as -1.
internal override Entity? ComputeLimitDivideEtImpera(Variable x, Entity dist, ApproachFrom side)
{
if (ComputeLimitImpl(this, x, dist, side) is { } lim)
return lim;
var argumentLimit = Argument.ComputeLimitDivideEtImpera(x, dist, side);
if (argumentLimit?.Evaled is Integer { EInteger.Sign: < 0 })
return null;
return ComputeLimitImpl(New(argumentLimit is { IsFinite: true } lim1 ? lim1 : Argument), x, dist, side);
}
}

// The upper incomplete gamma function, continuous in z away from 0, and at 0 for Re a > 0, where it is Gamma(a): the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,18 @@ private static Entity AsAPowerOfTheVariable(Entity node, Variable x)

private static Entity? SimplifyAndComputeLimitToInfinityCore(Entity expr, Variable x)
{
expr = expr.Replace(node => AsAPowerOfTheVariable(node, x)).Simplify();
expr = expr.Replace(node => AsAPowerOfTheVariable(node, x));
// The value at the destination, read before the simplification below as well as after
// it: the substitution is what answers a continuous point in any case, under the same
// guards for a jump, a cut and an indeterminate power, and simplifying first is the
// expensive half. x / (tanh(1/2) - tan(x)) at 1, moved out to infinity as
// x = 1 + 1/x, took 10 s to simplify for a value the substitution reads in
// milliseconds. A real number only: a complex one can be the value on a cut, and the
// guards for those after this were written against the simplified form.
if (LimitSolvers.SolveBySubstitution(expr, x) is { } beforeSimplifying
&& beforeSimplifying.Evaled is Real { IsFinite: true })
return beforeSimplifying.InnerSimplified;
expr = expr.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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// 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
{
/// <summary>
/// A limit at a finite point is taken by moving the variable out to infinity, and the expression was simplified there
/// before its value was read: <c>x / (tanh(1/2) - tan(x))</c> at 1, a point where it is continuous, took 10 s, and
/// <c>tanh(x) / (tanh(1/2) - tan(x))</c> 18 s, for a value the substitution reads in milliseconds. Found by
/// limitcheck at <c>DEPTH=3</c>, <c>SEED=28</c>, as twelve limits that did not finish in 5 s; no issue was filed.
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AContinuousPointIsReadBeforeSimplifyingTest
{
// The cap is far from both: each takes about 0.1 s, and took 10 to 18 s.
[Theory]
[InlineData("x / (tanh(1/2) - tan(x))", "1", ApproachFrom.BothSides)]
[InlineData("tanh(x) / (tanh(1/2) - tan(x))", "1", ApproachFrom.BothSides)]
[InlineData("tanh(x) / (tanh(1/2) - tan(x))", "-1", ApproachFrom.Left)]
[InlineData("tanh(x) / (tanh(1/2) - tan(x))", "2", ApproachFrom.Right)]
public void IsTheValueThereAndPrompt(string input, string destination, ApproachFrom side)
{
var expression = MathS.FromString(input);
var task = Task.Factory.StartNew(() => expression.Limit("x", destination, side), TaskCreationOptions.LongRunning);
Assert.True(task.Wait(TimeSpan.FromSeconds(5)), $"lim {side} x->{destination} {input} did not finish in five seconds");
var difference = (task.Result - expression.Substitute("x", destination)).EvalNumerical();
Assert.True(difference is Entity.Number.Complex value && Entity.Number.Complex.Abs(value) is Entity.Number.Real abs && abs < 1e-10,
$"lim {side} x->{destination} {input} = {task.Result}");
}

// A pole of the factorial is not a value to read: x! at -1 has an infinity of each sign on its two sides.
[Fact]
public void TheFactorialAtAPoleIsNotItsValue()
{
Assert.IsType<Entity.Limitf>(MathS.FromString("x!").Limit("x", -1, ApproachFrom.Left));
Assert.IsType<Entity.Limitf>(MathS.FromString("x!").Limit("x", -1, ApproachFrom.Right));
}
}
}
Loading