UpValues


Normally Cosh[x]+Sinh[x] doesn't evaluate to Exp[x] but there are different
ways of making the kernel automatically perform this simplification.  A few
ways to do this are discussed in the cells below.  Before we begin the next
cell should be evaluated to unprotect the functions that will be modified.

Unprotect[Plus, Cosh, Sinh] ;  Clear[Plus, Cosh, Sinh, t, x]

Once Plus is unprotected evaluating the next cell will cause an expression  such as Cosh[x] + Sinh[x] to evaluate to Exp[z].

Cosh[z_] + Sinh[z_] := Exp[z]

The rule above has the desired effect in the cell below.

1/(1 + Cosh[t + 1.5] + Sinh[t + 1.5])

1/(1 + ^(1.5 + t))

However, Plus is a function that's used very often, and the code above  will slow down evaluation of Plus.  Actually it doesn't slow down Plus as  much as one might think.  In  Mathematica versions 3 and 4 the functions Plus and Times apply built in rules before  user defined rules.  So when the next cell evaluates the kernel uses a  built-in rule to compute (2+3) before it checks for the above user defined  identity.  Likewise built-in rules are used for (3+2/3), and (x+2 x), so here  too the kernel never gets a chance to see if the user defined rule above  should be used.  As a result the user defined rule had no effect on the speed  of evaluating the cell below.

(2 + 3)^(1/2) Exp[3 + 2/3] (x + 2 x)

3 5^(1/2) ^(11/3) x


On the other hand consider evaluation of the next cell after the above
identity for Cosh[z_]+Sinh[z_] is in the kernel.

Exp[3 + 2/3 + x] (2 + 3 + x)^(1/2) Sin[x + 2 x + y]

^(11/3 + x) 5 + x^(1/2) Sin[3 x + y]

When evaluating the cell above the kernel uses a built-in rule to evaluate   (3 + 2/3 + x⟶11/3 + x ), then it checks to see if the user defined rule for Sinh[z_] + Cosh[z_] applies to (11/3 + x).  Later in the evaluation the kernel uses a built-in rule to evaluate (2 + 3 + x ⟶5 + x) and it checks to see if the user defined rule for Cosh[z_] + Sinh[z_] applies to (5 + x).  Still later in the evaluation the kernel uses a built-in rule to  evaluate (x + 2 x + y ⟶3 x + y) and it checks to see if the user defined rule for Cosh[z_] + Sinh[z_] applies to (3 x + y).  In this case the user defined rule for Cosh[z_] + Sinh[z_] causes degradation in the time it takes to evaluate the last example even  though the functions Cosh, Sinh appear nowhere in the expression.

The user  defined rule above was a "DownValue" for Plus as can be seen from the next  cell.  When the rule was entered above the kernel determined that Plus is the  head of Cosh[z_] + Sinh[z_] and that is why it was associated with Plus.

DownValues[Plus]

{HoldPattern[Cosh[z_] + Sinh[z_]] ^z}


Instead of associating the above identity with Plus (an often used function)
we can associate the identity with a head one level deeper than Plus.  In
this case we can associate the identity with Cosh and/or Sinh as an UpValue.  
Before proceeding we should do away with the above definition.

Clear[Plus, Cosh, Sinh]

Evaluating the next cell will associate the identity with Sinh as an  UpValue.  For more on UpValues see The Mathematica Book section 2.4.10.

Sinh/:Cosh[z_] + Sinh[z_] := Exp[z]


By starting the line above with Sinh/:  the kernel knows to associate the
identity with Sinh.  Once the previous cell is evaluated the next cell can be
used to see that the identity is associated with Sinh.

UpValues[Sinh]

{HoldPattern[Cosh[z_] + Sinh[z_]] ^z}


At first one might guess that the above Identity would cause the next cell to
change into Exp[3.5] which then evaluates to 33.1155.  However Cosh[3.5]
evaluates to 16.5728, and Sinh[3.5] evaluates to 16.5426 before the kernel
checks for the above identity.

Cosh[3.5] + Sinh[3.5]

33.1155


Now when the next line evaluates the above identity is used, but only after
the kernel finds it can't do anything with Cosh[3] or Sinh[3].

Cosh[3] + Sinh[3]

^3

Now consider evaluation of the expression below.  Here the above identity  is still stored in UpValues[Sinh] and not  in DownValues[Plus] or anywhere  else.  Since the identity isn't stored with Plus there is no impact on the  evaluation of (x + 1), Cosh[x + 1] , (3 + Cosh[x + 1] ) or (3 + Cosh[x + 1] )^(-1).  In fact the above identity has no impact on the evaluation of  Sinh[3].   However, when the kernel evaluates (2 x Sinh[3]) it checks for user defined  UpValues of Integer (the head of 2), but there are none.  Then the kernel  checks for user defined UpValues of (x), but there are none.  Then the kernel  checks the for user defined UpValues of Sinh, and there is an UpValue.  At  that point the kernel must see if the UpValue for Sinh can be used, but finds  that it can't.  Then the kernel checks for built in UpValues for Integer, x,  Sinh and either finds none or none that apply to  (2 x Sinh[3] ).  Then the kernel checks for user defined DownValues for Times and finds  none.  Finally the kernel checks for built in DownValues for Times, and  either finds none or none that apply to (2 x Sinh[3]).  At that point evaluation is complete.

(2 x Sinh[3])/(3 + Cosh[x + 1])

(2 x Sinh[3])/(3 + Cosh[1 + x])

Storing the identity for Cosh[z_] + Sinh[z_] with UpValues[Sinh] should have much less impact on the evaluation of  typical expressions than storing it with DownValues[Plus].  However, this  identity will have an impact on the evaluation of many expressions that  include Sinh, and this is likely the reason why Wolfram Research didn't  include it as part of the default evaluation process.

Evaluating the next  cell will remove any the above definitions.

Clear[Plus, Cosh, Sinh]

Instead of storing the above identity with UpValues[Sinh] one could store  it with UpValues[Cosh] using the next cell.  The only difference this will  make is that it will slow down evaluation some Cosh[z_] expressions, but never slow down evaluation of Sinh[_] expressions.  Starting the next line with Cosh/:  tells the kernel to  store the identity with UpValues[Cosh].

Cosh/:Cosh[z_] + Sinh[z_] := Exp[z]

Still another way to store this identity is to use the next line.  That  will store the identity with UpValues for each symbol at level 1 of (Cosh[z_] + Sinh[z_] ).  In this case Plus is at level 0 and Sinh, Cosh are the only symbols at  level 1.  This approach will slow down evaluation of Sinh[_] and Cosh[_]  expressions with no advantage over storing the identity only with  UpValues[Sinh], or only with UpValues[Cosh].

Cosh[z_] + Sinh[z_]^:=Exp[z]

All example above used UpValues with delayed assignment.  Mathematica provides ways to make UpValues with immediate assignment, but one should  be very cautious of them.  This is done in the cells below.  In each case then variable  (z) is used for the named pattern on the left side, but when (z) has a global  value this global value will be used on the right side.

z = 7 + 5 I ;  Clear[Plus, Sinh, Cosh]  Sinh/:Cosh[z_] + Sinh[z_] = Exp[z] ;

x^(1/2) + Cosh[3] + Sinh[z]

x^(1/2) + Cosh[3] + Sinh[7 + 5 ]


Instead a definition such as the one above should be stored as an UpValue
with delayed assignment as I do in the next cell.  When delayed assignment is
used a global value assigned to (z) will not affect the results.

Clear[Plus, Sinh, Cosh] ;  Sinh/:Cosh[z_] + Sinh[z_] := Exp[z]

x^(1/2) + Cosh[3] + Sinh[3]

^3 + x^(1/2)


We can't assign a definition with a symbol that's too deep inside an
expression.

We can use UpValues to store the identity (Sin[z_]^( 2) + Cos[z_]^( 2) = 1).  However, this identity can't be stored with the UpValues[Sin] or  UpValues[Cos] because Sin, Cos are too deep in the expression ( Sin[z_]^( 2) + Cos[z_]^( 2) ).  UpValues must be stored with a symbol that appears at level 1.  In this  case Plus is at level 0, Power is at level 1, Sin, Cos, Integer are at level  2.  The identity can be stored in  UpValues[Power] as in the next cell.

Unprotect[Plus, Power] ;  Clear[Plus, Power, b] ;  Power/:Sin[z_]^2 + Cos[z_]^2 = 1 ;

Sin[b + π/6]^2 + Cos[b + π/6]^2

1

UpValues[Power]

{HoldPattern[Cos[z_]^2 + Sin[z_]^2] 1}


We can also store the identity with DownValues[Plus] using the next cell.

Unprotect[Power, Plus] ;    Clear[Plus, Power, a]    Sin[z_]^2 + Cos[z_]^2 = 1 ;

Sin[a + π/3]^2 + Cos[a + π/3]^2

1

DownValues[Plus]

{HoldPattern[Cos[z_]^2 + Sin[z_]^2] 1}


Before continuing you might want to evaluate the following to remove all
definitions made above.

Unprotect[Plus, Cosh, Sinh, Power] ;  Clear[Plus, Cosh, Sinh, Power] ;  Protect[Plus, Cosh, Sinh, Power] ;


Created by Mathematica  (May 16, 2004)

Back to Ted’s Tricks index page