There is a package called NumericalCalculus
which has a function ND
, which takes a numerical derivative. This is a bit odd (to me), in that it takes one-sided forward derivatives, but I'll use it here. This has two options, Terms
and Scale
, which control the order and the distance between sampling points
(play around with the parameters in ND[f[x], x, 1, Terms -> 3,Scale->1]
to figure out how this works).
However, for some reason this doesn't seem to have a nice generalisation to partial derivatives. This is a start of such an implementation.
First we load the package:
Needs["NumericalCalculus`"]
Then we define some functions for second differentiation with respect to the same variable and two different variables:
ndd[f_, {a_, a0_}, {a_, a0_}, {x0_, y0_, z0_}, terms_: 5, scale_: 0.1] :=
ND[f[x, y, z], {a, 2}, a0, Terms -> terms, Scale -> scale] /. {x -> x0, y -> y0, z -> z0}
ndd[f_, {a_, a0_}, {b_, b0_}, {x0_, y0_, z0_}, terms_: 5, scale_: 0.1] /; (!SameQ[a, b]) :=
ND[ND[f[x, y, z], a, a0, Terms -> terms, Scale -> scale], b, b0,
Terms -> terms, Scale -> scale] /. {x -> x0, y -> y0, z -> z0}
These are used in the following function that then takes the second derivative with respect to each variable at a point {x0,y0,z0}
.
calculatePartialDerivatives[ff_, {x0_, y0_, z0_}, terms_: 5, scale_: 0.1] :=
Table[ndd[ff, d1, d2, {x0, y0, z0}, terms, scale],
{d1, {{x, x0}, {y, y0}, {z, z0}}}, {d2, {{x, x0}, {y, y0}, {z, z0}}}]
We can test this on a function defined only on numerical input,
testfn[x_?NumericQ, y_?NumericQ, z_?NumericQ] := Sin[x y] + Cos[x z]
calculatePartialDerivatives[testfn, {1, 0.5, 0.2}] // Chop
{{-0.159059, 0.63787, -0.394683}, {0.63787, -0.479426, 0}, {-0.394683, 0, -0.980067}}
which is the same as the explicit result (to 10dp with these settings of terms and scale):
Table[D[Sin[x y] + Cos[x z], d1, d2], {d1, {x,y,z}}, {d2, {x,y,z}}]/. {x->1, y->0.5, z->0.2}
{{-0.159059, 0.63787, -0.394683}, {0.63787, -0.479426, 0}, {-0.394683, 0, -0.980067}}
It is important to either cache the function evaluations, or Simplify
the output from calculatePartialDerivatives
before plugging the function in. Doing so massively reduces the number of evaluations required:
Clear[testfn, testfn2, testfn3]
nEvals = 0; nEvals2 = 0; nEvals3 = 0; nTerms = 6;
testfn[x_?NumericQ, y_?NumericQ, z_?NumericQ] := (nEvals++; Sin[x y] + Cos[x z])
testfn2[x_?NumericQ, y_?NumericQ, z_?NumericQ] := (nEvals2++; Sin[x y] + Cos[x z])
testfn3[x_?NumericQ, y_?NumericQ, z_?NumericQ] := testfn3[x, y, z] = (nEvals3++; Sin[x y] + Cos[x z])
calculatePartialDerivatives[testfn, {1, 0.5, 0.2}, nTerms, 0.1] // Chop;
Simplify[calculatePartialDerivatives[q, {1, 0.5, 0.2}, nTerms, 0.1]] /. q -> testfn2 // Chop;
calculatePartialDerivatives[testfn3, {1, 0.5, 0.2}, nTerms, 0.1];
These give the same answer (precision dependent), but evaluate their numerical function very different numbers of times:
{nEvals, nEvals2, nEvals3}
(* {24864, 318, 143} *)
Adding the Simplify
cancels out a lot of the repeated terms in the expansions, while caching the result (testfn3[x_?NumericQ, y_?NumericQ, z_?NumericQ] := testfn3[x, y, z] = ...
) gets the same the same effect and is even better as some terms are repeated between the different partial derivatives. It is also marginally faster (as the Simplify
takes some time to try and simplify the expression).
rr
andM
are undefined. Can you ask your question as a minimal working example that gets to the question, which is how to take a numerical derivative. $\endgroup$M
can be set to one for convenience andrr[[2]]
represents the best fit parameters. But since I am just interested in how to compute the derivative of the chi square involving numerically defined variables, it doesn't matter what these best fit values are so can also just be set to arbitrary values. $\endgroup$a,b,NN
. Is it clear? Or should I rephrase? $\endgroup$