12

So generally, if you have two functions f,g: X -->Y, and if there is some binary operation + defined on Y, then f + g has a canonical definition as the function x --> f(x) + g(x).

What's the best way to implement this in Mathematica?

f[x_] := x^2
g[x_] := 2*x
h = f + g;
h[1]

yields

(f + g)[1]

as an output

of course,

H = Function[z, f[z] + g[z]];
H[1]

Yields '3'.

Sjoerd C. de Vries
  • 16,122
  • 3
  • 42
  • 94
nick maxwell
  • 1,441
  • 3
  • 16
  • 22
  • Thank you, and +1 (By the way, it is better not to sign your messages, as this is redundant, being that your identity block it below every question or answer.) – Mr.Wizard Oct 26 '11 at 02:36

4 Answers4

13

Consider:

In[1]:= Through[(f + g)[1]]

Out[1]= f[1] + g[1]

To elaborate, you can define h like this:

h = Through[ (f + g)[#] ] &;

If you have a limited number of functions and operands, then UpSet as recommended by yoda is surely syntactically cleaner. However, Through is more general. Without any new definitions involving Times or h, one can easily do:

i = Through[ (h * f * g)[#] ] &
i[7]
43218
Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
  • Thanks for the `Through` function; I did not know about that! – abcd Oct 25 '11 at 22:52
  • Sweet, thanks. I'm writing a gram-schmidt function; it would be nice if it worked for generic objects posing as vectors, such as functions, lists, etc. I think probably the best thing to do is have as optional arguments, 'add' and 'scale', so that you can define what vector addition and scaling is, so the gramm-schmidt function would work on general objects. – nick maxwell Oct 25 '11 at 23:10
  • @ yoda, see previous comment: I'll have long lists of functions, which will be the projections of the next vector on to the spans of the previous, orthonormalized vectors. – nick maxwell Oct 25 '11 at 23:12
  • 3
    @nmaxwell, depending on your familiarity with *Mathematica*, you may find this informative: `combine[{__funcs}, _operator] := Through[operator[funcs][#]] &` -- you can use it like `h = combine[{f,g}, Plus]`. – Mr.Wizard Oct 25 '11 at 23:21
  • 1
    The package PushThrough of David Park uses Through to answer similar questions: http://home.comcast.net/~djmpark/Mathematica.html – faysou Oct 26 '11 at 00:59
  • @Faysal, I must have seen that before, but I cannot recall it. It appears useful and appropriate. – Mr.Wizard Oct 26 '11 at 02:16
  • @nmaxwell Might want to have a look at Orthogonalize and Projection. – Daniel Lichtblau Oct 26 '11 at 15:42
10

Another way of doing what you're trying to do is using UpSetDelayed.

f[x_] := x^2;
g[x_] := 2*x;

f + g ^:= f[#] + g[#] &; (*define upvalues for the operation f+g*)

h[x_] = f + g;

h[z]

Out[1]= 2 z + z^2

Also see this very nice answer by rcollyer (and also the ones by Leonid & Verbeia) for more on UpValues and when to use them

Community
  • 1
  • 1
abcd
  • 41,765
  • 7
  • 81
  • 98
  • +1 -- I didn't suggest UpSet because I took the question to be generic for a variety of functions rather than specifically `f` and `g`. – Mr.Wizard Oct 25 '11 at 22:54
  • Yeah, you're probably right... It was his last statement that suggested he was thinking of overloading operators made me post an answer that would point towards it. Besides, I couldn't think of any other and actually waited for about 5-10 mins after you answered just to see if you'll do a quick ninja edit to include `UpSet` :D – abcd Oct 25 '11 at 22:58
3

I will throw in a complete code for Gram - Schmidt and an example for function addition etc, since I happened to have that code written about 4 years ago. Did not test extensively though. I did not change a single line of it now, so a disclaimer (I was a lot worse at mma at the time). That said, here is a Gram - Schmidt procedure implementation, which is a slightly generalized version of the code I discussed here:

oneStepOrtogonalizeGen[vec_, {}, _, _, _] := vec;

oneStepOrtogonalizeGen[vec_, vecmat_List, dotF_, plusF_, timesF_] := 
    Fold[plusF[#1, timesF[-dotF[vec, #2]/dotF[#2, #2], #2]] &, vec,  vecmat];

GSOrthogonalizeGen[startvecs_List, dotF_, plusF_, timesF_] := 
    Fold[Append[#1,oneStepOrtogonalizeGen[#2, #1, dotF, plusF, timesF]] &, {},  startvecs];

normalizeGen[vec_, dotF_, timesF_] := timesF[1/Sqrt[dotF[vec, vec]], vec];

GSOrthoNormalizeGen[startvecs_List, dotF_, plusF_, timesF_] := 
  Map[normalizeGen[#, dotF, timesF] &, GSOrthogonalizeGen[startvecs, dotF, plusF, timesF]];

The functions above are parametrized by 3 functions, realizing addition, multiplication by a number, and the dot product in a given vector space. The example to illustrate will be to find Hermite polynomials by orthonormalizing monomials. These are possible implementations for the 3 functions we need:

hermiteDot[f_Function, g_Function] := 
   Module[{x}, Integrate[f[x]*g[x]*Exp[-x^2], {x, -Infinity, Infinity}]];

SetAttributes[functionPlus, {Flat, Orderless, OneIdentity}];
functionPlus[f__Function] :=   With[{expr = Plus @@ Through[{f}[#]]}, expr &];

SetAttributes[functionTimes, {Flat, Orderless, OneIdentity}];
functionTimes[a___, f_Function] /; FreeQ[{a}, # | Function] := 
      With[{expr = Times[a, f[#]]}, expr &];

These functions may be a bit naive, but they will illustrate the idea (and yes, I also used Through). Here are some examples to illustrate their use:

In[114]:= hermiteDot[#^2 &, #^4 &]
Out[114]= (15 Sqrt[\[Pi]])/8

In[107]:= functionPlus[# &, #^2 &, Sin[#] &]
Out[107]= Sin[#1] + #1 + #1^2 &

In[111]:= functionTimes[z, #^2 &, x, 5]
Out[111]= 5 x z #1^2 &

Now, the main test:

In[115]:= 
results = 
  GSOrthoNormalizeGen[{1 &, # &, #^2 &, #^3 &, #^4 &}, hermiteDot, 
      functionPlus, functionTimes]

Out[115]= {1/\[Pi]^(1/4) &, (Sqrt[2] #1)/\[Pi]^(1/4) &, (
  Sqrt[2] (-(1/2) + #1^2))/\[Pi]^(1/4) &, (2 (-((3 #1)/2) + #1^3))/(
  Sqrt[3] \[Pi]^(1/4)) &, (Sqrt[2/3] (-(3/4) + #1^4 - 
  3 (-(1/2) + #1^2)))/\[Pi]^(1/4) &}

These are indeed the properly normalized Hermite polynomials, as is easy to verify. The normalization of built-in HermiteH is different. Our results are normalized as one would normalize the wave functions of a harmonic oscillator, say. It is trivial to obtain a list of polynomials as expressions depending on a variable, say x:

In[116]:= Through[results[x]]
Out[116]= {1/\[Pi]^(1/4),(Sqrt[2] x)/\[Pi]^(1/4),(Sqrt[2] (-(1/2)+x^2))/\[Pi]^(1/4),
(2 (-((3 x)/2)+x^3))/(Sqrt[3] \[Pi]^(1/4)),(Sqrt[2/3] (-(3/4)+x^4-3 (-(1/2)+x^2)))/\[Pi]^(1/4)}
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
3

I would suggest defining an operator other than the built-in Plus for this purpose. There are a number of operators provided by Mathematica that are reserved for user definitions in cases such as this. One such operator is CirclePlus which has no pre-defined meaning but which has a nice compact representation (at least, it is compact in a notebook -- not so compact on a StackOverflow web page). You could define CirclePlus to perform function addition thus:

(x_ \[CirclePlus] y_)[args___] := x[args] + y[args]

With this definition in place, you can now perform function addition:

h = f \[CirclePlus] g;
h[x]
(* Out[3]= f[x]+g[x] *)

If one likes to live on the edge, the same technique can be used with the built-in Plus operator provided it is unprotected first:

Unprotect[Plus];
(x_ + y_)[args___] := x[args] + y[args]
Protect[Plus];

h = f + g;
h[x]
(* Out[7]= f[x]+g[x] *)

I would generally advise against altering the behaviour of built-in functions -- especially one as fundamental as Plus. The reason is that there is no guarantee that user-added definitions to Plus will be respected by other built-in or kernel functions. In some circumstances calls to Plus are optimized, and those optimizations might be not take the user definitions into account. However, this consideration may not affect any particular application so the option is still a valid, if risky, design choice.

WReach
  • 18,098
  • 3
  • 49
  • 93
  • Like the OP says, function addition is canonically defined and so any other definition is a *deviation* of what all mathematicians expect. – qwr Jan 29 '20 at 08:40