Prolog library: tidy.pl
pereira at sri-unix.UUCP
pereira at sri-unix.UUCP
Mon Aug 15 16:02:29 AEST 1983
/* TIDY.PL : Lawrence's go fast, hacked up, version of Tidy
Lawrence
Updated: 9 August 82
This code implements a simple tidy/evaluator which tries to
maximise evaluation. It is supposed to be efficient so that there
is not much expense involved in using it as a first step to something
else. It traverses the input term in a single pass and does not
build any significant intermediate structures. It performs two
kinds of function:
1) Simple normalisation
All occurences of the operators / and - are removed in
favour of * and +.
Bags are formed for * and +. These bags are left recursive
trees built with the functors *(_,_) and +(_,_). All numbers
in the bag are evaluated and the result, if any, will always
be the top rightmost element of the bag. This will not be present
if it would be the unit element of the bag. Nested exponentiations
(of the form ((A^B)^C)^D etc) are collapsed into their base
to the power of a multiplication bag (ie A^(B*C*D)).
egs: * bags:
* *
/ \ / \
* c * 19
/ \ / \
a b * c
/ \
a b
(This form gives the advantages of bags, ie traversing, selecting
elements is easy and you know when you've finished, while leaving
the expression as a standard algebraic term. However this may not
be optimal for all purposes)
2) Evaluation
Numerical evaluation is performed where possible. Advantage is
taken of the associative properties of * and + (as the bags are
formed). Some simplifying rules are applied concerning zero and
unit elements of these bags. A small amount of distribution of
one operator over another is done where this will aid evaluation.
The total set of functions that tidy actually tidies are as follows:
*(_,_) /(_,_) +(_,_) -(_,_) -(_) ^(_,_) &(_,_) #(_,_)
Expressions involving other functions are handed to eval if all their
arguments have been evaluated (to numbers). This may result in these
simple expressions being reduced to numbers (which may then be involved
in further evaluations).
NB Tidy should be used as the expression evaluator instead of eval
(from LONG.PL) whenever the expressions are (or may be) partly
symbolic. It will call eval for all the numeric subexpressions
(using its normalisation tricks to try and maximise the the amount
of numeric evaluation). Over purely numeric expressions tidy will
be equivalent to eval.
** TODO
There is undoubtably scope for improvement. I can think of:
b) Improve the evaluation. Only self generated numbers
(such as *-1, ^-1) are distributed at the moment.
tidy does not handle such things already occuring
in the input. Eg (a*b^-1)^-1 is left as it is.
But it is arguable how much of this should go on.
(Try to produce bag with the minimum number of
recipricals/negatives?)
c) Various people seem to want sin/arcsin (etc) cancelling
added for good measure.
** SIGNIFICANT BUG
There is a logical bug in the current code. I have assumed
that when bag sweepers bottom out, the tidied version of
the bit at the bottom will not be the sort of thing that could
have been incorporated into the bag, otherwise we would
have spotted it and kept going. However, this is not true!
For example: a*b*( (c*d)+0 )
When the c*d comes back it is a mulbag and should be merged
with the upper a*b mulbag. The current code doesn't do this
because it I didn't realise that the lower bag could be hidden
as shown in the example. When I find time I intend to rewrite
the affected bits so that a proper bag merge is defined and
applied. The interesting bit is how to do this without
rebuilding one of the bags completely - ie by using partial
structures a la difference lists. But I am not sure how much
I care about such efficiency/complexity hacks any more. Trying
to be clever in the current code taught me what an effort it
is and how it makes things much more complicated. What I really
need is a practical program transformation system to
Unfold/Fold a multipass specification into a single pass hack
(Cf Burstall & Darlington, Feather etc). Please tell me when
you have such a thing ready for use.
** RECENT FIXES
[ 1981 ]
(4 March) Code for & and # added by Leon. This code leaves the
structure of these functions as it was (ie no bagging
is done), however simplifications are applied. Note
that this includes some identical element merging (but
this does not use associativity).
(10 March) Put some cuts in the code for & and #. Also renamed
the predicates and flattened out the structures into
separate arguments (These little things!).
Fixed problem stemming from assumption that arithmetic
always succeed. This was not true (power sometimes fails)
and resulted in tidy failing when an arithmetic operation
failed. The fix involved moving the cuts in the ...build
and ...fin routines which call arithmetic routines. No
assumption is now made about their success, even for
add and multiply which should always succeed.
Reordered the file a little and added some more
documentation.
(18 March) Added tidy clauses for logs.
Also normalize clause for sqrt (Leon)
(1 April) When given nested variables tidy produces mode errors
as these are not expected. Fixed this by adding checks
to appropriate places and a top level error message
if tidy fails.
(2 August) Added the BUG note above and a couple of other comments.
[ 1982 ]
(13 May 82) Tidied up the use of simplification a bit.
simplify_axiom's moved elsewhere.
(9 August 82) Added tidy_withvars for those seldom occasions where
the structure to be tidied is allowed to contain
variables (required by Bernard).
*/
/* EXPORT */
:- public tidy/2,
tidy_withvars/2,
simple/1.
/* IMPORT */
% This version designed for use with rational package (LONG)
% In particular it uses:
% number/1
% eval/2
% add/3
% multiply/3
% power/3
%
% Other imports (existence optional):
%
% simplify_axiom/2
/* MODES */
:- mode tidy(+,?),
tidy_withvars(?,?),
t_wvhack(+,+,-,-),
dotidy(+,-),
simple(?),
tidyall(+,+,+,+,-),
chknum(+,+,-),
try_eval(+,+,-),
apply_simplification(+,-),
mulbag(+),
plusbag(+),
expbag(+),
multidy(+,+,+,+,-,-),
m2tidy(+,+,-),
mulbuild(+,+,+,-,-),
plustidy(+,+,+,+,-,-),
p2tidy(+,+,-),
plusbuild(+,+,+,-,-),
exptidy(+,+,+,-,-,-),
distr_inverse(+,-),
andfin(+,+,-),
orfin(+,+,-),
mulfin(+,+,-),
plusfin(+,+,-),
expfin(+,+,+,-),
n_expfin(+,+,-),
tidy_varerr.
/* Implementation - some hints.
The top level is pretty straight forward, note that the invarient
that all solution arguments should be initially uninstantiated is
guaranteed by unifying the tidied expression with the output variable
right at the end of the tidy operation (tidy/2). This is to avoid any
bugs with output arg unification failures causing clauses with cuts
to be missed. [You should know what this means - if not then think
about it. Eg ?- foo(a,b). foo(a,c) :- !. foo(_,b). ]
The bag sweeping routines sweep across the term (left to right
for multiplication and division bags but right to left down
exponentiation chains) with a pair of accumulators being passed
across. Thus for multidy, Left and Ltag are the two incoming
accumulators and Right and Rtag are the resultant accumulators
after this bit of the tree has been looked at. (For exptidy the
names are the other way round). One accumulator is the bag of
symbolic structures (eg Left), the other is the bag of numeric
structures (eg Ltag). The numeric bag will always be just a number
since the arithmetic operations are done immediately whenever a number
is found. The symbolic bag may be empty, which is represented by the
Prolog atom 'empty'. Simultaneously with this accumulation there is a
process of pushing down a distibuted term (Distr). This is one of {1,-1}.
For multidy this is the power that each final element should be raised
to, and for plustidy this is the multiplier that each final element
should be multiplied by. This value is flipped back and forth as the
top down descent passes through divisions (multidy) and subtractions
(plustidy).
The bag sweepers bottom out when they hit the top of an expression
which they won't be able to incorporate. m2tidy and p2tidy see that
this expression gets (recursively) tidied, and then they incorporate
this with the distributed term (simplifying this away when it is 1).
There is a special case here when the expression bottomed out on involves
the same operation as will be used with the distributed term. In this case
the distibuted term can be shoved down into the bag below (by making it
the initial value of the numeric bag). Mulbuild and plusbuild add whatever
comes back from this to the accumulators. There is a decision here
concerning which bag (symbolic or numeric) it goes in. If evaluation works
on the incoming numeric bag (Ltag) and the element then this gives a new
value for this bag (Rtag). Otherwise it will be put into the symbolic bag.
There is a special case here for constructing with (previously) empty
bags. (Not wanting explicit tail markers, like [] in lists, makes things
slightly harder here).
Exptidy is simpler in that it only recursively sweeps one side. Note that
it uses multidy so that all the possible multiplication bags on the
right hand side of the exp chain will get packed into one. Since this a
right to left sweep down the chain there will be some reordering of
elements from the original. (However, they are changing from exp chains
to mul bags as well - so it's not important). The bottom left term in the
chain comes out as the base of course. Thus exptidy has this extra result.
The various ...fin routines take the final (accumulator) symbolic and
numeric bags and produce a final term. There are various things going
om here: Simplification rules get applied, empty symbolic bags disappear
and so forth. Mulfin and plusfin check to see if the symbolic bag is a
number, because I also want to be able to use them to glue arbitrary bits
together (current example: the use of mulfin in p2tidy). Expfin combines
bits of exponential stuff with bits of multiplication stuff (since the
base is to be raised to a mul bag). This makes it a bit more complicated.
In general one can make use of the zero element reduction to completely
elininate the need to look at certain bits of the tree. In this
implementation we would spot that the numeric bag (eg Ltag) had become
the zero element, and we could then return a result without looking any
further. This further optimisation is left as an exercise for the reader.
*/
% @@@ (Marker - Ie, easy to find string)
%% Top Level %%
% Tidy top level
tidy(X,Ans) :- dotidy(X,Y), !, Ans = Y.
tidy(X,X)
:- ttynl, display('** TIDY error: '), ttyprint(X),
ttynl, display(' (trace and continuing...)'), ttynl,
trace.
% New Thingy for when variables are allowed.
% Implemented very badly at the moment - should
% be improved (v slow, O(n^2)).
tidy_withvars(X,Ans) :-
variables(X,Vlist),
t_wvhack(Vlist,1,Subst1,Subst2),
subst(Subst1,X,X0),
tidy(X0,Ans0),
subst(Subst2,Ans0,Ans).
t_wvhack([],_,true,true).
t_wvhack([V],N,(V='$tidyv'(N)),('$tidyv'(N)=V)) :- !.
t_wvhack([V|Vrest],N,(V='$tidyv'(N))&SRest1,('$tidyv'(N)=V)&SRest2) :-
N1 is N+1,
t_wvhack(Vrest,N1,SRest1,SRest2).
% The general tidy routine
% Dispatches on special bag types (or logical ops)
% otherwise just tidies arguments recursively
% and then attempts evaluation.
dotidy(V,V) :- var(V), !, tidy_varerr.
dotidy(X,X) :- simple(X), !.
dotidy(Expr,Ans) :- % Top down application of simplification
apply_simplification(Expr,NewExpr),
!,
dotidy(NewExpr,Ans).
dotidy(A&B,Ans)
:- !,
dotidy(A,Ans1),
dotidy(B,Ans2),
andfin(Ans1,Ans2,Ans).
dotidy(A#B,Ans)
:- !,
dotidy(A,Ans1),
dotidy(B,Ans2),
orfin(Ans1,Ans2,Ans).
dotidy(X,Ans)
:- mulbag(X),
!,
multidy(X,1,empty,1,Right,Rtag),
mulfin(Rtag,Right,Ans).
dotidy(X,Ans)
:- plusbag(X),
!,
plustidy(X,1,empty,0,Right,Rtag),
plusfin(Rtag,Right,Ans).
dotidy(X,Ans)
:- expbag(X),
!,
exptidy(X,empty,1,Mult,Mtag,Base),
expfin(Mult,Mtag,Base,Ans).
dotidy(X,Newans)
:- functor(X,Fn,Arity),
functor(Ans,Fn,Arity),
tidyall(Arity,X,Ans,win,Flag),
try_eval(Flag,Ans,Newans).
% Simple things are always tidiest
simple(X) :- (atomic(X) ; number(X)), !.
% Tidy all the arguments of a term to
% give some new term. Keep track of whether
% or not all the tidied arguments are
% numbers.
tidyall(0,_,_,Flag,Flag) :- !.
tidyall(N,X,Ans,Flag,FinalFlag)
:- arg(N,X,Arg),
arg(N,Ans,Narg),
N1 is N-1,
dotidy(Arg,Narg),
chknum(Flag,Narg,Flag2),
tidyall(N1,X,Ans,Flag2,FinalFlag).
% Maintain number checking flag
chknum(lose,_,lose).
chknum(win,X,win) :- number(X), !.
chknum(win,_,lose).
% Try to evaluate non bag function
% Eval should just return the structure if it
% can't do the arithmetic
% We also try to simplify the result - this is the
% bottom up application of the simplify axioms. Note
% that this bottom up application is only being
% applied to non-bag functors (This should be fixed).
try_eval(lose,X,Y) :- apply_simplification(X,Y),!.
try_eval(lose,X,X).
try_eval(win,X,Y) :- eval(X,Y).
%% Simplification %%
% Just use the axioms if they exist.
apply_simplification(X,Y) :- simplify_axiom(X,Y), !.
%% Bag Sweeping %%
% Types of operation to which bag collecting
% is applicable.
mulbag(A*B).
mulbag(A/B).
plusbag(A+B).
plusbag(A-B).
plusbag(-(A)).
expbag(A^B).
% Collecting a multiplication bag together
multidy(V,_,_,_,_,_) :- var(V), !, tidy_varerr.
multidy(A*B,Distr,Left,Ltag,Right,Rtag)
:- !,
multidy(A,Distr,Left,Ltag,Q,Qtag),
multidy(B,Distr,Q,Qtag,Right,Rtag).
multidy(A/B,Distr,Left,Ltag,Right,Rtag)
:- !,
multidy(A,Distr,Left,Ltag,Q,Qtag),
distr_inverse(Distr,Idistr),
multidy(B,Idistr,Q,Qtag,Right,Rtag).
multidy(X,Distr,Left,Ltag,Right,Rtag)
:- m2tidy(X,Distr,Q),
mulbuild(Q,Left,Ltag,Right,Rtag).
m2tidy(E,Distr,Ans)
:- expbag(E),
!,
exptidy(E,empty,Distr,Result,Tag,Base),
expfin(Result,Tag,Base,Ans).
m2tidy(X,1,Ans)
:- !,
dotidy(X,Ans).
m2tidy(X,Distr,Ans)
:- dotidy(X,Result),
expfin(empty,Distr,Result,Ans).
% Build mul bags with various special cases
% handled.
mulbuild(N,Left,Ltag,Left,Rtag)
:- number(N),
multiply(N,Ltag,Rtag),
!.
mulbuild(X,empty,Ltag,X,Ltag) :- !.
mulbuild(X,Left,Ltag,Left*X,Ltag).
% Collecting a plus bag together
plustidy(V,_,_,_,_,_) :- var(V), !, tidy_varerr.
plustidy(A+B,Distr,Left,Ltag,Right,Rtag)
:- !,
plustidy(A,Distr,Left,Ltag,Q,Qtag),
plustidy(B,Distr,Q,Qtag,Right,Rtag).
plustidy(A-B,Distr,Left,Ltag,Right,Rtag)
:- !,
plustidy(A,Distr,Left,Ltag,Q,Qtag),
distr_inverse(Distr,Idistr),
plustidy(B,Idistr,Q,Qtag,Right,Rtag).
plustidy(-(A),Distr,Left,Ltag,Right,Rtag)
:- !,
distr_inverse(Distr,Idistr),
plustidy(A,Idistr,Left,Ltag,Right,Rtag).
plustidy(X,Distr,Left,Ltag,Right,Rtag)
:- p2tidy(X,Distr,Q),
plusbuild(Q,Left,Ltag,Right,Rtag).
p2tidy(M,Distr,Ans)
:- mulbag(M),
!,
multidy(M,1,empty,Distr,Result,Tag),
mulfin(Tag,Result,Ans).
p2tidy(X,1,Ans)
:- !,
dotidy(X,Ans).
p2tidy(X,Distr,Ans)
:- dotidy(X,Result),
mulfin(Distr,Result,Ans).
% Build plus bags with various special cases
% handled.
plusbuild(N,Left,Ltag,Left,Rtag)
:- number(N),
add(N,Ltag,Rtag),
!.
plusbuild(X,empty,Ltag,X,Ltag) :- !.
plusbuild(X,Left,Ltag,Left+X,Ltag).
% Collecting together exp bags
exptidy(V,_,_,_,_,_) :- var(V), !, tidy_varerr.
exptidy(A^B,Right,Rtag,Left,Ltag,Base)
:- !,
multidy(B,1,Right,Rtag,Q,Qtag),
exptidy(A,Q,Qtag,Left,Ltag,Base).
exptidy(X,Right,Rtag,Right,Rtag,Base)
:- dotidy(X,Base).
% Inverting factors being distributed
distr_inverse(1,-1).
distr_inverse(-1,1).
%% Finalising Structures %%
% Final AND building
andfin(false,X,false) :- !. % Zero element
andfin(true,X,X) :- !. % Unit element
andfin(X,false,false) :- !. % Zero element
andfin(X,true,X) :- !. % Unit element
andfin(X,X,X) :- !. % Merging of identical elements
andfin(X,Y,X&Y). % General case
% Final OR building
orfin(true,X,true) :- !. % Zero element
orfin(false,X,X) :- !. % Unit element
orfin(X,true,true) :- !. % Zero element
orfin(X,false,X) :- !. % Unit element
orfin(X,X,X) :- !. % Merging of identical elements
orfin(X,Y,X#Y). % General case
% Final multiplication building
mulfin(0,_,0) :- !. % Zero element
mulfin(N,empty,N) :- !. % Completely evaluated
mulfin(1,X,X) :- !. % Unit element
mulfin(N,N2,Ans) % Further evaluation possible
:- number(N2),
multiply(N,N2,Ans),
!.
mulfin(N,Bag*N2,Ans) % Caught a nested mult bag
:- number(N2),
multiply(N,N2,N3),
mulfin(N3,Bag,Ans),
!.
mulfin(N,X,X*N). % General case
% Final plus building
plusfin(N,empty,N) :- !. % Completely evaluated
plusfin(0,X,X) :- !. % Unit element
plusfin(N,N2,Ans) % Further evaluation possible
:- number(N2),
add(N,N2,Ans),
!.
plusfin(N,Bag+N2,Ans) % Caught a nested plus bag!
:- number(N2),
add(N,N2,N3),
plusfin(N3,Bag,Ans),
!.
plusfin(N,X,X+N). % General case
% Final exp building
expfin(_,0,_,1) :- !. % E^0 -> 1
expfin(_,_,0,0) :- !. % 0^X -> 0
expfin(empty,N,E,Ans) % special empty bag cases
:- !,
n_expfin(N,E,Ans).
expfin(Bag,1,E,E^Bag) :- !. % case with bag unit element
expfin(Bag,N,E,Z^Bag) % E^(Bag*N) -> (E^N)^Bag
:- number(E), % providing E^N will evaluate
power(E,N,Z),
!.
expfin(Bag,N,E,E^(Bag*N)). % General case
% special exp cases for when the symbolic
% bag is empty
n_expfin(1,E,E) :- !. % E^1 -> E
n_expfin(N,E,Ans) % E^N evaluates
:- number(E),
power(E,N,Ans),
!.
n_expfin(N,E,E^N). % General case for empty bag
%% Junk %%
% Produce an error message and fail (when
% variables are found).
% The failure will trip the Tidy top level
% error message (who throws a nl for us).
tidy_varerr :- ttynl, display('** Prolog variable in expression'), fail.
More information about the Comp.sources.unix
mailing list