calc: Automatic Rewrites

 
 11.11.11 Automatic Rewrites
 ---------------------------
 
 It is possible to get Calc to apply a set of rewrite rules on all
 results, effectively adding to the built-in set of default
 simplifications.  To do this, simply store your rule set in the variable
 ‘EvalRules’.  There is a convenient ‘s E’ command for editing
 ‘EvalRules’; SeeOperations on Variables.
 
    For example, suppose you want ‘sin(a + b)’ to be expanded out to
 ‘sin(b) cos(a) + cos(b) sin(a)’ wherever it appears, and similarly for
 ‘cos(a + b)’.  The corresponding rewrite rule set would be,
 
      [ sin(a + b)  :=  cos(a) sin(b) + sin(a) cos(b),
        cos(a + b)  :=  cos(a) cos(b) - sin(a) sin(b) ]
 
    To apply these manually, you could put them in a variable called
 ‘trigexp’ and then use ‘a r trigexp’ every time you wanted to expand
 trig functions.  But if instead you store them in the variable
 ‘EvalRules’, they will automatically be applied to all sines and cosines
 of sums.  Then, with ‘2 x’ and ‘45’ on the stack, typing ‘+ S’ will
 (assuming Degrees mode) result in ‘0.7071 sin(2 x) + 0.7071 cos(2 x)’
 automatically.
 
    As each level of a formula is evaluated, the rules from ‘EvalRules’
 are applied before the default simplifications.  Rewriting continues
 until no further ‘EvalRules’ apply.  Note that this is different from
 the usual order of application of rewrite rules: ‘EvalRules’ works from
 the bottom up, simplifying the arguments to a function before the
 function itself, while ‘a r’ applies rules from the top down.
 
    Because the ‘EvalRules’ are tried first, you can use them to override
 the normal behavior of any built-in Calc function.
 
    It is important not to write a rule that will get into an infinite
 loop.  For example, the rule set ‘[f(0) := 1, f(n) := n f(n-1)]’ appears
 to be a good definition of a factorial function, but it is unsafe.
 Imagine what happens if ‘f(2.5)’ is simplified.  Calc will continue to
 subtract 1 from this argument forever without reaching zero.  A safer
 second rule would be ‘f(n) := n f(n-1) :: n>0’.  Another dangerous rule
 is ‘g(x, y) := g(y, x)’.  Rewriting ‘g(2, 4)’, this would bounce back
 and forth between that and ‘g(4, 2)’ forever.  If an infinite loop in
 ‘EvalRules’ occurs, Emacs will eventually stop with a “Computation got
 stuck or ran too long” message.
 
    Another subtle difference between ‘EvalRules’ and regular rewrites
 concerns rules that rewrite a formula into an identical formula.  For
 example, ‘f(n) := f(floor(n))’ “fails to match” when ‘n’ is already an
 integer.  But in ‘EvalRules’ this case is detected only if the righthand
 side literally becomes the original formula before any further
 simplification.  This means that ‘f(n) := f(floor(n))’ will get into an
 infinite loop if it occurs in ‘EvalRules’.  Calc will replace ‘f(6)’
 with ‘f(floor(6))’, which is different from ‘f(6)’, so it will consider
 the rule to have matched and will continue simplifying that formula;
 first the argument is simplified to get ‘f(6)’, then the rule matches
 again to get ‘f(floor(6))’ again, ad infinitum.  A much safer rule would
 check its argument first, say, with ‘f(n) := f(floor(n)) :: !dint(n)’.
 
    (What really happens is that the rewrite mechanism substitutes the
 meta-variables in the righthand side of a rule, compares to see if the
 result is the same as the original formula and fails if so, then uses
 the default simplifications to simplify the result and compares again
 (and again fails if the formula has simplified back to its original
 form).  The only special wrinkle for the ‘EvalRules’ is that the same
 rules will come back into play when the default simplifications are
 used.  What Calc wants to do is build ‘f(floor(6))’, see that this is
 different from the original formula, simplify to ‘f(6)’, see that this
 is the same as the original formula, and thus halt the rewriting.  But
 while simplifying, ‘f(6)’ will again trigger the same ‘EvalRules’ rule
 and Calc will get into a loop inside the rewrite mechanism itself.)
 
    The ‘phase’, ‘schedule’, and ‘iterations’ markers do not work in
 ‘EvalRules’.  If the rule set is divided into phases, only the phase 1
 rules are applied, and the schedule is ignored.  The rules are always
 repeated as many times as possible.
 
    The ‘EvalRules’ are applied to all function calls in a formula, but
 not to numbers (and other number-like objects like error forms), nor to
 vectors or individual variable names.  (Though they will apply to
 _components_ of vectors and error forms when appropriate.)  You might
 try to make a variable ‘phihat’ which automatically expands to its
 definition without the need to press ‘=’ by writing the rule
 ‘quote(phihat) := (1-sqrt(5))/2’, but unfortunately this rule will not
 work as part of ‘EvalRules’.
 
    Finally, another limitation is that Calc sometimes calls its built-in
 functions directly rather than going through the default
 simplifications.  When it does this, ‘EvalRules’ will not be able to
 override those functions.  For example, when you take the absolute value
 of the complex number ‘(2, 3)’, Calc computes ‘sqrt(2*2 + 3*3)’ by
 calling the multiplication, addition, and square root functions directly
 rather than applying the default simplifications to this formula.  So an
 ‘EvalRules’ rule that (perversely) rewrites ‘sqrt(13) := 6’ would not
 apply.  (However, if you put Calc into Symbolic mode so that ‘sqrt(13)’
 will be left in symbolic form by the built-in square root function, your
 rule will be able to apply.  But if the complex number were ‘(3,4)’, so
 that ‘sqrt(25)’ must be calculated, then Symbolic mode will not help
 because ‘sqrt(25)’ can be evaluated exactly to 5.)
 
    One subtle restriction that normally only manifests itself with
 ‘EvalRules’ is that while a given rewrite rule is in the process of
 being checked, that same rule cannot be recursively applied.  Calc
 effectively removes the rule from its rule set while checking the rule,
 then puts it back once the match succeeds or fails.  (The technical
 reason for this is that compiled pattern programs are not reentrant.)
 For example, consider the rule ‘foo(x) := x :: foo(x/2) > 0’ attempting
 to match ‘foo(8)’.  This rule will be inactive while the condition
 ‘foo(4) > 0’ is checked, even though it might be an integral part of
 evaluating that condition.  Note that this is not a problem for the more
 usual recursive type of rule, such as ‘foo(x) := foo(x/2)’, because
 there the rule has succeeded and been reactivated by the time the
 righthand side is evaluated.
 
    If ‘EvalRules’ has no stored value (its default state), or if
 anything but a vector is stored in it, then it is ignored.
 
    Even though Calc’s rewrite mechanism is designed to compare rewrite
 rules to formulas as quickly as possible, storing rules in ‘EvalRules’
 may make Calc run substantially slower.  This is particularly true of
 rules where the top-level call is a commonly used function, or is not
 fixed.  The rule ‘f(n) := n f(n-1) :: n>0’ will only activate the
 rewrite mechanism for calls to the function ‘f’, but ‘lg(n) + lg(m) :=
 lg(n m)’ will check every ‘+’ operator.
 
      apply(f, [a*b]) := apply(f, [a]) + apply(f, [b]) :: in(f, [ln, log10])
 
 may seem more “efficient” than two separate rules for ‘ln’ and ‘log10’,
 but actually it is vastly less efficient because rules with ‘apply’ as
 the top-level pattern must be tested against _every_ function call that
 is simplified.
 
    Suppose you want ‘sin(a + b)’ to be expanded out not all the time,
 but only when algebraic simplifications are used to simplify the
 formula.  The variable ‘AlgSimpRules’ holds rules for this purpose.  The
 ‘a s’ command will apply ‘EvalRules’ and ‘AlgSimpRules’ to the formula,
 as well as all of its built-in simplifications.
 
    Most of the special limitations for ‘EvalRules’ don’t apply to
 ‘AlgSimpRules’.  Calc simply does an ‘a r AlgSimpRules’ command with an
 infinite repeat count as the first step of algebraic simplifications.
 It then applies its own built-in simplifications throughout the formula,
 and then repeats these two steps (along with applying the default
 simplifications) until no further changes are possible.
 
    There are also ‘ExtSimpRules’ and ‘UnitSimpRules’ variables that are
 used by ‘a e’ and ‘u s’, respectively; these commands also apply
 ‘EvalRules’ and ‘AlgSimpRules’.  The variable ‘IntegSimpRules’ contains
 simplification rules that are used only during integration by ‘a i’.