# Operate on functions with arbitrarily many arguments

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.

• 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 – Lukas Lang Aug 21 '19 at 19:01
• 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$ – TanEma Aug 21 '19 at 19:04
• @TanEma but then what should be done? What exactly do you want to automate? (because you can just type the definition yourself, right?) – Lukas Lang Aug 21 '19 at 19:10
• 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 – TanEma Aug 21 '19 at 19:13
• @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 – Lukas Lang Aug 21 '19 at 20:44

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 use Sequence)

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 use UnitVector to get the appropriate lists:

Derivative[Sequence @@ UnitVector[3, 1]][f]


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 of Derivative to do the differentiation.

• Since we're using a PatternSequence (__) for vars, 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 like computeUncertainty2[Echo@f[a, b, c], {a, Echo@a0, Echo@δa}, {b, b0, δb}, {c, c0, δc}] would trigger each Echo 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}]


• 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? – TanEma Aug 22 '19 at 15:51
• @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 – Lukas Lang Aug 23 '19 at 16:08