Do not differentiate over a name that cannot vary (#993) - #1004
Merged
Conversation
Entity.Differentiate(Variable) takes a Variable, and MathS.pi and MathS.e are ones, so they could be handed to it and it differentiated as though they varied. "sin(pi)".Differentiate(MathS.pi) was: -1 now: 0 sin(pi) is 0, and its derivative with respect to anything is 0. -1 is cos(pi): the chain rule run over a symbol that cannot change. The test is whether the name evaluates to a number, not whether it is spelled like a constant. A name a binder declares can vary even when it is spelled pi, and it evaluates to itself -- which is what keeps this compatible with #984, whose answer to derivative(pi ^ 2, pi) is over the variable the binder holds. This call has no binder in it, and the constant arrives directly in the position that says what varies. The guard is on the two public overloads rather than deeper, so that x! -- whose derivative the library cannot take at all -- is answered too: the variable settles it whatever the expression is. Variable.InnerDifferentiate carries it as well, for the internal callers that reach the chain rule without going through Differentiate. Integrate and Limit over a constant are not changed. They have no value to give, so leaving them unevaluated is the existing "I could not settle this", not a refusal of something settled. The derivative is settled. Suite 7383 passed, 0 failed. Corpus unchanged at 116/119 with 0 wrong. crashcheck 1834 cases, 0 crashed. Composed with #990, #991 and #1003 on a local integration branch: 7484 passed, 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKcbJP266A3EGyQ5kq7Ru
…ver-constant-993 # Conflicts: # BREAKING-CHANGES.md # Sources/AngouriMath/Functions/Continuous/Differentiation.cs # Sources/Tests/UnitTests/Calculus/DerivativeTest.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #993, taking the
0option rather than the refusal I argued for on the issue — see Why not refuse.The defect
Entity.Differentiate(Variable)takes aVariable, andMathS.piandMathS.eare ones, so they could be handed to it and it differentiated as though they varied.sin(pi)is0, and its derivative with respect to anything is0.-1iscos(pi).Why not refuse
I wrote on the issue that refusing was the option that "survives the family", because
∫ f dchas no zero-shaped answer and answering one with a value while refusing the other would be incoherent. That was wrong, and the two cases are not the same:d(sin(pi))/d(pi)is settled — decidepiis constant and the expression does not vary, so the answer is0. Refusing it would be declining something the library can answer.∫ f dpihas nothing to solve. Leaving it unevaluated is the existing "I could not settle this" doing its job, not a refusal.So
IntegrateandLimitare untouched here, and there is no exception, no new API and no signature change — which is also what makes this a one-guard PR rather than a change across four public methods and the native ABI.x!is the case worth calling out: the library cannot take that derivative at all, and it is still0here, because the variable settles it whatever the expression is. That is why the guard sits on the public overloads rather than deeper — a node that builds an unresolvedDerivativefnever reaches the chain rule's base case.The test is the value, not the spelling
variable.Evaled is Number, not "is it namedpi". A name a binder declares can vary even when it is spelledpi, and it evaluates to itself. That is what keeps this compatible with #984/#991, and I measured it on both branches before writing the guard.Scope: the node form belongs to #984
On this branch
"derivative(x * pi, pi)".InnerSimplifiedalso goes fromxto0, because the node asks the same public method. That is not this PR's answer to keep. With #991 in, the parser bindspithere and it becomes2 * pi_1— a derivative over the variable the binder holds — which is #984's intended behaviour and supersedes it.I found this by building the integration branch, not by reasoning: my first version guarded
Derivativef.InnerSimplifydirectly and five tests failed on the composition, because it was answering a question #991 had already re-scoped. #993's own text says as much — "this call has no binder in it" — and I had over-reached past it. The guard is now on the direct API only, where the constant genuinely arrives in the position that says what varies.Measured
work/crashcheck: 1834 cases, 0 crashed.BREAKING-CHANGES.mdentry with both values measured on a build of each arm.Against the other open PRs
constant-node-984BREAKING-CHANGES.md+Differentiation.cs(the same one line — both guards are wanted)fix/differentiate-power-simplifies-1002BREAKING-CHANGES.md+DerivativeTest.cs(both additive)BREAKING-CHANGES.mdonlyResolved all of them on a local integration branch of #993 + #990 + #991 + #1003 and ran it: 7484 passed, 0 failed, with each PR's own behaviour intact —
🤖 Generated with Claude Code
https://claude.ai/code/session_01KbKcbJP266A3EGyQ5kq7Ru