Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

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.

share|improve this question

2 Answers 2

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.

share|improve this answer
    
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. –  Uwe Thümmel Jul 25 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 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 at 12:50
    
defWithOpts looks great. Thanks a lot! –  Uwe Thümmel Jul 25 at 13:00
    
@UweThümmel You're welcome. Let me know if you have any trouble with it. –  Mr.Wizard Jul 25 at 13:02

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.

share|improve this answer
    
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 at 11:19
    
@Mr.Wizard tks for your comment. I changed f to accept default value for par. –  Murta Jul 25 at 11:33
    
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 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 at 11:39
2  
For completeness, the one-liner Plot[PDF[LogNormalDistribution[#mu, #sigma], x], {x, #lb, #ub}] &[par] works. –  Daniel W Jul 25 at 16:35

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.