Documentation |
Defining equivalence rules for mathematical expressions
This functionality does not run in MATLAB.
Rule(pattern, replacement, <conditions>) Rule(procedure, <condProc>)
Rule is a data type. Each object of Rule – a rule – describes the equivalence between mathematical expressions. The arguments of a rule are two pattern expressions, that are equivalent, and optional some conditions for the validity of the equivalence.
Rule can be applied to any expression, and returns an expression equivalent to the input, or FAIL.
Additionally, a rule can consist of a procedure that returns an equivalent expression to a given expression or FAIL without using the pattern matcher.
Rules created with Rule are mainly used to build a rule base for the new Simplify. See the documentation of Simplify and Example 8 for a real application of Rule. Example 3 shows, how to implement rewriting rules via Rule.
All other examples are only given to explain the behavior of rules. In practice, single rules and their manual application is unusual.
There are two kinds of rules: Use the library pattern matcher to determine whether the rule is suitable, or use a user defined procedure to analyze a given expression and return an equivalent expression.
Rule(pattern, replacement, conditions) defines a rule that describes the equivalence of the expressions pattern and replacement.
When this rule is applied to a given expression ex, the pattern matcher is called with the arguments
match(ex, pattern, Cond = conditions)
and returns a set of replacements S:={var = ex_var, ...} for each variable var of pattern, and ex_var is the corresponding subexpression of ex (see match for detailed description).
In this case the result of the substitution subs(replacement, S) is returned as equivalent expression to ex.
The call to match can also return FAIL, when ex doesn't have the same structure as pattern. Then the return value of the rule application is FAIL, too.
See match for the description of valid conditions.
Alternatively, a rule can consist of a procedure that is called with a given expression, and must return an equivalent expression or FAIL. The "pattern matcher" is not called.
Rule(procedure, condProc) defines such a rule that returns an equivalent expression to any given input as return value of procedure or FAIL.
The optional condition condProc must be a procedure, too. This procedure is called before the procedure that produces equivalent expressions, with a given expression ex. When the call condProc(ex) returns TRUE, then the return value of the call procedure(ex) is returned as the result of the application of the rule, otherwise FAIL.
With a rule that consists of a procedure, several relations pattern <=> result can be expressed. This is mostly more efficient, than using match for each equivalence.
The first rule represents the simplification sin(X)^2 + cos(X)^2 = 1. The first argument of the rule is the expression sin(X)^2 + cos(X)^2. Each expression, which has the same structure, is found by match, and the second argument of the rule 1 is returned as result. There are no conditions for the validity of this equivalence. The identifiers used for defining the rule are write protected, because they have names beginning with #:
r := Rule(sin(`#X`)^2 + cos(`#X`)^2, 1): Rule::apply(r, sin(2*x - 1)^2 + cos(2*x - 1)^2)
The next expression doesn't have the right form, the application of the rule fails:
Rule::apply(r, sin(2*x - 1)^2 + cos(2*x + 1)^2)
The next rule represents the addition theorem sin(X + Y) = sin(X)*cos(Y) + sin(Y)*cos(X). The first argument of the rule is the expression sin(X + Y). Each expression that is a call to sin with a sum as argument, is identified by match, and the sum sin(X)*cos(Y) + sin(Y)*cos(X) is returned, where X and Y are replaced by the corresponding parts of the given expression. There are no conditions for the validity of this equivalence. The second part of the rule is prevented from evaluation with hold. The identifiers used for defining the rule are write protected, because they have names beginning with #:
r := Rule(sin(`#X` + `#Y`), hold(sin(`#X`)*cos(`#Y`) + sin(`#Y`)*cos(`#X`))): Rule::apply(r, sin(tan(x) + tan(y)))
The matcher identifies the difference of two expressions a and b as the sum a + -b, therefore also the following example works:
Rule::apply(r, sin(tan(x) - tan(y)))
We define two rules based on the trigonometric identies sin(x)^{2} = 1 - cos(x)^{2} and :
myrules := [Rule(sin(`#X`)^`#n`, (1 - cos(`#X`)^2)^(`#n`/2), {`#n` -> is(`#n`, Type::Even)}), Rule(tan(`#X`)^`#n`, (1/cos(`#X`)^2 - 1)^(`#n`/2), {`#n` -> is(`#n`, Type::Even)}) ]:
We wish to apply these rules as rewriting rules to various expressions. We forward Rule::apply to all subexpressions of an expression via misc::maprec. For convenience, an interface function myrewrite is implemented that calls misc::maprec:
myrewrite:= proc(f, rules) local _rewrite; begin _rewrite:= proc(x) local r, tmp; begin for r in rules do tmp:= Rule::apply(r, x); if tmp <> FAIL then x:= tmp; end; end; return(x) end; misc::maprec(f, TRUE = _rewrite); end:
Now we can call myrewrite(f, myrules) to apply the rewriting rules to an expression f:
f:= tan(x) + sin(2*x) - tan(y)^2*sin(x + 3)^6 + sin(x)^2 * tan(23)^4: myrewrite(f, myrules);
delete myrules, myrewrite, f:
Another rule represents the simplification sin(X) = 0, which is only true, when X is an integer multiple of PI:
r := Rule(sin(`#X`), 0, {`#X` -> is(`#X`/PI, Type::Integer)}): Rule::apply(r, sin(2*x*PI))
In the last call, the argument of sin doesn't have the necessary property, so the application of the rule fails.
After an assumption to x, the expression has the right form:
assume(x, Type::Integer): Rule::apply(r, sin(2*x*PI))
The next application of the rule checks a constant expression:
Rule::apply(r, sin(2*PI))
Why FAIL? The problem is, sin simplifies the constant input 2*PI to 0 itself, so the rule gets 0 as input. However, 0 doesn't have the necessary form, so FAIL is returned.
Another rule represents the simplification ln(neg^even*r) = even*ln(-neg) + ln(r), which is only true, when neg is negative and even is an even number:
r := Rule(ln(`#Neg`^`#Even`*`#X`), `#Even`*ln(-`#Neg`) + ln(`#X`), {`#Neg` -> is(`#Neg`, Type::Negative) = TRUE, `#Even` -> is(`#Even`, Type::Even) = TRUE}): delete e, n, x: Rule::apply(r, ln(n^e*x))
The rule application fails, because the variables doesn't have the necessary properties.
With an assumption n should be a negative variable and e should be even:
assume(n < 0): assume(e, Type::Even): Rule::apply(r, ln(n^e*x))
This rule represents the application of rewrite to an expression with the target exp, when the expression has subexpressions of type "sin" or "cos". The first argument of the rule is a procedure that calls rewrite with any expression and target exp and returns an expression equivalent to the input (because rewrite does it). The second argument is a procedure that checks, whether sin or cos is contained in the input expression:
r := Rule(X -> rewrite(X, exp), X -> has(X, sin) or has(X, cos)): Rule::apply(r, sin(2*x - 1)^2 + cos(2*x - 1)^2)
The next expression doesn't have sin or cos, so the application of the rule fails:
Rule::apply(r, tan(2*I*x))
This rule represents the application of rewrite to an expression with several targets. The first argument of the rule is a procedure that applies rewrite to the given expression, with a target depending on the input. This rule doesn't have a condition procedure:
rewProc := proc(ex) begin rewrite(ex, (if has(ex, exp) then tan elif has(ex, sin) or has(ex, cos) then cot elif has(ex, tan) or has(ex, cot) then sincos else exp end_if)) end_proc: r := Rule(rewProc): Rule::apply(r, exp(2*x))
The rule is applied again to the last result:
Rule::apply(r, %)
The last result should be simplified back to the first expression:
Simplify(%)
The new Simplify uses a rule base for applying a lot of rewriting rules for finding the simplest form of any given expression.
We want to rewrite only some powers and assume that all used variables are real (without using properties).
The list PowerRules consists of several rules. The procedure powerRules returns all this rules in a list.
Because of better readability, the names of the used identifiers are short and not protected names:
PowerRules := [Rule(A^m*A^n, hold(A^(m + n))), Rule(A^m/A^n, hold(A^(m - n))), Rule(A^n*B^n, hold((A*B)^n)), Rule(A^n/B^n, hold((A/B)^n)), Rule(A^n/B^n, hold((B/A)^-n)), Rule((A^m)^n, hold(A^(m*n)))]: powerRules := proc() begin PowerRules end_proc:
Simplify is called with the option SelectRules, and expects a procedure that returns a list of rules, applicable to a given expression. In this case, all of the rules are returned in every case.
Simplify applies all rules to a given expression and also to rewritten results, and tries to find the easiest form of the expression with respect to the default valuation procedure Simplify::complexity.
Because of the argument SelectRules = powerRules, only the given rules are used by Simplify:
Simplify(T^(1/2)*(R*T)^(-1/2), SelectRules = powerRules)
Other expressions cannot be simplified with the same rule base:
Simplify(sin(x)^2 + cos(x)^2, SelectRules = powerRules)
delete r, x, powerRules, PowerRules:
pattern |
A MuPAD^{®} expression; all identifiers are used as pattern variables for the pattern matcher |
replacement |
A MuPAD expression with the same identifiers, as pattern; the replacement expression should be protected from evaluation with the function hold |
conditions |
A set (of type DOM_SET) of procedures and expressions in the pattern variables, or the empty set (see match and option Cond) |
procedure |
A MuPAD procedure that is called with an expression and must return an equivalent expression or FAIL |
condProc |
A procedure that is called with an expression before procedure, and must return TRUE, when procedure should be called with the expression, otherwise FAIL is returned immediately |