38

I have a number of functions that all take a large number of parameters. I am wondering what is the best practice of passing these parameters to those functions. I could, of course, simply specify the parameters outside the functions, as in (note that, in the actual example, there are far more parameters)

mu=1;
sigma=1;
lb=0;
ub=10;
f[x_] := PDF[LogNormalDistribution[mu, sigma],x]

However, I would prefer to explicitly pass the parameters to the functions. In Python, I would use a dictionary. In Mathematica, one possibility would be a replacement rule.

par={mu->1,sigma->1,lb->0,ub->10};
f[x_,par_] := PDF[LogNormalDistribution[mu, sigma],x]/.par

However, this can cause warnings if a function only takes numerical arguments, e.g.

Plot[f[x,par],{x,lb,ub}]/.par

Plot::plln: Limiting value lb in {x,lb,ub} is not a machine-sized real number. >>

The plotting actually, works, though.

Also, passing parameters using replacement rules seems to be inefficient, since - if possible - evaluations are done symbolically, and only then are values substituted for variables.

Reb.Cabin
  • 8,661
  • 1
  • 34
  • 62
U.T.
  • 573
  • 5
  • 7
  • I notice that you never Accepted an answer to this question. Does anything remain unaddressed or unsatisfactory? – Mr.Wizard Dec 08 '14 at 21:56

2 Answers2

28

Basic proposal

There are a number of options and their attractiveness will depend on the scenario for their use, therefore it is difficult to make any broad recommendations of best practice.

I will say that generally it is not recommended to rely on global assignments as in your first example, because this method scales poorly and because it is easy to make mistakes and get invalid results.

One approach you might consider is this:

Options[defs] = {mu -> 1, sigma -> 1, lb -> 0, ub -> 10};

f[x_, OptionsPattern[defs]] := PDF[LogNormalDistribution[OptionValue[mu], OptionValue[sigma]], x]

Now you can call f with one argument:

f[1.6]
0.216668

Or you can override values with explicit Options:

f[1.6, mu -> 1.7]
0.117023

You can also quickly change a value using SetOptions:

SetOptions[defs, sigma -> 2]
{mu -> 1, sigma -> 2, lb -> 0, ub -> 10}
f[1.6]
0.120368

Note: making assignments to the Option names (e.g. mu = 1) will break your code.
Consider either Protect-ing these Symbols or using Strings instead, e.g. "mu" -> 1.

One disadvantage of this method is that it lengthens definitions. Sometimes using With makes these more clear:

f[x_, OptionsPattern[defs]] := 
  With[{mu = OptionValue[mu], sigma = OptionValue[sigma]}, 
    PDF[LogNormalDistribution[mu, sigma], x]
  ]

This can be streamlined using listWith from: Constructing symbol definitions for With:

SetAttributes[listWith, HoldAll];

listWith[(set : Set | SetDelayed)[L_, R_], body_] := set @@@ Thread[Hold @@@ {L, R}, Hold] /. _[x__] :> With[{x}, body]

Now:

f[x_, OptionsPattern[defs]] := 
  listWith[{mu, sigma} = OptionValue[{mu, sigma}],
    PDF[LogNormalDistribution[mu, sigma], x]
  ]

Automation

Manual specification

Although this will not work with String parameter names here is a method to further automate function construction:

SetAttributes[defWithOpts, HoldAll]

defWithOpts[ sym_Symbol, opts : {__Symbol}, (set : Set | SetDelayed)[h_[args___], RHS_] ] := set[ h[args, OptionsPattern[sym]], listWith[opts = OptionValue[opts], RHS] ]

Now:

defWithOpts[def, {mu, sigma},
  g[x_] := PDF[LogNormalDistribution[mu, sigma], x]
]

And the definition that was created:

?g

