You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Differentiating a factorial or gamma answers NaN, which claims the derivative does not exist #958
Entity.Differentiate answers NaN for the factorial and the gamma function, which claims their derivatives do not exist. They do — both are smooth away from the poles at non-positive integers.
Measured on master at a45a7256, .NET 10, default settings:
expression
InnerSimplified
Evaled
derivative(sin(x), x)
cos(x)
cos(x)
derivative(abs(x), x)
sgn(x) provided not x = 0
same
derivative(sgn(x), x)
0 provided not x = 0
same
derivative(x!, x)
NaN
NaN
derivative(gamma(x), x)
NaN
NaN
The first three are the library working correctly, including the two that need a side condition. The last two are the defect.
Why it is a wrong answer rather than a missing feature
x! is not undefined and not non-differentiable — the library evaluates it at non-integers through the gamma function:
"(1/2)!".EvalNumerical()// 0.886226925452758...
so it is a smooth function of a real argument, and d/dx x! exists everywhere except at the poles. At x = 3 it is Γ'(4) = 6(H₃ − γ) ≈ 3.966, a perfectly ordinary number.
Not answering is a legitimate answer; answering wrongly is not. Unevaluated means "I could not settle this", NaN means "this does not exist", and confusing them ships a wrong answer.
This is that confusion. The honest answer is the unevaluatedderivative(x!, x), which says "I cannot settle this" and leaves the caller free to handle it. NaN says something false, and it propagates: anything multiplying or adding this becomes NaN too, so a single factorial in a larger expression can silently destroy an otherwise fine derivative.
Scope
This is not#171 ("Inverse and derivative for Factorial and Gamma"), which is the feature request to actually compute these and is deliberately marked Not now. This issue is only that the refusal is spelled wrongly. Fixing it does not require implementing digamma — it requires the derivative rule to decline rather than to assert non-existence, and it would remain correct once #171 is eventually done.
I have not checked how many other nodes return NaN from InnerDifferentiate for the same reason; that sweep is the obvious next step and I would expect this not to be the only one.
Swept the whole node space, and I was wrong about the scope — in the good direction. I wrote above that I would expect this not to be the only one. It is.
Enumerated every concrete Entity node type by reflection (65 of them), built each with a variable argument and differentiated it, on master at a45a7256:
differentiated fine: 46 answered NaN: 1 not constructible this way: 18
The one is Factorialf. And gamma(x) parses toFactorialf, so the two rows in the table above are one node type rather than two — the whole defect is a single rule.
The 18 my constructor probe could not build are numbers (Integer, Rational, Real, Complex, Boolean), sets (Integers, Reals, FiniteSet, Interval, …), containers (Matrix, Piecewise) and the three calculus nodes, which take extra arguments. I checked those by hand rather than leave the gap:
derivative(derivative(y, y), x)
0
derivative(integral(y, y), x)
0
derivative(limit(y, y, 0), x)
0
derivative(piecewise(x provided x > 0, 0), x)
piecewise(1 provided x > 0, 0 provided True)
derivative([1, 2], x)
[0, 0]
derivative(apply(f, x), x)
derivative(apply(f, x), x) — unevaluated
derivative(x in RR, x)
derivative(x in RR, x) — unevaluated
So every other node either differentiates correctly, or refuses by staying unevaluated.
That last point is the useful one for fixing this.apply(f, x) is the closest analogue — a function whose derivative the library genuinely cannot compute — and it already does the right thing: it hands back the unevaluated derivative(...) rather than asserting non-existence. Factorialf is the single node that does not follow that pattern, so the fix is to make it behave like its neighbour, and there is no sweep of other nodes owed afterwards.
Activity
Rafael-SOWNet commentedon Aug 16, 2026
Swept the whole node space, and I was wrong about the scope — in the good direction. I wrote above that I would expect this not to be the only one. It is.
Enumerated every concrete
Entitynode type by reflection (65 of them), built each with a variable argument and differentiated it, onmasterata45a7256:The one is
Factorialf. Andgamma(x)parses toFactorialf, so the two rows in the table above are one node type rather than two — the whole defect is a single rule.The 18 my constructor probe could not build are numbers (
Integer,Rational,Real,Complex,Boolean), sets (Integers,Reals,FiniteSet,Interval, …), containers (Matrix,Piecewise) and the three calculus nodes, which take extra arguments. I checked those by hand rather than leave the gap:derivative(derivative(y, y), x)0derivative(integral(y, y), x)0derivative(limit(y, y, 0), x)0derivative(piecewise(x provided x > 0, 0), x)piecewise(1 provided x > 0, 0 provided True)derivative([1, 2], x)[0, 0]derivative(apply(f, x), x)derivative(apply(f, x), x)— unevaluatedderivative(x in RR, x)derivative(x in RR, x)— unevaluatedSo every other node either differentiates correctly, or refuses by staying unevaluated.
That last point is the useful one for fixing this.
apply(f, x)is the closest analogue — a function whose derivative the library genuinely cannot compute — and it already does the right thing: it hands back the unevaluatedderivative(...)rather than asserting non-existence.Factorialfis the single node that does not follow that pattern, so the fix is to make it behave like its neighbour, and there is no sweep of other nodes owed afterwards.Decline a factorial's derivative instead of claiming it does not exist (
Decline a factorial's derivative instead of claiming it does not exist (
Add sum and product, the first operators that bind a variable over a …
The polygamma functions, and the derivative of the factorial (#2068)