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
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,21 @@ for `u` made a matrix of vectors. A lambda written in place is now applied befor
| `apply(lambda(u, [[0, -1], [1, 0]] * u), [p, q])` | `[[0, [-p, -q]], [[p, q], 0]]` | `[-q, p]` |
| `apply(lambda(u, [1, 1]T * u), [p, q])` | `[[[p, q], [p, q]]]` | `p + q` |

### l'Hopital's rule is not applied across the jumps of a step function

**Silent: a limit of a quotient with a `floor`, `ceil`, `round` or `sgn` that jumps in every neighbourhood of the
destination is left unevaluated, where l'Hopital's rule answered it wrongly.** The rule needs both parts differentiable
near the destination, and the derivative of `floor(x)` is `0` only between the integers, so `floor(x) / x` at `+oo` read
`0 / 1`. Where a step's argument tends to a finite value the step is constant on either side, and the rule is applied as
before ([#2281](https://github.com/asc-community/AngouriMath/issues/2281)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"limit(floor(x) / x, x, +oo)".ToEntity().Simplify()` | `0` | `limit(floor(x) / x, x, +oo)`; the limit is 1 |
| `"limit(ln(floor(x)) / ln(x), x, +oo)".ToEntity().Simplify()` | `0` | unevaluated; the limit is 1 |
| `"limit(ln(x) / ln(floor(x)), x, +oo)".ToEntity().Simplify()` | `NaN` | unevaluated; the limit is 1 |
| `"limit((x - floor(x)) / x, x, +oo)".ToEntity().Simplify()` | `1` | unevaluated; the limit is 0 |

### One and a squared tangent are found anywhere in a sum

**Silent: an identity reaches its value whichever way the sum is sorted.** `1 + tan(x)^2 = sec(x)^2` and
Expand Down
3 changes: 3 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[Tests/UnitTests/Calculus/LogarithmOfARationalFunctionByPartsTest.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/LHopitalAcrossAJumpTest.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

[AngouriMath/Functions/Algebra/Polynomials/FractionFreeDeterminant.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 @@ -259,6 +259,12 @@ private static bool IsRealValued(Entity expr, Variable x)
&& (lowerLogLimit.Nodes.Any(child => child == Real.PositiveInfinity || child == Real.NegativeInfinity)
|| lowerLogLimit == Integer.Zero))
{
// Nor through a step that jumps all the way in, for the reason l'Hopital's rule
// itself declines it: ln(floor(x)) / ln(x) answered 0.
// https://github.com/asc-community/AngouriMath/issues/2281
if (LimitFunctional.HasAStepThatJumpsNear(upperLogArgument, x, Real.PositiveInfinity, ApproachFrom.BothSides)
|| LimitFunctional.HasAStepThatJumpsNear(lowerLogArgument, x, Real.PositiveInfinity, ApproachFrom.BothSides))
return null;
var upperDerivative = upperLogArgument.Differentiate(x).InnerSimplified;
var lowerDerivative = lowerLogArgument.Differentiate(x).InnerSimplified;
// A derivative the library cannot compute comes back *unevaluated* rather than
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,34 @@ private static bool IsIndeterminateQuotient(Entity numerator, Entity denominator
finally { lHopitalDepth--; }
}

/// <summary>
/// Whether a step function in <paramref name="expr"/> jumps in every neighbourhood of the
/// destination: a floor, a ceil or a round whose argument does not tend to a finite value,
/// or a sign whose argument has no limit, and so may cross zero all the way in.
/// </summary>
/// <remarks>
/// l'Hopital's rule needs both parts differentiable on a punctured neighbourhood of the
/// destination. The derivative of floor(x) is 0 provided not x in ZZ, and every
/// neighbourhood of +oo holds integers, so floor(x) / x at +oo read 0 / 1 and answered 0,
/// where it is 1. Where the argument tends to a finite value the step is constant on
/// either side of the destination, and the rule holds there.
/// <a href="https://github.com/asc-community/AngouriMath/issues/2281">#2281</a>
/// </remarks>
internal static bool HasAStepThatJumpsNear(Entity expr, Variable x, Entity dest, ApproachFrom side)
{
return expr.Nodes.Any(node => node switch
{
Floorf(var argument) => DoesNotSettle(argument),
Ceilf(var argument) => DoesNotSettle(argument),
Roundf(var argument) => DoesNotSettle(argument),
Signumf(var argument) => argument.ContainsNode(x)
&& EvalAssumingContinuous(argument.Limit(x, dest, side)) is not Real { IsNaN: false },
_ => false
});
bool DoesNotSettle(Entity argument) => argument.ContainsNode(x)
&& EvalAssumingContinuous(argument.Limit(x, dest, side)) is not Real { IsFinite: true };
}

private static Entity? ApplylHopitalRuleImpl(Entity expr, Variable x, Entity dest, ApproachFrom side)
{
if (expr is not Divf && AsQuotient(expr, x, dest, side) is { } quotient)
Expand All @@ -1379,7 +1407,8 @@ private static bool IsIndeterminateQuotient(Entity numerator, Entity denominator
if (numLimit == 0 && denLimit == 0 ||
IsInfiniteNode(numLimit) && IsInfiniteNode(denLimit))
if (num is not Number && den is not Number)
if (num.ContainsNode(x) && den.ContainsNode(x))
if (num.ContainsNode(x) && den.ContainsNode(x)
&& !HasAStepThatJumpsNear(expr, x, dest, side))
{
// Simplified, because the shape of the quotient is what the
// machinery below matches on and differentiation does not
Expand Down
49 changes: 49 additions & 0 deletions Sources/Tests/UnitTests/Calculus/LHopitalAcrossAJumpTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// 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>
/// l'Hopital's rule is not applied across the jumps of a step function. It needs both parts differentiable on a punctured
/// neighbourhood of the destination, the derivative of floor(x) is 0 provided not x in ZZ, and every neighbourhood of +oo
/// holds integers: floor(x) / x at +oo read 0 / 1 and answered 0, where it is 1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/2281">#2281</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class LHopitalAcrossAJumpTest
{
[Theory]
[InlineData("limit(floor(x) / x, x, +oo)", "1")]
[InlineData("limit(ceil(x) / x, x, +oo)", "1")]
[InlineData("limit(floor(2x) / x, x, +oo)", "2")]
[InlineData("limit(floor(x)^2 / x^2, x, +oo)", "1")]
[InlineData("limit(ln(floor(x)) / ln(x), x, +oo)", "1")]
[InlineData("limit(ln(x) / ln(floor(x)), x, +oo)", "1")]
[InlineData("limit((x - floor(x)) / x, x, +oo)", "0")]
[InlineData("limitright(x * floor(1/x), x, 0)", "1")]
public void ItIsTheLimitOrNothing(string limit, string value)
{
var answer = limit.ToEntity().Simplify();
Assert.True(answer.Nodes.Any(node => node is Entity.Limitf) || answer.Evaled == value.ToEntity().Evaled,
$"{limit} is {answer}, where it is {value}");
}

/// <summary>Where a step's argument settles, the step is constant on either side, and the rule holds.</summary>
[Theory]
[InlineData("limit(floor(x + 1/2) * sin(x) / x, x, 0)", "0")]
[InlineData("limit((ceil(x + 1/2) + 1) * sin(x) / x, x, 0)", "2")]
[InlineData("limit(sgn(x + 1) * (1 - cos(x)) / x^2, x, 0)", "1/2")]
[InlineData("limit(sin(x) / x, x, 0)", "1")]
[InlineData("limit(ln(x) / x, x, +oo)", "0")]
public void WhereTheStepSettlesItIsAnswered(string limit, string value)
=> Assert.Equal(value.ToEntity().Evaled, limit.ToEntity().Simplify().Evaled);
}
}
Loading