Global`g

g[x_, OptionsPattern[def]] := 
  listWith[{mu, sigma} = OptionValue[{mu, sigma}], 
    PDF[LogNormalDistribution[mu, sigma], x]]

For code specific to String parameter names see: How to write complex function definitions at run time?

Automatic detection

Yet another idea for automation, built on the assumption that you will first define the Options list (with dummy values if necessary) then the functions. It works by finding all cases of parameter (Option) names within the right-hand-side of the definition.

SetAttributes[defAutoOpts, HoldAll]

defAutoOpts[ sym_Symbol: defs, (set : Set | SetDelayed)[h_[args___], RHS_] ] := Cases[Unevaluated@RHS, Alternatives @@ Options[sym][[All, 1]], {-1}, Heads -> True] // set[h[args, OptionsPattern[sym]], listWith[# = OptionValue[#], RHS]] &

Now you can do this:

defAutoOpts[
  h[x_] := PDF[LogNormalDistribution[mu, sigma], x]
]

Which creates:

h[x_, OptionsPattern[defs]] := 
  listWith[{mu, sigma} = OptionValue[{mu, sigma}], 
    PDF[LogNormalDistribution[mu, sigma], x]]

You can also call defAutoOpts[defs2, . . .] to use a different parameter list.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • Great! Would it be possible to construct listWith such that it only needs {mu,sigma} and body as an argument? I have tried kwargs[list_, f_] := listWith[list = OptionValue[list], f]

    but that does not work.

    – U.T. Jul 25 '14 at 12:22
  • @UweThümmel I'm glad this is helpful. I actually briefly had code in my answer that is somewhat like what you request but I removed it as it would not work with String parameter names. I shall assume that doesn't concern you and add it back. You probably needed HoldAll for kwargs but depending on how you are using it you will also need either an OptionsPattern[] or OptionValue[defs, list]. – Mr.Wizard Jul 25 '14 at 12:45
  • @UweThümmel Please look at defWithOpts and tell me if this does what you would like or if you wish to explore further development of kwargs. – Mr.Wizard Jul 25 '14 at 12:50
  • defWithOpts looks great. Thanks a lot! – U.T. Jul 25 '14 at 13:00
  • @UweThümmel You're welcome. Let me know if you have any trouble with it. – Mr.Wizard Jul 25 '14 at 13:02
  • @UweThümmel Yet another extension added. – Mr.Wizard Jul 25 '14 at 13:59
  • Ahh, I see what brought up your inquiry into my question about With. Nice, +1. – bobthechemist Jul 25 '14 at 17:54
21

In V10, another option is to use Association.

par=<|"mu"->1,"sigma"->1,"lb"->0,"ub"->10|>;

f[x_, p_Association:par] := PDF[LogNormalDistribution[p["mu"], p["sigma"]], x]

Plot[f[x, ##], {x, #lb, #ub}] &@par

enter image description here

Another form for Plot is:

Plot[f[x, par], {x, par@"lb", par@"ub"}]

And as @Mr.Wizard commented, you can use the default value for par, omitting it:

Plot[f[x], {x, par@"lb", par@"ub"}]

I like Associations because notation is much simpler than Options rule. The disadvantage is that they don't have filters as Options, and Associations do not accept pattern tests.

Murta
  • 26,275
  • 6
  • 76
  • 166
  • I see that I overlooked the use of lb and ub in Plot. I do like the look of this approach, though I have not yet used it myself. The way you wrote this one will need to pass par to f every time it is called; you could improve that by making par the default value for p: p_:Unevaluated[par]. I think you also lose the ability to override values by supplying an Option (at least cleanly). – Mr.Wizard Jul 25 '14 at 11:19
  • @Mr.Wizard tks for your comment. I changed f to accept default value for par. – Murta Jul 25 '14 at 11:33
  • 1
    Be aware that you just hard-coded the current value of par as the default. If the user later changes the dictionary this will lead to error and confusion. – Mr.Wizard Jul 25 '14 at 11:36
  • Oops: my recommendation p_ : Unevaluated[par] doesn't work either. Sadly Default will have problems of its own. I'm afraid you'll need something like this on every definition: Block[{par}, f[x_, p : (_Association | par) : par] := . . . ] – Mr.Wizard Jul 25 '14 at 11:39
  • 4
    For completeness, the one-liner Plot[PDF[LogNormalDistribution[#mu, #sigma], x], {x, #lb, #ub}] &[par] works. – Daniel W Jul 25 '14 at 16:35
  • @Mr.Wizard Can you explain to me why p:_Association:par does not work in this case? Is it because par does not match _Association in its unevaluated form? – sebhofer Jul 27 '14 at 09:21
  • @sebhofer If par is already defined that pattern will work but the current values will be hard-coded. If par is undefined or Blocked it will fail because as you note par does not match _Association, and the default must match the pattern. – Mr.Wizard Jul 27 '14 at 09:33
  • @Mr.Wizard I was referring to the latter case. Thx for the explanation! – sebhofer Jul 27 '14 at 09:35
  • After having give a quick try to Association and a more thorough one to OptionsPattern I have to say that I really prefer OptionsPattern. In deed OptionsPattern can have defaults, which in some cases is very handy. Also one can have a quick summary of the inputs taken by a function by just doing the standard Options[functions]. If OptionValue is too long to type, which I can agree upon, a quick OV[x_]:=OptionValue[x] would do the job. Any impressions against or in favor of my comparison of the two possibilities? – Rho Phi May 13 '15 at 14:39