Is it possible to define a function that can operate on another function with arbitrarily many arguments? For example, I want to do error propagation using partial derivatives. It is not clear to me whether this is possible, since the function for which I'm doing the error propagation can have any number of arguments.
-
1$\begingroup$ How do you want to specify the function? Should it always add the arguments, or something else? It would be very helpful if you could show a few examples of input and output how you imagine it $\endgroup$– Lukas LangAug 21, 2019 at 19:01
-
$\begingroup$ That's the point. It should be any kind of function you could think of.For example $g[x,y,z]=x*y*z$, or $g[x,y]=sin(x)+y^2$ $\endgroup$– TanEmaAug 21, 2019 at 19:04
-
1$\begingroup$ @TanEma but then what should be done? What exactly do you want to automate? (because you can just type the definition yourself, right?) $\endgroup$– Lukas LangAug 21, 2019 at 19:10
-
$\begingroup$ I want to make a programm that calculates the error using partial derivatives (you insert a function, you insert values for the variables, you insert the absolute error for every variable and you calculate the error). But I don't want the programm to be for some specific function, but rather for any $\endgroup$– TanEmaAug 21, 2019 at 19:13
-
1$\begingroup$ @TanEma I've rewritten your question to more clearly ask what I think you wanted to ask (judging from your comment). Please feel free to revert the edit if I misunderstood you $\endgroup$– Lukas LangAug 21, 2019 at 20:44
1 Answer
I will present two approaches here to implement a function that does error propagation for an arbitrary function with arbitrarily many arguments. The first approach is more straightforward and easier to understand. The second one is more involved and requires some understanding of the different Hold
related concepts of Mathematica, but is more similar in terms of use to something like Sum
.
The math
In both cases, I'll be implementing the following formula:
$$\delta f|_{\substack{x_1=x_{1,0}\\x_2=x_{2,0}\\\dots}}=\sqrt{\sum_{i=0}^n\left(\left.\frac{\partial f}{\partial x_i}\right|_{\substack{x_1=x_{1,0}\\x_2=x_{2,0}\\\dots}}\delta x_i\right)^2}$$
Simple approach
computeUncertainty[f_, vars_] :=
Sqrt@Sum[ (*sum over all variables *)
(
Derivative[ (* generate the Derivative that we need *)
Sequence @@ UnitVector[Length@vars, i]
][
f
][(* apply it to the we select *)
Sequence @@ vars[[All, 1]]
]*vars[[i, 2]] (* multiply by the error *)
)^2,
{i, Length@vars} (* summation is over i from 1 (implicit) to n, the length of vars *)
]
Some things to note
The function is used like this:
computeUncertainty[f, {{a0, δa}, {b0, δb}, {c0, δc}}]
g[a_, b_, c_] := a + b + c computeUncertainty[g, {{a0, δa}, {b0, δb}, {c0, δc}}]
h[x_, y_, z_] := Sin[x] z + y^2 computeUncertainty[h, {{3, 0.1}, {1, 0.05}, {-2, 0.2}}] (* 0.223607 *)
We use
Sequence
to insert a list as individual arguments:f[Sequence @@ {a, b, c}] (* f[a, b, c] *)
We could use
Apply
(@@
) directly: (but I find it more readable in this case to useSequence
)f @@ {a, b, c} (* f[a, b, c] *)
Derivative
wants a list of numbers indicating how many times to differentiate with respect to each argument. We useUnitVector
to get the appropriate lists:Derivative[Sequence @@ UnitVector[3, 1]][f]
Advanced approach
Attributes[computeUncertainty2] = {HoldAll}; (* prevent evaluation of the arguments*)
computeUncertainty2[expr_, vars__] := Extract[(* get the variable names *)
Unevaluated@{vars} (* prevent evaluation of the variables*),
{All, 1}, (* all the names *)
Function[ (* anonymous function with HoldAll attribute *)
v,
Block[
v, (* temporarily remove any values from the variables *)
With[
{eExpr = expr, eVars = {vars}}, (* ensure that expr & vars are only evaluated once *)
Sqrt@Sum[ (* the sum *)
(
D[expr, v[[i]]] * (* differentiate the expression w.r.t the variables *)
{vars}[[i, 3]]
)^2,
{i, Length@v}
] /.
Rule @@@ {vars}[[All, ;; 2]] (* insert the values specified for the variables *)
]
],
HoldAll
]
]
SyntaxInformation[computeUncertainty2] = { (* set syntax highlighting information *)
"ArgumentsPattern" -> {__}, (* the function has at least one argument *)
"LocalVariables" -> {"Table", {2, \[Infinity]}} (* highlight the variables like in Sum *)
};
Notes:
The function is used like this:
computeUncertainty2[f[a, b, c], {a, a0, δa}, {b, b0, δb}, {c, c0, δc}]
computeUncertainty2[a + b + c, {a, a0, δa}, {b, b0, δb}, {c, c0, δc}]
computeUncertainty2[Sin[x] z + y^2, {x, 3, 0.1}, {y, 1, 0.05}, {z, -2, 0.2}] (* 0.223607 *)
This function operates on an actual expression instead of just the name of a function. This means we can use
D
instead ofDerivative
to do the differentiation.- Since we're using a
PatternSequence
(__
) forvars
, we need to wrap it in a list in most places, i.e.{vars}
- We use
Block
to protect the computation from insertion of values for the variable we want to differentiate w.r.t. - We use
With[{eExpr = expr, eVars = {vars}}, …]
to ensure that everything is evaluated only once. Otherwise, stuff likecomputeUncertainty2[Echo@f[a, b, c], {a, Echo@a0, Echo@δa}, {b, b0, δb}, {c, c0, δc}]
would trigger eachEcho
statement several times. We use
@@@
(Apply
at level 1) to build the replacement rules:Rule @@@ {{a, a0, δa}, {b, b0, δb}, {c, c0, δc}}[[All, ;; 2]] (* {a -> a0, b -> b0, c -> c0} *)
We use SyntaxInformation to reproduce the syntax highlighting of functions like
Sum
:All the
HoldAll
&Unevaluated
tricks are to ensure the function works even when the variables already have values:a = 3; b = 2; computeUncertainty2[f[a, b, c], {a, a0, δa}, {b, b0, δb}, {c, c0, δc}]
-
$\begingroup$ Thank you for your answer, but I'm not sure how to incorporate it. Should I input values for $a,b,c,δa, δb, δc$ and define the function at the beginning? $\endgroup$– TanEmaAug 22, 2019 at 15:51
-
$\begingroup$ @TanEma I've added an example to the answer showing how to both approaches with numerical values. Essentially, you just replace the values
a0
,b0
,... and errorsδa
,δb
,... with numbers as needed. You can also mix values and variables. For the first approach, you just define your function beforehand, for the second one, you insert the expression you want to analyze as first argument. Let me know if you have any other questions $\endgroup$ Aug 23, 2019 at 16:08