From 22ccf10fe5dccec37d6b816ab69c7165bad1e0e1 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 27 Aug 2026 13:38:37 +0000 Subject: [PATCH] A limit binds the variable it approaches along (#989) `FreeVariables` learned about a summation, a product and a definite integral in #1045, and about `limit` not at all. That commit enumerates the indefinite integral and the derivative as deliberate exclusions and does not mention a limit, so this was an omission rather than a decision. And the reason those two are excluded is what makes this one bind. An antiderivative of `t * b` over `t` is `b * t ^ 2 / 2 + C` and `d/dt` denotes a function of `t` -- both are still functions of the variable. **A limit never is.** `lim(t, t, 0)` is 0, and no limit's value depends on the name it approaches along, so the variable is consumed exactly as a summation index is. The destination is where the dependence goes and is bound over as well, so `lim(t, t, b)` is a function of `b` alone. `Limitf` is a `CalculusOperator(Expression, Var)`, the same shape as `Integralf`, so this is one arm next to it -- unconditional where the integral's is conditional, which is the distinction the comment now records. Measured on a build of each side: `limit(t * b, t, 0).FreeVariables` was `{ t, b }` and is `{ b }`. `Vars` and `VarsAndConsts` untouched. --- BREAKING-CHANGES.md | 24 +++++++++++++++++++ .../Core/Entity/Entity.Definition.cs | 9 +++++++ .../UnitTests/Common/FreeVariablesTest.cs | 20 ++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 49778b5d8..64ca5b48f 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -21,6 +21,7 @@ read first. | Silent? | What | Was | Is | |---|---|---|---| +| **Silent** | `"limit(t * b, t, 0)".ToEntity().FreeVariables`, and every limit | `{ t, b }` — the variable it approaches along counted as free | `{ b }` | | | `Entity.DomainConditionIn(Domain)` | did not exist | the domain of definition for a **stated** reading, through the whole tree | | | `MathS.Polynomials.Factor("(y * x3 + 1) * (x4 - y3)", "x")`, and bivariate polynomials whose leading coefficient in the main variable is a polynomial | `null` — a refusal | `(x ^ 4 - y ^ 3) * (x ^ 3 * y + 1)` | | | `MathS.Polynomials.Factor("x7 - y7", "x")`, and bivariate polynomials whose substituted image over-factors | `null` — a refusal | `(x - y) * (x ^ 6 + x ^ 5 * y + … + y ^ 6)` | @@ -316,6 +317,29 @@ bracketing for. The change only ever adds `\left(`/`\right)` groups, which CShar parses, so nothing downstream needs a matching change ([#822](https://github.com/asc-community/AngouriMath/issues/822)). +### A limit binds the variable it approaches along + +`FreeVariables` learned about a summation, a product and a definite integral in +[#1045](https://github.com/asc-community/AngouriMath/pull/1045), and about `limit` not at all — +that commit names the indefinite integral and the derivative as deliberate exclusions and does +not mention it. + +| | before | now | +|---|---|---| +| `"limit(t * b, t, 0)".ToEntity().FreeVariables` | `{ t, b }` | `{ b }` | +| `"limit(t, t, b)".ToEntity().FreeVariables` | `{ t, b }` | `{ b }` | +| `"limitleft(t * b, t, 0)".ToEntity().FreeVariables` | `{ t, b }` | `{ b }` | +| `"limit(t, t, 0)".ToEntity().FreeVariables` | `{ t }` | `{ }` | + +The reason the indefinite integral and the derivative do not bind is exactly what makes a limit +bind: an antiderivative of `t * b` over `t` is `b * t ^ 2 / 2 + C` and `d/dt` denotes a function +of `t`, both still functions of the variable — while **a limit never is**. `lim(t, t, 0)` is `0`, +and no limit's value depends on the name it approaches along. The destination is where the +dependence goes, so it is bound over too: `lim(t, t, b)` is a function of `b` alone. + +`Vars` and `VarsAndConsts` are untouched, as they were in #1045 — they mean every name occurring +([#989](https://github.com/asc-community/AngouriMath/issues/989)). + ### A leading coefficient that is a polynomial no longer stops the lift The Hensel lift below declined when the leading coefficient in the main variable was not a diff --git a/Sources/AngouriMath/Core/Entity/Entity.Definition.cs b/Sources/AngouriMath/Core/Entity/Entity.Definition.cs index 2eb8037af..d1a68a575 100644 --- a/Sources/AngouriMath/Core/Entity/Entity.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Entity.Definition.cs @@ -632,6 +632,15 @@ public IReadOnlyList Vars // on purpose, and a sweep that "fixes" that makes them wrong. Integralf { Range: { } limits } integral => BoundBy(integral.Var, integral.Expression, limits.from, limits.to), + // A limit binds its variable always, and this is the one calculus + // operator of the three for which that is unconditional. The reason the + // two above are conditional does not apply to it: an antiderivative and a + // derivative are still functions of the variable, and a limit never is. + // lim(k, k, 0) is 0, and no limit's value depends on the name it + // approaches along -- the destination is where the dependence goes. + // https://github.com/asc-community/AngouriMath/issues/989 + Limitf limit + => BoundBy(limit.Var, limit.Expression, limit.Destination), _ => new HashSet(@this.DirectChildren.SelectMany(c => c.FreeVariables)) } , diff --git a/Sources/Tests/UnitTests/Common/FreeVariablesTest.cs b/Sources/Tests/UnitTests/Common/FreeVariablesTest.cs index 2c64f42bd..0377dc2ca 100644 --- a/Sources/Tests/UnitTests/Common/FreeVariablesTest.cs +++ b/Sources/Tests/UnitTests/Common/FreeVariablesTest.cs @@ -145,6 +145,26 @@ public void TheIndexIsBoundOverTheBoundsToo() public void ADefiniteIntegralBindsItsVariable(string exprRaw) => Assert.Equal(SeqVar("b"), exprRaw.ToEntity().FreeVariables); + /// + /// A limit binds its variable always, where the integral above binds only when it + /// has limits to bind between — and the reason the two below do not bind is what makes + /// the difference. An antiderivative and a derivative are still functions of the + /// variable; a limit never is. lim(t, t, 0) is 0. + /// + /// + /// The destination is where a limit's dependence goes, so it is bound over as well: + /// lim(t, t, b) is a function of b and of nothing else. A one-sided limit + /// binds the same way — the side is not a variable. + /// #989 + /// + [Theory] + [InlineData("limit(t * b, t, 0)")] + [InlineData("limit(t, t, b)")] + [InlineData("limitleft(t * b, t, 0)")] + [InlineData("limitright(t * b, t, 0)")] + public void ALimitBindsItsVariable(string exprRaw) + => Assert.Equal(SeqVar("b"), exprRaw.ToEntity().FreeVariables); + /// /// And the two that look like the same shape and are not. The antiderivative of /// t * b over t is b * t ^ 2 / 2 + C, still a function of t;