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
19 changes: 18 additions & 1 deletion BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,23 @@ root near 23.
| `"x^7 - 5x^6 + 1 = 0".Solve("x")` | six roots, none of them near 5 | seven roots |
| `"x^4 - 3*x^(5/2) + 2 = 0".Solve("x")` | `{ 1, 4.8870…^(2/5) }` | four roots: `1`, `1.3734…^2`, and two off the real line |

### A trigonometric function reads the value its argument tends to

**A one-sided limit at a finite point of a trigonometric function is answered, where it was left unevaluated.** The
limit is taken by moving the variable out to infinity, and the value read off there kept the infinity in it:
`x^2 + x - 1` from the left at 0 came back as `-1 - 1/+oo + 1/+oo^2`. The function above it asked whether that was
finite, was told no, and declined. A trigonometric function's argument now has the infinities folded out of its limit.
A function with a cut, such as the arctangent or the logarithm, still declines there: it is not continuous on its cut,
and folding would answer with the value on it.

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"sin(x^2 + x - 1)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `limitleft(sin(x ^ 2 + x - 1), x, 0)` | `sin(-1)` |
| `"sin(x^2 + x - 1)".ToEntity().Limit("x", 0)` | `limit(sin(x ^ 2 + x - 1), x, 0)` | `sin(-1)` |
| `"cos(x^2 + x + 2)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `limitleft(cos(x ^ 2 + x + 2), x, 0)` | `cos(2)` |
| `"tan(x^2 + x + 2)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `limitleft(tan(x ^ 2 + x + 2), x, 0)`, in 4.3 s | `sin(2) / cos(2)` |
| `"tan(x^2 + x - 1)".ToEntity().Limit("x", 0)` | `limit(tan(x ^ 2 + x - 1), x, 0)` | `sin(-1) / cos(-1)` |

### A quartic that is biquadratic once depressed is solved

**Silent: a quartic whose depressed form `y^4 + alpha y^2 + gamma`, for `y = x + b/(4a)`, has no cubic term is solved,
Expand Down Expand Up @@ -150,7 +167,7 @@ the left at -1, which is `-1 - 0.129 i`, and which 2.7.0's previous changes had
|---|---|---|
| `"tan(x^2 + x - 1)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `-cos(-1) / sin(-1)` | `tan(-1)` |
| `"sin(x^2 + x - 1) / cos(x^2 + x - 1)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `-cos(-1) / sin(-1)` | `tan(-1)` |
| `"tan(x^2 - x - 1)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `-cos(-1) / sin(-1)` | `limitleft(tan(x ^ 2 - x - 1), x, 0)` |
| `"tan(x^2 - x - 1)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `-cos(-1) / sin(-1)` | `sin(-1) / cos(-1)`, with the entry below |
| `"-x + ln(2*pi*x)/2".ToEntity().Limit("x", "+oo", ApproachFrom.Left)` | `limitleft(-x + ln(2 * pi * x) / 2, x, +oo)` | `-oo` |
| `"x - sqrt(x^2 + x)".ToEntity().Limit("x", "+oo", ApproachFrom.Left)` | `limitleft(x - sqrt(x ^ 2 + x), x, +oo)` | `-1/2` |
| `"e^x * x".ToEntity().Limit("x", "-oo", ApproachFrom.Right)` | `limitright(e ^ x * x, x, -oo)` | `0` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,27 @@ partial record Modf
}
}

/// <summary>
/// A part's limit with the destination folded out of it. A one-sided limit at a finite
/// point moves the variable out to infinity, and the value read off there keeps the
/// infinity in it: x^2 + x - 1 from the left at 0 is -1 - 1/+oo + 1/+oo^2, which is -1
/// but is not finite to the check above, so sin(x^2 + x - 1) from the left at 0 was left
/// unevaluated. Only for the trigonometric functions, which have no cut, and so are
/// continuous at every finite argument short of a pole. A function with a cut is not:
/// arctan and the logarithm decline there by their own reading, and folding their
/// argument's limit would answer with the value on the cut.
/// </summary>
private static Entity? WithoutTheDestination(Entity? limit)
=> limit is { } found && found.Nodes.Any(node => node is Real { IsFinite: false })
&& found.InnerSimplified is var folded && !folded.Nodes.Any(node => node is Real { IsFinite: false })
? folded : limit;

partial record Sinf
{
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),
WithoutTheDestination(Argument.ComputeLimitDivideEtImpera(x, dist, side)) is { IsFinite: true } lim1 ? lim1 : Argument),
x, dist, side);
}

Expand All @@ -207,7 +222,7 @@ partial record Cosf
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),
WithoutTheDestination(Argument.ComputeLimitDivideEtImpera(x, dist, side)) is { IsFinite: true } lim1 ? lim1 : Argument),
x, dist, side);
}

Expand All @@ -216,7 +231,7 @@ partial record Secantf
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),
WithoutTheDestination(Argument.ComputeLimitDivideEtImpera(x, dist, side)) is { IsFinite: true } lim1 ? lim1 : Argument),
x, dist, side);
}

Expand All @@ -225,7 +240,7 @@ partial record Cosecantf
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),
WithoutTheDestination(Argument.ComputeLimitDivideEtImpera(x, dist, side)) is { IsFinite: true } lim1 ? lim1 : Argument),
x, dist, side);
}

Expand Down Expand Up @@ -265,7 +280,7 @@ partial record Tanf
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),
WithoutTheDestination(Argument.ComputeLimitDivideEtImpera(x, dist, side)) is { IsFinite: true } lim1 ? lim1 : Argument),
x, dist, side);
}

Expand All @@ -274,7 +289,7 @@ partial record Cotanf
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),
WithoutTheDestination(Argument.ComputeLimitDivideEtImpera(x, dist, side)) is { IsFinite: true } lim1 ? lim1 : Argument),
x, dist, side);
}

Expand Down
47 changes: 47 additions & 0 deletions Sources/Tests/UnitTests/Calculus/ASubstitutedValueIsFoldedTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// 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.Core;
using Xunit;
using static AngouriMath.Entity;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// A one-sided limit at a finite point is taken by moving the variable out to infinity, and the value read off there kept
/// the infinity in it: <c>x^2 + x - 1</c> from the left at 0 came back to the descent as <c>-1 - 1/+oo + 1/+oo^2</c>.
/// The sine above it asked whether that was finite, was told no, and declined -- so <c>sin(x^2 + x - 1)</c> from
/// the left at 0 was left unevaluated, as on 2.6.0. Found by limitcheck at <c>DEPTH=3</c>, <c>SEED=26</c>; no issue
/// was filed.
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ASubstitutedValueIsFoldedTest
{
[Theory]
[InlineData("sin(x^2 + x - 1)", ApproachFrom.Left, "sin(-1)")]
[InlineData("cos(x^2 + x + 2)", ApproachFrom.Left, "cos(2)")]
[InlineData("sin(x^2 + x - 1)", ApproachFrom.BothSides, "sin(-1)")]
[InlineData("sin(x^2 + x - 1)", ApproachFrom.Right, "sin(-1)")]
public void IsTheValueAtTheDestination(string input, ApproachFrom side, string expected)
=> Assert.Equal(MathS.FromString(expected), MathS.FromString(input).Limit("x", 0, side));

// Answered as a quotient of the two, which is the tangent's value.
[Theory]
[InlineData("tan(x^2 + x + 2)", ApproachFrom.Left)]
[InlineData("tan(x^2 - x - 1)", ApproachFrom.Left)]
[InlineData("tan(x^2 + x - 1)", ApproachFrom.BothSides)]
public void IsTheTangentsValue(string input, ApproachFrom side)
{
var limit = MathS.FromString(input).Limit("x", 0, side);
var expected = MathS.FromString(input).Substitute("x", 0);
Assert.False(limit is Limitf, $"lim {side} x->0 {input} was left unevaluated");
var difference = (limit - expected).EvalNumerical();
Assert.True(difference is Number.Complex value && Number.Complex.Abs(value) is Number.Real abs && abs < 1e-10,
$"lim {side} x->0 {input} = {limit}, which is not {expected}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,15 @@ public sealed class AnUndecidedLimitIsNotInfiniteTest
[InlineData("tan(x^2 + x - 1)", ApproachFrom.Left)]
[InlineData("sin(x^2 + x - 1) / cos(x^2 + x - 1)", ApproachFrom.Left)]
[InlineData("tan(x^2 + x - 1)", ApproachFrom.Right)]
[InlineData("tan(x^2 - x - 1)", ApproachFrom.Left)]
[InlineData("tan(x^2 + x - 1)", ApproachFrom.BothSides)]
public void IsTheTangent(string input, ApproachFrom side)
{
var limit = MathS.FromString(input).Limit("x", 0, side);
Assert.False(limit is Limitf, $"lim {side} x->0 {input} was left unevaluated");
AssertIsTheTangent(input, side, limit);
}

// These come back unevaluated: the parts' own limits from the left are not taken, and that is a decline, not a
// wrong answer. What they must not be is the quotient of the derivatives, as the first was before.
[Theory]
[InlineData("tan(x^2 - x - 1)", ApproachFrom.Left)]
[InlineData("tan(x^2 + x - 1)", ApproachFrom.BothSides)]
public void IsTheTangentOrUnevaluated(string input, ApproachFrom side)
{
var limit = MathS.FromString(input).Limit("x", 0, side);
if (limit is not Limitf)
AssertIsTheTangent(input, side, limit);
}

// An infinity has one side, and asking for it one-sidedly was left unevaluated where the limit is a value. The
// Stirling limits relied on reading the first of these, unevaluated, as infinite: it is -oo, by luck. The last
// two are a power whose base tends to a number and whose exponent tends to 0.
Expand Down
Loading