Programming with Options

Many of the graphics functions and some other buil-in functions accept optional arguments, such as Plot, FindRoot, MatrixForm, NIntegrate, and Show.  The optional arguments are specified as rules in the form option names -> optionValue.  Unlike required arguments, options need not be specfied in a particular order.  Typically options are used in functions that have many user-settable features.  In this talk, I show you how to specified optional arguments for your own functions.

Copyright Notice

Copyright 1997 Variable Symbols, Inc., 
All rights reserved.  You may distribute this Notebook to others as long as this copyright notice appears at the top of the Notebook.
Variable Symbols, Inc.
6537 Chabot Road
Oakland, CA 94618
Fax: 510 652 8461
Tel: 510 652 8462
info@VariableSymbols.com
www.VariableSymbols.com

Optional Arguments & Default Values

Specifying Default Values

There may be times when you want an argument to be optional and have the argument assume some default value if the argument is not specified.  There are several ways to do this.  Here we define a function for calculating mortgage payments.  Notice that the usage statement indicates that you can call this function with 1, 2, 3, or 4 arguments. If we specify an argument with the pattern name_:value, then when the argument is not specified, it is taken to be the value specified.

Clear[mortgage]\(	mortgage::usage\  = 
      "mortgage[principal,paymentsPerYear,interest,years] calculates mortgage payments based on a user specifying the number of paymentsPerYear, interest, number of years, and principal of the loan.  The last three arguments are optional and taken to be 12, 7.25, and 30 if not specified."; 
    
		
	mortgage[principal_, paymentsPerYear_Integer:  12, 
        interest_Real:  7.25, nYears_Integer:  30] := 
	
      Module[{n = paymentsPerYear\ nYears, 
			
          r = 1 + interest\/\(100\ paymentsPerYear\)}, 
		
        \(principal\ r\^n\ \((1 - r)\)\)\/\(1 - r\^n\)]\)

If you do not specify the second argument to the mortgage function, it will default to the value 12, i.e., paymentsPerYear will assume the value 12.

mortgage[200000]

Suppose I want to buy a $15,000 car and get a 3-year loan.  What arguments should I pass to have mortgage compute my payments? What does the following compute? Be careful when there are several arguments that will assume default values.

mortgage[15000,  3]

The previous input assumes we are making three payments per year.  If we want to compute the payments for a 3 year long, because of the way the function is defined, we need to specify the number of payments per year before specifying the number of years.

mortgage[15000, 12, 3]

We can check this result by specifying values for all four arguments.

mortgage[15000, 12, 7.25, 3]

Another Way to Specify Optional Arguments

The pattern ____ (3 underscores) matches a sequence of zero or more expressions.  Here is the definition for LogLogPlot from the package Graphics`Graphics`.  Notice that the final argument opts is optional.

Needs["Graphics`Graphics`"]

LogLogPlot[f_, {x_, xmin_, xmax_}, opts___] := 
      Module[{r, 
          g = ParametricPlot[{Log[10, x], Log[10, f]}, {x, xmin, xmax}, 
              Ticks -> {LogScale, LogScale}, 
              FrameTicks -> {LogScale, LogScale, LogScale, LogScale}, 
              DisplayFunction -> Identity, opts]}, r = PlotRange[g]; 
        Show[g, DisplayFunction -> $DisplayFunction, 
          PlotRange -> r, 
          AxesOrigin -> 
            (#1[[1]]&)/@r]]

Here are the options for LogLogPlot.

Options[LogLogPlot]

You can call LogLogPlot with or without options and the optional arguments can be specified in any order.

Clear[x]LogLogPlot[(x^2), {x, .2, 10}, 
      PlotLabel -> "LogLog Plot of x^2", Frame -> True];

LogLogPlot[(x^2), {x, .2, 10}];

Which Functions have What Options

Using Options, you can find out the options and default values of a function.

Options[FindRoot]

Sometimes I wish to know what functions have a particular option.  I wrote the function hasOption for such purposes.

Clear[hasOption] \(hasOption::usage\  = \ 
      "hasOption[optName] or hasOption[optName -> value] returns the functions that accept the options optName"; 

    hasOption[a_Symbol\  | \ \ a_Rule\  | \ a_RuleDelayed]\  := 
		
      Module[{r\  = \ a}, \ 
		
        If[Head[a]\  == \ Symbol, \ r\  = \ a\  -> \ _]; 
\ \ \ \ \ \ 
        Select[
			Names["System`*"], \ 

          \ \ \ \ \ \ \ \ \(! 
                \((StringMatchQ[StringTake[#1, \ 1], \ "$"])\)\)\  && \ 

              \ \ \ \ \ \ \ \ \ \ \ \ 
              MemberQ[Options[ToExpression[#1]], \ r]&]
	]\)

Here we see the functions have the option PlotPoints, the functions have the option Compiled set to True, and the functions that have WorkingPrecision set to any value.  I included the last example to show that the argument to the function hasOption can include a pattern.

hasOption[PlotPoints ]

hasOption[Compiled  ->  True]

hasOption[WorkingPrecision  ->  _]

Programming with Options

Many of the graphics functions and some other built-in functions accept optional arguments.  Typically options are used in functions that have many user-settable features.This section shows you how to define options for your own functions.  Options are typically rules consisting of the option name and its setting.  Unlike required arguments, options need not be specified in a particular order.  Here we specify usage statements for options for the mortgage function.

paymentsPerYear::"usage" = 
      "paymentsPerYear is an option of the function mortgage."; interest::"usage" = 
      "interest is an option of the function  mortgage."; nYears::"usage" = 
      "nYears is an option of the function  mortgage.";

Now we define the options and their default settings.

Clear[mortgage]Options[mortgage] = {paymentsPerYear -> 12, 
        interest -> 8.5, nYears -> 30};

After you have defined the options, the default settings can be changed using SetOptions.

SetOptions[mortgage,  interest  ->  7.25]

Once the options are set, you can obtain values of the default values by  calling Options.

Options[mortgage]

Now let's see how you can write a function in which these options will assume these default values unless the user specifies other values as rules in the form optionName->optionValue when calling the mortgage function.  Assume {opts} to be a list of the options specified as an argument to the function.   We set the value of a variable int to the value of the option named interest that the user specifies as an argument or to the default value of the option by using the following code.

int = interest /. {opts} /. Options[mortgage]

Let's see how this works.  Suppose the user calls mortgage without specifying a value for interest.What's the value of int after executing the following statement?  It's as the default value for interest.

Clear[opts]int = interest /. {} /. Options[mortgage]

Suppose the user calls mortgage and specifies a value for interest.What's the value of int after executing the following statement?  It's the value specified in opts.

opts = interest -> 9.5; int = interest /. {opts} /. Options[mortgage]

Notice that if value of interest is not given in opts, then int is assigned the default value of the option interest.

\(opts = paymentsPerYear -> 4; 

    int = \(interest /. {opts}\) /. Options[mortgage]\)

Now we define the mortgage function, which accepts these options.

mortgage::usage = 
      "mortgage[principal, opts]\n returns the amount of each payment. \
Options are interest, nYears, and paymentsPerYear.";

\(mortgage[principal_, opts___Rule] := 
      Module[{int, years, payments, n, r}, 
		
        int = \(interest /. {opts}\) /. Options[mortgage]; 
		
        years = \(nYears /. {opts}\) /. Options[mortgage]; 
        payments = \(paymentsPerYear /. {opts}\) /. Options[mortgage]; 
		
        n = payments\ years; 
		r = 1 + int\/\(100.\ payments\); 
        \(principal\ r\^n\ \((1 - r)\)\)\/\(1 - r\^n\)]\)

If we call mortgage with no options, Mathematica uses the default value for the options.

mortgage[200000]

mortgage[200000,  nYears  -> 30,  paymentsPerYear -> 12,  
      interest -> 7.25]

If we call mortgage with an option, Mathematica uses the value specified for that option and the defaullt values for the other options.

mortgage[200000, interest -> 8]

mortgage[200000, interest -> 8,  paymentsPerYear ->  12,   
      nYears  -> 30]

There are several advantages to using optional arguments.  You need not specify optional arguments, as their name implies and they can be specified in any order.

Bicycle Gears

Here's an example of a function to compute gear ratios (actually, it computes the distance travelled in one pedal cycle).  This function was written by Troels Peterson, my co-editor at The Mathematica Journal

Clear[gears]\(gears::usage\  = "gear[front, back] gives the distance travelled
		in one pedal cycle given two lists with the number of teeth in front
		and back, e.g., gear[{52,42},{14,18,21,14,28}]"; 
	
    gears[ranges__List]\  := \ 
	
      TableForm[
\ \ \ \ \ \ Round[27\ Pi\ Outer[Divide, \ ranges]], 

        \ \ \ \ \ \ TableHeadings\  -> \ {ranges}
	]\)

Here's what we get when providing the gears from Troels' road bike,

gears[{52,  42},  {14,  18,  21,  24,  28}]

Here's the gears for a my Trek 730 hybrid.

gears[{42,  34,  24},  {11,  13,  15,  18,  20,  24,  28}]

The wheels on the mountain bike are not 27 inches, Let's rewrite the gear function so that it accepts an optional argument that specifies the wheel diameter, which should default to 27.

Solution

Clear[gears]\(gears::usage\  = "gear[front, back] plots the distance 
		travelled in one pedal cycle given two lists with the number of teeth in 
		front and the back, e.g., gear[{52,42},{14,18,21,14,28}]"; 	
    wheelDiameter::usage\  = \ "wheelDiameter is an option used for 
		specifying the size of a wheel. "; 

    Options[gears]\  = \ {wheelDiameter\  -> \ 27}; 

    gears[front_List, \ back_List, \ opts___]\  := \ 
		
      Module[{wheel}, 
		
        wheel\  = \ \(wheelDiameter\  /. \ {opts}\)\  /. \ Options[gears]; 

        		TableForm[
			
          Round[wheel\ Pi\ Outer[Divide, \ front, \ back]], 
			
          TableHeadings\  -> {front, \ back}]
	]\)

gears[{52,  42},  {14,  18,  21,  24,  28}]

gears[{52,  42},  {14,  18,  21,  24,  28},  
      wheelDiameter  ->  26]

Filtering Options

There are times when you might want to write a function that processes its arguments and then calls another function and passes the relevant options on to that function.  For example, consider a function that computes the distance travelled in one pedal cycle and plots the result using  MultipleListPlot.

The function FilterOptions defined in the package Utilities`FilterOptions` is intended for just such a purpose.

\(Needs["Utilities`FilterOptions`"]; 
\(?FilterOptions\)\)

Here we use FilterOption to return those options that are valid for MultipleListPlot.

Needs["Graphics`MultipleListPlot`"]\(	{FilterOptions[MultipleListPlot, 
		wheelDiameter\  -> 21, \ 
        Frame\  -> \ False, \ PlotJoined\  -> \ True\ ]}\)

So now let's write the function plotGears.  It start off similar to the gear function that we saw in the last section. But we added a call to FilterOptions to obtain the options for MultipleListPlot that the user specifies as arguments to plotGears.

Clear[plotGears]Needs["Graphics`MultipleListPlot`"]\(plotGears::usage\  = "plotGear[front, back] plots the distance 
		travelled in one pedal cycle given two lists with the number of teeth in 
		front and the back, e.g., gear[{52,42},{14,18,21,14,28}]"; 	
    wheelDiameter::usage\  = \ "wheelDiameter is an option used for 
		specifying the size of a wheel. "; 

    Options[plotGears]\  = \ {wheelDiameter\  -> \ 27}; 

    plotGears[front_List, \ back_List, \ opts___]\  := \ 
		
      Module[{wheel, \ mOpts}, 
		
        wheel\  = \ \(wheelDiameter\  /. \ {opts}\)\  /. \ Options[gears]; 

        		mOpts\  = FilterOptions[MultipleListPlot, \ opts]; 
		
        values\  = \ Round[wheel\ Pi\ Outer[Divide, \ front, \ back]]; 
		
        Apply[\ MultipleListPlot[##, mOpts]&, \ values]
	]\)

\(\(plotGears[{42, \ 34, \ 24}, 
      \ {11, \ 13, \ 15, \ 18, \ 20, \ 24, \ 28}, \ 
		
      wheelDiameter\  -> \ 27, \ PlotJoined\  -> \ True, \ 
		
      GridLines -> \ Automatic, Frame\  -> \ True]; \)\)

Let's look at another example.  Here, we've written the function plotWithTitle that labels the graph with the function being plotted.  We've specified the function with three rules.  One rule handles the case where no options are specified.  The second rule handles the case where the user specifies one or more options.  The third rule handles the case where the user specifies a PlotLabel.  The function ignores it since the graph is automatically labelled with the function being plotted.

Clear[plotWithTitle]\(	plotWithTitle[fn_, {v_, v1, v2}, o1___, PlotLabel -> lg_, o2___]\  := 
      
\ \ \ \ \ \ \ \ 
      Module[{opts}, 
		\ \ \ opts\  = \ FilterOptions[Plot, o1, o2]; 

        		\ \ \ \ Plot[fn, r, \ PlotLabel\  -> \ txt, \ Evaluate[opts]]
	]
        \)\(plotWithTitle[fn_, r_List, o1__]\  := 
		
      Module[{opts}, 
		\ \ \ opts\  = \ FilterOptions[Plot, o1]; 

        			\ Plot[fn, r, \ PlotLabel\  -> \ fn, Evaluate[\ opts]]
	]\)\(	plotWithTitle[fn_, r_List]\  := 
			\ 
      Plot[fn, r, \ PlotLabel\  -> \ fn]
\)

\(\(plotWithTitle[x^2, \ {x, \ 0, \ 2}, \ 
		
      AxesLabel\  -> \ {x, \ y}, \ Axis\  -> \ {0, \ 2}]; \)\)

\(\(plotWithTitle[x^2, \ {x, \ 0, \ 2}, \ 
		
      AxesLabel\  -> \ {x, \ y}, \ PlotLabel\  -> " my graph"]; \)\)

Graphics`Legend`

If you want to see more examples that use FilterOptions, take a look at code in the package Graphics`Legend`.  What does this do?

\(Plot[fn_, r_, o1___, PlotLegend -> None, o2___]\  := 
\ \ \ \ \ \ \ \ 
      Plot[fn, r, Evaluate[FilterOptions[Plot, o1, o2]]]\)

When you call Plot with the option PlotLegend-> None, Mathematica calls Plot with only options that are valid for the Plot command.

Here's another bit of code from the package Graphics`Legend` in which FilterOption is used to pick options for specifying different aspects of the graph, i.e., the plot, the shadow box, and the legent.

\(Plot[fn_, r_, o1___, PlotLegend -> lg_, o2___]\  := 
\ \ \ \ \ \ \ \ 
      Module[{txt\  = \ lg, sopts, gopts, lopts, ps, disp, ln, gr, tb}, 

        \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 
        gopts\  = \ FilterOptions[Plot, o1, o2]; 

        \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 
        sopts\  = \ FilterOptions[ShadowBox, o1, o2]; 

        \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 
        lopts\  = \ FilterOptions[Legend, o1, o2]; 

        \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ {ps, disp}\  = \ 
          \({PlotStyle, DisplayFunction} /. {gopts}\) /. 

            \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 
            Options[Plot]; 
				...

        			\ 
        ShowLegend[gr, {tb, sopts, lopts}, \ DisplayFunction\  -> \ disp]]\)

Mathematica Can Select Default Values

You can specify your own default values for optional arguments or you can leave it to Mathematica to select default values.  The default values that Mathematica selects depend on how the pattern is used in an expression.  In other words, it depends on context.  The pattern x_. assumes the system's default value if the user doesn't specify an expression.  The default value is designed to keep the value of the expression unchanged.

Predict the values of a, b, and e if you don't specify them.

Clear[f]\(	f[a_.\  + \ b_.\ x^e_.\ ]\  := 
      \ {{"a", \ a}, {"b", \ b}, {"e", \ e}}\)f[x]

When I didn't supply a value to match a_., a is taken to be 0. When I didn't supply a value to match b_., b is taken to be 1. When I didn't supply an expression to match e_., e is taken to be 1. The default value for a pattern multiplying an expression is 1.  The default value for a pattern being added to an expression is 0.  The default value for a pattern in an exponent is 1.

{Default[Plus],  Default[Times]}

See how patterns with default values used to be used in the rules for performing trigonometric simplifications.

`TrigFactorRel = {
    a_. Sin[x_] + a_. Sin[y_] :> 2 a Sin[x/2+y/2] Cos[x/2-y/2],
    a_. Sin[x_] + b_. Sin[y_] :> 2 a Sin[x/2-y/2] Cos[x/2+y/2] /; a+b == 0,
    a_. Cos[x_] + a_. Cos[y_] :> 2 a Cos[x/2+y/2] Cos[x/2-y/2],
    a_. Cos[x_] + b_. Cos[y_] :> 2 a Sin[x/2+y/2] Sin[y/2-x/2] /; a+b == 0,
    a_. Tan[x_] + a_. Tan[y_] :> a Sin[x+y]/(Cos[x] Cos[y]),
    a_. Tan[x_] + b_. Tan[y_] :> a Sin[x-y]/(Cos[x] Cos[y]) /; a+b == 0,

    a_. Sin[x_] Cos[y_] + a_. Sin[y_] Cos[x_] :> a Sin[x + y],
    a_. Sin[x_] Cos[y_] + b_. Sin[y_] Cos[x_] :> a Sin[x - y] /; a+b == 0,
    a_. Cos[x_] Cos[y_] + b_. Sin[x_] Sin[y_] :> a Cos[x + y] /; a+b == 0,
    a_. Cos[x_] Cos[y_] + a_. Sin[x_] Sin[y_] :> a Cos[x - y]
};

Summary

Patterns for Matching Expressions

x__ (2 underscores) matches a sequence of 1 or more expressions
x___ (3 underscores) matches a sequence of 0 or more expressions
x_:x0 if no argument is provided, assumes a default value of x0
x_. if no argument is provided, assumes a default value selected by Mathematica based on how it is used

Programming with Options

One can specify optional arguments in several different ways:
1) Specify multiple rules.
2) Specify an argument with ___ (matches 0 or more expressions)
3) Specify options and default values with Options.  Determine the value of the variable by looking at the value of the optional arguments and if it isn't supplied, look at the default value of the option using a statement of the form:

Clear[f]\(	f::usage\  = \ 
      "f[arg1, arg2, options...] is an example of a function that
		takes optional arguments specified as rules in the form name->value"; 

    Opt1::usage\  = \ "Opt1 is an option for f"; 

    Opt2::usage\  = \ "Opt2 is an option for f"; 

    Options[f]\  = 
      \ {Opt1\  -> \ defaultValue1, \ Opt2\  -> \ defaultValue2}; 
	
    f[arg1_, \ arg2_, opts___Rule] := 
	
      Module[{opt1, \ opt2}, 
		opt1 = \(Opt1 /. {opts}\) /. Options[f]; 

        		opt2 = \(Opt2 /. {opts}\) /. Options[f]; 

        		{arg1, \ arg2, opt1, \ opt2}
]\)

See how it works.

f[a,  b,  Opt1 ->  5]

Exercises

Perfect Shuffle

a) In a perfect shuffle, the dealer splits the deck exactly in half and then reorders the deck by interleaving cards from each half. There are two ways to do this:  either the first card of the first deck remains the first card, which is called an out shuffle, or the first card becomes the second card in the shuffled deck, in which case the shuffle is called an in shuffle. For example, if you had a deck of 10 cards, initially arranged in ascending order, i.e., {1,2,3,4,5,6,7,8,9,10}, after one out shuffle, the deck would be ordered {1,6,2,7,3,8,4,9,5,10}. After one in shuffle, the deck would be ordered  {6,1,7,2,8,3,9,4,10,5}.  Write the function perfectShuffle, which takes a list and by performs and in shuffle and if the user specifies the option Shuffle to out , then it performs an out shuffle.

b)  Write the function returnToOrder, which computes the number of perfect shuffles to return a deck of n cards to its original order.  Use returnToOrder to determine the number of perfect shuffles you need to return a deck of 52 cards to its original order using only in shuffles and only out shuffles.

Biography

Nancy Blachman is an editor of The Mathematica Journal and president and founder of Variable Symbols, Inc., a company that specializes in training and consulting in Mathematica. She is author of the popular tutorial book Mathematica: A Practical Approach (Prentice Hall) and is currently working with Colin Williams on the second edition. She teaches a class in "Problem Solving with Mathematica" at Stanford University.