The rewrite mechanism understands the algebraic properties of functions like `+' and `*'. In particular, pattern matching takes the associativity and commutativity of the following functions into account:
+ - * = != && || and or xor vint vunion vxor gcd lcm max min beta
For example, the rewrite rule:
a x + b x := (a + b) x
will match formulas of the form,
a x + b x, x a + x b, a x + x b, x a + b x
Rewrites also understand the relationship between the `+' and `-' operators. The above rewrite rule will also match the formulas,
a x - b x, x a - x b, a x - x b, x a - b x
by matching `b' in the pattern to `-b' from the formula.
Applied to a sum of many terms like `r + a x + s + b x + t', this pattern will check all pairs of terms for possible matches. The rewrite will take whichever suitable pair it discovers first.
In general, a pattern using an associative operator like `a + b' will try 2 n different ways to match a sum of n terms like `x + y + z - w'. First, `a' is matched against each of `x', `y', `z', and `-w' in turn, with `b' being matched to the remainders `y + z - w', `x + z - w', etc. If none of these succeed, then `b' is matched against each of the four terms with `a' matching the remainder. Half-and-half matches, like `(x + y) + (z - w)', are not tried.
Note that `*' is not commutative when applied to matrices, but
rewrite rules pretend that it is. If you type m v to enable
matrix mode (see section Matrix and Scalar Modes), rewrite rules will match `*'
literally, ignoring its usual commutativity property. (In the
current implementation, the associativity also vanishes--it is as
if the pattern had been enclosed in a plain
marker; see below.)
If you are applying rewrites to formulas with matrices, it's best to
enable matrix mode first to prevent algebraically incorrect rewrites
from occurring.
The pattern `-x' will actually match any expression. For example, the rule
f(-x) := -f(x)
will rewrite `f(a)' to `-f(-a)'. To avoid this, either use
a plain
marker as described below, or add a `negative(x)'
condition. The negative
function is true if its argument
"looks" negative, for example, because it is a negative number or
because it is a formula like `-x'. The new rule using this
condition is:
f(x) := -f(-x) :: negative(x) or, equivalently, f(-x) := -f(x) :: negative(-x)
In the same way, the pattern `x - y' will match the sum `a + b' by matching `y' to `-b'.
The pattern `a b' will also match the formula `x/y' if `y' is a number. Thus the rule `a x + b x := (a+b) x' will also convert `a x + x / 2' to `(a + 0.5) x' (or `(a + 1:2) x', depending on the current fraction mode).
Calc will not take other liberties with `*', `/', and `^'. For example, the pattern `f(a b)' will not match `f(x^2)', and `f(a + b)' will not match `f(2 x)', even though conceivably these patterns could match with `a = b = x'. Nor will `f(a b)' match `f(x / y)' if `y' is not a constant, even though it could be considered to match with `a = x' and `b = 1/y'. The reasons are partly for efficiency, and partly because while few mathematical operations are substantively different for addition and subtraction, often it is preferable to treat the cases of multiplication, division, and integer powers separately.
Even more subtle is the rule set
[ f(a) + f(b) := f(a + b), -f(a) := f(-a) ]
attempting to match `f(x) - f(y)'. You might think that Calc will view this subtraction as `f(x) + (-f(y))' and then apply the above two rules in turn, but actually this will not work because Calc only does this when considering rules for `+' (like the first rule in this set). So it will see first that `f(x) + (-f(y))' does not match `f(a) + f(b)' for any assignments of the meta-variables, and then it will see that `f(x) - f(y)' does not match `-f(a)' for any assignment of `a'. Because Calc tries only one rule at a time, it will not be able to rewrite `f(x) - f(y)' with this rule set. An explicit `f(a) - f(b)' rule will have to be added.
Another thing patterns will not do is break up complex numbers. The pattern `myconj(a + b i) := a - b i' will work for formulas involving the special constant `i' (such as `3 - 4 i'), but it will not match actual complex numbers like `(3, -4)'. A version of the above rule for complex numbers would be
myconj(a) := re(a) - im(a) (0,1) :: im(a) != 0
(Because the re
and im
functions understand the properties
of the special constant `i', this rule will also work for
`3 - 4 i'. In fact, this particular rule would probably be better
without the `im(a) != 0' condition, since if `im(a) = 0' the
righthand side of the rule will still give the correct answer for the
conjugate of a real number.)
It is also possible to specify optional arguments in patterns. The rule
opt(a) x + opt(b) (x^opt(c) + opt(d)) := f(a, b, c, d)
will match the formula
5 (x^2 - 4) + 3 x
in a fairly straightforward manner, but it will also match reduced formulas like
x + x^2, 2(x + 1) - x, x + x
producing, respectively,
f(1, 1, 2, 0), f(-1, 2, 1, 1), f(1, 1, 1, 0)
(The latter two formulas can be entered only if default simplifications have been turned off with m O.)
The default value for a term of a sum is zero. The default value for a part of a product, for a power, or for the denominator of a quotient, is one. Also, `-x' matches the pattern `opt(a) b' with `a = -1'.
In particular, the distributive-law rule can be refined to
opt(a) x + opt(b) x := (a + b) x
so that it will convert, e.g., `a x - x', to `(a - 1) x'.
The pattern `opt(a) + opt(b) x' matches almost any formulas which
are linear in `x'. You can also use the lin
and islin
functions with rewrite conditions to test for this; see section Logical Operations. These functions are not as convenient to use in rewrite
rules, but they recognize more kinds of formulas as linear:
`x/z' is considered linear with b = 1/z by lin
,
but it will not match the above pattern because that pattern calls
for a multiplication, not a division.
As another example, the obvious rule to replace `sin(x)^2 + cos(x)^2' by 1,
sin(x)^2 + cos(x)^2 := 1
misses many cases because the sine and cosine may both be multiplied by an equal factor. Here's a more successful rule:
opt(a) sin(x)^2 + opt(a) cos(x)^2 := a
Note that this rule will not match `sin(x)^2 + 6 cos(x)^2' because one a would have "matched" 1 while the other matched 6.
Calc automatically converts a rule like
f(x-1, x) := g(x)
into the form
f(temp, x) := g(x) :: temp = x-1
(where temp
stands for a new, invented meta-variable that
doesn't actually have a name). This modified rule will successfully
match `f(6, 7)', binding `temp' and `x' to 6 and 7,
respectively, then verifying that they differ by one even though
`6' does not superficially look like `x-1'.
However, Calc does not solve equations to interpret a rule. The following rule,
f(x-1, x+1) := g(x)
will not work. That is, it will match `f(a - 1 + b, a + 1 + b)' but not `f(6, 8)'. Calc always interprets at least one occurrence of a variable by literal matching. If the variable appears "isolated" then Calc is smart enough to use it for literal matching. But in this last example, Calc is forced to rewrite the rule to `f(x-1, temp) := g(x) :: temp = x+1' where the `x-1' term must correspond to an actual "something-minus-one" in the target formula.
A successful way to write this would be `f(x, x+2) := g(x+1)'.
You could make this resemble the original form more closely by using
let
notation, which is described in the next section:
f(xm1, x+1) := g(x) :: let(x := xm1+1)
Calc does this rewriting or "conditionalizing" for any sub-pattern which involves only the functions in the following list, operating only on constants and meta-variables which have already been matched elsewhere in the pattern. When matching a function call, Calc is careful to match arguments which are plain variables before arguments which are calls to any of the functions below, so that a pattern like `f(x-1, x)' can be conditionalized even though the isolated `x' comes after the `x-1'.
+ - * / \ % ^ abs sign round rounde roundu trunc floor ceil max min re im conj arg
You can suppress all of the special treatments described in this
section by surrounding a function call with a plain
marker.
This marker causes the function call which is its argument to be
matched literally, without regard to commutativity, associativity,
negation, or conditionalization. When you use plain
, the
"deep structure" of the formula being matched can show through.
For example,
plain(a - a b) := f(a, b)
will match only literal subtractions. However, the plain
marker does not affect its arguments' arguments. In this case,
commutativity and associativity is still considered while matching
the `a b' sub-pattern, so the whole pattern will match
`x - y x' as well as `x - x y'. We could go still
further and use
plain(a - plain(a b)) := f(a, b)
which would do a completely strict match for the pattern.
By contrast, the quote
marker means that not only the
function name but also the arguments must be literally the same.
The above pattern will match `x - x y' but
quote(a - a b) := f(a, b)
will match only the single formula `a - a b'. Also,
quote(a - quote(a b)) := f(a, b)
will match only `a - quote(a b)'---probably not the desired effect!
A certain amount of algebra is also done when substituting the meta-variables on the righthand side of a rule. For example, in the rule
a + f(b) := f(a + b)
matching `f(x) - y' would produce `f((-y) + x)' if
taken literally, but the rewrite mechanism will simplify the
righthand side to `f(x - y)' automatically. (Of course,
the default simplifications would do this anyway, so this
special simplification is only noticeable if you have turned the
default simplifications off.) This rewriting is done only when
a meta-variable expands to a "negative-looking" expression.
If this simplification is not desirable, you can use a plain
marker on the righthand side:
a + f(b) := f(plain(a + b))
In this example, we are still allowing the pattern-matcher to use all the algebra it can muster, but the righthand side will always simplify to a literal addition like `f((-y) + x)'.