Applying a list of functions to an argument: a dual of  Map

FunctionMap provides a  convenient and elegant solution to a variety of comparison tasks, including results of calculations with different parameter values, effects of different functions, and solutions of differential equations with different initial conditions.

by János Tóth and Péter Ván

1. The abstract problem and its first solution

There are cases when the usual level of exactness of mathematics is not enough for our purposes in programming. Such an example is provided when calculating the image of a set. In mathematics the following definition is quite common

\(TraditionalForm
    \`f(A) := {f(a); \ a ∈ A}\ \ \ \ for\ \ \ sets\ A ⊂ 
        \(D\_f.\)\)

In programming one has to make a distinction between the original function  f and the set-valued function generated by f which is defined on the power set of the domain of f. Instead of sets let us talk about lists, the most fundamental data  structure of Mathematica. "If you have a list of elements, it is often important to be able to apply a function separately to each of the elements. You can do this in Mathematica using Map" [1, p. 241].

We can however meet problems when a single argument is given together with a list of functions and we wish to apply all the functions of the list to the given argument. The simplest way to solve this is to use the function Through as shown in the following examples. The first one is an abstract example:

Through[{f, g, h}[x]]

The second is to make a plot of a list of functions. This can be done in the following way.

Plot[
      Evaluate[Through[{Cos, Sin, BesselJ[0, #]&}[x]]], {x, 0, 4  π}];

It may also happen that the list of functions one should like to apply to the fixed argument has a more complicated structure. In this case,  one has to construct a new function from scratch.

FunctionMap1[funlist_, argu_, levelspec_: {1}] := 
      Map[#[argu]&, funlist, levelspec]

Let us see some abstract examples:

FunctionMap1[{f, g}, x]

FunctionMap1[{{f, g}, {h, j}, k}, x, {2}]

2. Practical applications

A more convincing graphical application

Suppose we are given a two variable function and we are going to investigate this from several points of view. Then, it is quite natural to make a series of graphics of different type as below.

<< Graphics`

Through[{ContourPlot[##]&, PlotVectorField[{1, #1}, #2, #3]&, 
          ImplicitPlot[#1 == 0, #2, #3]&, Plot3D[##]&}[
        (y^2) - Cos[x], {x, -π, π}, {y, -π, π}]];

The above result can also be obtained by the function we have defined.

FunctionMap1[{ContourPlot@@#&, 
        PlotVectorField[{1, #[[1]]}, #
              [[2]], #
              [[3]]]&, 
        ImplicitPlot[#[[1]] == 0, #
              [[2]], #
              [[3]]]&, Plot3D@@#&}, {
        (y^2) - Cos[x], {x, -π, π}, {y, -π, π}}];

If one wants to have some freedom to control the options then one might  wish to define another function, perhaps using Utilities`FilterOptions`.

Plotting several numerical solutions at once

\(xini = {1, 2}; 

    ndsol = \(\(NDSolve[{\(x'\)[t] == \(-x[t]\), x[0] == \ #}, {x}, {t, 0, 
                10}]\)[\([1, 1, 2]\)]&\)/@xini; 

    Plot[Evaluate[FunctionMap1[ndsol, t]], {t, 0, 4}, PlotRange -> {0, 2.1}]; 
    \)

One may wish to see the log-log plot of the solution:

Plot[Evaluate[Log[FunctionMap1[ndsol, Log[x]]]], {x, 1, 4}, 
      PlotRange -> {-1, 1}];

Simplifying roots

Another practical application arises when one has an ugly formula and one should like to compare the different simplifying and transforming functions of Mathematica.

sols = Solve[(x^3) - 3  (x^2) + 1 == 0, x];

FunctionMap1[{Expand, Simplify, FullSimplify, ExpToTrig}, sols];

(The reader may wish to omit the semicolon at the end of the last expression.)

Trigonometric form of a complex number

Suppose we are given the task that the expression \(TraditionalForm
      \`\(1 + i\)\/\(2 
          \((Cos[π\/3] + i\ Sin[π\/3])\)\)\) is to be transformed into trigonometric form. The program provides us several transformation possibilities for complex numbers.

FunctionMap1[{Expand, Factor, Apart, TrigExpand, ExpToTrig, 
        ComplexExpand}, 
      ((1 + I)/(2 
          (Cos[(π/3)] + I Sin[(π/3)])))]

Let us measure how complicated these expressions are by the number of the  leaves in their FullForm.

LeafCount/@%

None of the built-in functions gives the answer expected by the teacher, or the answer with a much less number of leaves.

LeafCount/@{((E^(-((I π)/12)))/(Sqrt[2])), 
        ((-(((-1))^((11/12))))/(Sqrt[2]))}

The exponential function of a matrix

The exponential function of a matrix is usually quite complicated and it should usually be transformed  before use.

Timing[
      FunctionMap1[{Expand, TrigExpand, TrigFactor, TrigReduce, ExpToTrig, 
          ComplexExpand}, MatrixExp[{{1, 5}, {-1, -3}} t]]];

Map[LeafCount, %, {3}]

Timing[
      Through[{Expand, TrigExpand, TrigFactor, TrigReduce, ExpToTrig, 
            ComplexExpand}[MatrixExp[{{1, 5}, {-1, -3}} t]]]];

Map[LeafCount, %, {3}]

Solution to a differential equation

dsol = 
      DSolve[{x'[t] == 4  x[t] - 5  y[t], y'[t] == x[t], 
          x[0] == 0, y[0] == 1}, {x[t], y[t]}, t]

simpdsol = 
      FunctionMap1[{Simplify, FullSimplify, TrigToExp, PowerExpand}, dsol];

TableForm[simpdsol]

Map[Last, Map[LeafCount, simpdsol, {4}], {3}]

Noncommutativity of consecutive simplifications

Let us remark in passing that the simplifying functions can be applied more than once one after the other and the result depends on the order of the their application.

{TrigReduce[TrigToExp[Cos[2  x]]], TrigToExp[TrigReduce[Cos[2  x]]]}

When looking for such cases one can rely upon FunctionMap1 defined above.

Moreover we can observe that consecutive applications of the simplifying functions may result in forms that are  different from all the results obtained in a single step.

3. A more convenient version: the second solution

It will be more convenient if one can also see the names of the functions together with the result they cause. Let us see an ad hoc solution first.

\(expr = x - \(x\^2 - 1\)\/\(x + 1\) + 2\/\((x + 1)\)\^2; 

    FunctionMap1[{Expand, Factor, Apart}, expr] // 
      TableForm[#, TableHeadings -> {ToString/@{Expand, Factor, Apart}}]&
        \)

An automatized solution might be:

FunctionMap2[funlist_, argu_] := 
      TableForm[Transpose[{ToString/@funlist, #[argu]&/@funlist}]]

FunctionMap2[{f, g, h}, x]

FunctionMap2[{Expand, Factor, Apart}, expr]

Inclusion of levelspec into FunctionMap2 is left to the reader as an exercise. We wonder if it is possible to find a useful application of FunctionMap1 or FunctionMap2 in the case when the second argument is a generalized list (an expression  with a head different from List) ?

{Through[(f + g + h)[x]], FunctionMap1[f + g + h, x]}

4. A version giving the relevant result: the third and last solution

We must admit that up to now we have selected by hand those simplifying and transforming functions which lead to different results. But there is a simple way to do this automatically.  Our original plan was to apply the SameTest option of the built-in function Union to select those {functionname, result} pairs which contain different second elements. This is how it should  work.

st[c_, d_] := 
      c[[2]] === 
        d[[2]];

\(\(lis = 	{{f, 1}, {g, 2}, {h, 1}, {k, 3}, {h, 1}, {m, 2}, {n, 3}}; 
    \)\)

Union[lis, SameTest -> st]

But it doesn't. Neither does it in this way.

Union[lis, SameTest -> (#1[[2]] == #2[[2]]&)]

As it does not seem to work as expected one has to write a program to accomplish the required task.

\(crit[lis_] := Union[\(Transpose[lis]\)[\([2]\)]]; 
	
    ust[lis_] := 

      Table[\(Union[
            Select[lis, \((#1[\([2]\)] === \(crit[lis]\)[\([i]\)])\)&]]\)[
          \([1]\)], {i, 1, Length[crit[lis]]}]\)

Let us see if ust works on the above abstract example:

ust[lis]

Now let us apply this function to keep only those simplifying functions from a large list which lead to different results.

\(FunctionMap3[funlist_, argu_, levelspec_: {1}] := 
	
      Module[{crit, ust}, 
		
        crit[lis_] := Union[\(Transpose[lis]\)[\([2]\)]]; 
		
        ust[lis_] := 
				
          Table[\(Union[
                Select[lis, \((#1[\([2]\)] === \(crit[lis]\)[\([i]\)])\)&]]\)[
              \([1]\)], 
					{i, 1, Length[crit[lis]]}]; 
		
        ust[Transpose[{ToString/@funlist, Map[#[argu]&, funlist]}]] // 
          TableForm]\)

Let us collect all those simplifying functions which are presented as the palette AlgebraicManipulation.

\(AlgePal = 
      Get[$TopDirectory<>
          "SystemFiles\FrontEnd\Palettes\AlgebraicManipulation.nb"]; 

    largelist = 
      ToExpression/@
        Table[AlgePal[\([1, 1, 1, 1, 1, i, 1, 1, 1, 1]\)], {i, 1, 
            Length[AlgePal[\([1, 1, 1, 1, 1]\)]]}]\)

FunctionMap3[largelist, 
      ((1 + I)/(2 
          (Cos[(π/3)] + I Sin[(π/3)])))]

Now we have obtained the result in a nice form: the functions follow each  other in alphabetic order and only those functions are shown which lead to a  result differing form all the previous ones. Let us take two further examples  from MathWire, April 1998.

FunctionMap3[largelist, (Sqrt[(x^2)]) + Log[(E^y)]]

FunctionMap3[largelist, Re[Sin[x + I y]]]

5. A generalization of the last solution: many arguments, many functions

We may wish to have both more than one arguments and more than one  functions. Then the built-in function Outer may help.

FunctionMap4[funlist_List, arglist_List, levelspec_:  1] := 
      Outer[#2[#1]&, arglist, funlist, levelspec]

FunctionMap4[{f, g, h}, {x, y, z}] // TableForm

We can create a similar function also with Through:

\(FunctionMap4t[funlist_List, arglist_List] := 
      \(Through[funlist[#]]&\)/@arglist; 

    FunctionMap4t[{f, g, h}, {x, y, z}] // TableForm\)

6. Discussion

In this paper we hve investigated several approaches with Mathematica for interlocking different functions. Now let us suppose we are given two functions:

\(TraditionalForm\`f\_\(i\  : \ A\_i\) -> \(\(B\_i\)\(\ \ \ \)\(\((i = 1, 2)\)\(.\)\)\(\ \)\)\) We can define their Cartesian product:

\(TraditionalForm
      \`f\_1×\(f\_\(2\ \) : \ A\_1×A\_2\) -> 
        B\_1×B\_2, \ 
      \((a\_1, a\_2)\) ↦ 
        \((\(f\_1\)(a\_1), \(f\_2\)(a\_2))\)\).

If (Subscript[A, 1]) = (Subscript[A, 2]) =  : A,  then we can also define a function pair or joint consisting of (Subscript[f, 1]) and (Subscript[f, 2]) as follows:

((Subscript[f, 1]), (Subscript[f, 2])) :  A -> (Subscript[B, 1])×(Subscript[B, 2]),  a ↦  ((Subscript[f, 1])(a), (Subscript[f, 2])(a)).

Through, FunctionMap1, etc.… produces the joint function. Map  gives the Cartesian product of same functions. The most general FunctionMap4 gives the Cartesian product of the joints.

\(ff = {f\_1, f\_2}; aa = {a\_1, a\_2}; 
Map[f\_1, aa]\)FunctionMap1[ff, (Subscript[a, 1])]FunctionMap4[ff, aa]

But Mathematica is very flexible, gives several methods to mix and trick with function heads and arguments. One can produce much more slier expressions easily (see eg. the levelspec argument in FunctionMap). Hovewer, the Cartesian product of two different functions is the example of the contrary, the simplest construction in mathematics can be tricky in Mathematica:

DescartesProd1[funlist_List, arglist_List] := 
      Inner[#1@@{#2}&, funlist, arglist, List]DescartesProd2[funlist_List, arglist_List] := 
      Apply[#1[#2]&, Transpose[{funlist, arglist}], 2]

{DescartesProd1[ff, aa], DescartesProd2[ff, aa]}

ACKNOWLEDGEMENT

The present work has partly been supported by the grant No. T025472 of the Hungarian Natoional Research Program.

Reference

1. Wolfram, S.: The Mathematica Book, Wolfram Media, Cambridge University Press, 1996.

About the authors:

János Tóth received his diploma in mathematics at the Loránd Eötvös University of Budapest in 1971. Currently he is associate professor at the Department of Analysis of the Technical University of Budapest, Hungary, and participates in teaching at Loránd Eötvös University as well. He is the author of   Mathematical Models of Chemical Reactions (with P. Érdi, Manchester UP and Princeton UP) and of Mathematics and Mathematica (with L. Szili, Eötvös UP, in Hungarian) and  Reasearch and Publication in Science (with P. Csermely, T. Koltay and P. Gergely, in Hungarian, Osiris, Budapest, in Hungarian).

János Tóth
Department of Analysis
Technical University Budapest
Egry J. u. 1., H ép.
H–1111 Budapest, HUNGARY
jtoth@math.bme.hu, jtoth@helka.iif.hu

Péter Ván is physicist, he is an assistant professor of the Technical University of Budapest, Hungary.  His  research interest includes the theoretical  foundations and applications of classical and non-equilibrium  thermodynamics, space-time models and mathematical modeling of physical phenomena. He is applying Mathematica in his teaching and research.

Péter Ván
Department of Chemical Physics
Technical University Budapest
Budafoki út 8., F III ép.
H–1111 Budapest, HUNGARY
vanpet@phyndi.bme.hu

ELECTRONIC SUBSCRIPTIONS

Included in the distribution for each electronic subscription is the file functivegl.nb containing Mathematica code for the material described in this article.