Skip to content

Differentiate what does not mention the variable to 0, whatever its node - #2014

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
differentiate-what-does-not-mention-the-variable-to-zero
Oct 21, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
differentiate-what-does-not-mention-the-variable-to-zero

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Part of #233.

Every rule differentiated its parts by calling InnerDifferentiate directly, and only the base rule knew that a node without the variable differentiates to 0. So a power, a function, a logarithm or a quotient with no x in it went through the chain rule, and InnerSimplified folded 0 * f' into 0 provided <where f' is defined>. The derivative was NaN where the expression has a value:

.Differentiate("x") of at master and 2.5.0 this
(a - a)^(1/2) — 0 provided not a - a = 0, NaN 0
sqrt(a) * x a = 0 NaN sqrt(a)
arcsin(a) * x a = 1 NaN arcsin(a)
ln(c - c), 1/(a - a) — NaN 0

The new DerivativeOver(Variable) answers 0 for a node without the variable. Nodes containing a set, a matrix, a provided, a piecewise or a written NaN are excluded and keep their own rules: {1, 2} still gives {0}, and a piecewise keeps its conditions. All 47 child calls now go through it. The protected InnerDifferentiate overrides keep their names, so the public API is unchanged. Docs/Contributing/AddingNode.cs now tells a new node to call DerivativeOver for its parts. Binder variables (#984, #993) are unaffected.

The integrator relied on the spurious conditions in four places, which are now explicit:

  • SolveByEulerSubstitution's perfect-square test asked a value and missed symbolic squares such as (2 a b)^2 - 4 b^2 a^2. It now asks IsAPerfectSquareDiscriminant.
  • A symbolic root in x(t) under the same substitution was in effect never taken, because dx/dt carried provided not a = 0. It is now declined explicitly; taken, acos(a + b x)/x^4 ran past the budget.
  • Three routes the conditions cut short now decline early by value instead:
    • SolveByPartialFractions' logarithmic-derivative test (MayBeAConstantMultiple);
    • a candidate even in x in SolveBySubstitution, which repeats at −x;
    • a substitution whose quotient keeps x through every rewriting (KeepsTheVariableThroughEveryRewriting).
      Without them, 6.7.1:897 went from 4.1 GB to 20.8 GB; with them it is 0.38 GB.

Answers that change:

  • 1/(x sqrt(k^2 x^2 + 2 k x + 1)): no value anywhere on master, declined by 2.5.0; now answered by the perfect-square rule, 832 characters.
  • 1/(x (a^2 + 2 a b x^3 + b^2 x^6)^(5/2)) (Rubi's 1.2.3.2:131): answered on master in 1,039 characters with no value at any sample point; now declined, as 2.5.0 declined it.

Chain against master a567c8b, with the harness at its current revision on both arms:

  • pocket: every 15th row of every family, every 8th of 1.2.x, every 3rd of 6.7.1 and 1.1.2.4, 4,231 rows in all;
  • allocation over the 4,194 rows solved on both: 825.5 → 746.3 GB;
  • 1.1.2.4:566 and 4.3.2.1:690 go from timeout to 1.6 s and 0.65 s, alone;
  • 6.3.2:218 is answered by Integrate itself where master needed Simplify afterwards;
  • samples of families 0–8 unchanged, and no row's allocation grew past 1.3×.
row, alone master this
6.7.1:897 4.13 GB 0.38 GB
6.7.1:891 6.68 GB 0.31 GB
1.2.2.5:70 2.277 GB 2.262 GB
1.1.2.4:548 1.555 GB 0.183 GB

All four answers are identical to master's.

Tests:

  • 31 new cases in DerivativeTest.
  • The three InnerSimplifyTest.PiecewiseDerivative2* tests now expect piecewise(0 provided True), which simplifies to 0. Their old not x^2 = 0 was the quotient rule's denominator for a 1/x that does not vary in y, and their own comment said it "does not collapse all the way to 0".
  • A local run of the Calculus, derivative, limit, series and piecewise tests (6,010) passed except three at load 15:
    • InverseTangentPowerByPartsTest.ANonElementaryRemainderIsDeclinedQuickly;
    • two SlowDeclinesAheadOfTheAnsweringRuleIntegralTest cases.
      All three pass alone. Those two rows allocate the same on master (7.65 and 3.93 GB) and this branch (7.65 and 3.90 GB), with identical answers.

The BREAKING-CHANGES.md entry has 2.5.0's column measured. It also rewords one sentence of the acsch(c x) entry, which said a node with a rule of its own keeps it, which is no longer true.

🤖 Generated with Claude Code

https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

Every rule differentiated its parts by calling InnerDifferentiate directly,
and only the base rule knew that an x-free node differentiates to 0. So a
power, a function, a logarithm or a quotient with no x in it went through
the chain rule, and InnerSimplified folded 0 * f' into `0 provided` where
f' is defined: d/dx (a - a)^(1/2) and d/dx sqrt(a) x at a = 0 were NaN, and
so were arcsin(a) x at a = 1, ln(c - c) and 1/(a - a). DerivativeOver now
answers 0 for a node without the variable and without a set, a matrix, a
provided, a piecewise or a written NaN, which keep their own rules, and all
47 child calls go through it.

The integrator relied on the spurious conditions in four places, which are
now explicit. SolveByEulerSubstitution's perfect-square test asked a value
and missed symbolic squares such as (2 a b)^2 - 4 b^2 a^2, and a root of a
symbol in x(t) was in effect never taken; the test is now
IsAPerfectSquareDiscriminant, and such a root is declined. Three routes that
the conditions cut short now decline early by value: SolveByPartialFractions'
logarithmic-derivative test (MayBeAConstantMultiple), a candidate even in x
in SolveBySubstitution (it repeats at -x), and a substitution whose quotient
keeps x through every rewriting (KeepsTheVariableThroughEveryRewriting).

1/(x sqrt(k^2 x^2 + 2 k x + 1)) had no value anywhere on master and is
answered; 1/(x (a^2 + 2 a b x^3 + b^2 x^6)^(5/2)), answered with no value
anywhere on master, is declined as 2.5.0 declined it. Chain against master
a567c8b, a 4,231-row stride pocket over every family: allocation over the
rows solved on both 825.5 -> 746.3 GB; 1.1.2.4:566 and 4.3.2.1:690 timeout
-> 1.6 s and 0.65 s alone; 6.3.2:218 answered by Integrate itself; samples
unchanged; no row's allocation grew past 1.3x. 6.7.1:897 0.38 GB, from 4.13.

Part of #233.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
@Rafael-SOWNet Rafael-SOWNet added this to the 2.6.0 milestone Oct 21, 2026
@Rafael-SOWNet
Rafael-SOWNet merged commit f705d99 into master Oct 21, 2026
34 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in AngouriMath Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant