caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* [Caml-list] How to simplify an arithmetic expression ?
@ 2011-10-02 11:51 Diego Olivier Fernandez Pons
  2011-10-02 14:08 ` Gabriel Scherer
                   ` (2 more replies)
  0 siblings, 3 replies; 10+ messages in thread
From: Diego Olivier Fernandez Pons @ 2011-10-02 11:51 UTC (permalink / raw)
  To: caml-list

[-- Attachment #1: Type: text/plain, Size: 2814 bytes --]

    OCaml list,

It's easy to encapsulate a couple of arithmetic simplifications into a
function that applies them bottom up to an expression represented as a tree

let rec simplify = function
    | Plus (e1, e2) ->
        match (simplify e1, simplify e2) with
             | (Constant 0, _) -> e2

With a couple well known tricks like pushing constants to the left side and
so on...

How can I however guarantee that
    1. My final expression reaches a kind of minimal normal form
    2. My set of simplifications is optimal in the sense it doesn't traverse
subtrees without need

Here is my current simplifier and I have no way of telling if it really
simplifies the expressions as much as possible and if it does useless passes
or not

type expr =
    | Constant of float
    | Plus of expr * expr
    | Minus of expr * expr
    | Times of expr * expr
    | Variable of string

let rec normalForm = function
    | Minus (e1, e2) -> normalForm (Plus (normalForm e1, Times (Constant
(-1.0), normalForm e2)))
    | Plus (e1, e2) ->
        begin
        match (normalForm e1, normalForm e2) with
            | (Constant a, Constant b) -> Constant (a +. b)
            | (Constant 0.0, e) -> normalForm e
            | (e, Constant b) -> normalForm (Plus (Constant b, normalForm
e))
            | (Constant a, Plus (Constant b, e)) -> Plus (Constant (a +. b),
normalForm e)
            | (Plus (Constant a, e1), Plus (Constant b, e2)) -> Plus
(Constant (a +. b), normalForm (Plus (normalForm e1, normalForm e2)))
            | (Variable a, Variable b) when a = b -> Times (Constant 2.0,
Variable a)
            | (Times (Constant n, Variable b), Variable a) when a = b ->
Times (Constant (n +. 1.0), Variable a)
            | (Variable a, Times (Constant n, Variable b)) when a = b ->
Times (Constant (n +. 1.0), Variable a)
            | (Times (Constant n, Variable a), Times (Constant m, Variable
b)) when a = b -> Times (Constant (n +. m), Variable a)
            | other -> Plus (e1, e2)
        end
    | Times (e1, e2) ->
        begin
        match (normalForm e1, normalForm e2) with
            | (Constant a, Constant b) -> Constant (a *. b)
            | (Constant 0.0, e) -> Constant 0.0
            | (Constant 1.0, e) -> e
            | (e, Constant b) -> normalForm (Times (Constant b, normalForm
e))
            | (Constant a, Times (Constant b, e)) -> Times (Constant (a *.
b), e)
            | other -> Times (e1, e2)
         end
    | x -> x

let (++) = fun x y -> Plus (x, y)
let ( ** ) = fun x y -> Times (x, y)
let ( --) = fun x y -> Minus (x, y)
let f = function fl -> Constant fl

let x = Variable "x"
let h = fun i -> f i ** x -- f i ** f i ** x ++ (f 3.0 ++ f i)
let e = List.fold_left (fun t i -> Plus (t, h i)) (f 0.0) [1.0; 2.0; 3.0;
4.0; 5.0]

normalForm e


        Diego Olivier

[-- Attachment #2: Type: text/html, Size: 3682 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] How to simplify an arithmetic expression ?
  2011-10-02 11:51 [Caml-list] How to simplify an arithmetic expression ? Diego Olivier Fernandez Pons
@ 2011-10-02 14:08 ` Gabriel Scherer
  2011-10-02 15:09   ` Ernesto Posse
  2011-10-02 16:56 ` Xavier Clerc
  2011-10-05 13:09 ` Goswin von Brederlow
  2 siblings, 1 reply; 10+ messages in thread
From: Gabriel Scherer @ 2011-10-02 14:08 UTC (permalink / raw)
  To: Diego Olivier Fernandez Pons; +Cc: caml-list

In my experience, the OCaml code doing recursive call and pattern
matching is a relatively bad way to reason about such rewrite systems.
Your questions are extremely pertinent, and relatively difficult to
answer in general.

For a start, I think your code indeed repeats useless traversals. This
can be seen syntactically by the nesting of two normalForm calls, such
as:

  | (e, Constant b) -> normalForm (Plus (Constant b, normalForm e))

You reduce e to a normal form, then repeat the reduction on some
expression containing e. The outer call will surely re-traverse (the
normal form of) e, which is useless here.

One approach I like for such simplifications is the "normalization by
evaluation" approach. The idea is to define a different representation
of normal forms of your system as "semantic values"; I mean a
representation that has a meaning in itself and not just "what's left
after this arbitrary transformation"; in your case, that could be
multivariate polynomials (defined as an independent datatype). Then
you express your normalization algorithm as an evaluation of your
expression into semantic values; you can reify them back into the
expression datatype, and if you did everything right you get normal
forms (in particular, normalizing a reified value will return exactly
this value). The main difficulty is to understand what are the normal
forms you're looking for; then the code is relatively easy and can be
made efficient.

I'm afraid my explanation may be a bit too abstract and high-level. Do
not hesitate to ask for more concrete details.

On Sun, Oct 2, 2011 at 1:51 PM, Diego Olivier Fernandez Pons
<dofp.ocaml@gmail.com> wrote:
>     OCaml list,
> It's easy to encapsulate a couple of arithmetic simplifications into a
> function that applies them bottom up to an expression represented as a tree
> let rec simplify = function
>     | Plus (e1, e2) ->
>         match (simplify e1, simplify e2) with
>              | (Constant 0, _) -> e2
> With a couple well known tricks like pushing constants to the left side and
> so on...
> How can I however guarantee that
>     1. My final expression reaches a kind of minimal normal form
>     2. My set of simplifications is optimal in the sense it doesn't traverse
> subtrees without need
> Here is my current simplifier and I have no way of telling if it really
> simplifies the expressions as much as possible and if it does useless passes
> or not
> type expr =
>     | Constant of float
>     | Plus of expr * expr
>     | Minus of expr * expr
>     | Times of expr * expr
>     | Variable of string
> let rec normalForm = function
>     | Minus (e1, e2) -> normalForm (Plus (normalForm e1, Times (Constant
> (-1.0), normalForm e2)))
>     | Plus (e1, e2) ->
>         begin
>         match (normalForm e1, normalForm e2) with
>             | (Constant a, Constant b) -> Constant (a +. b)
>             | (Constant 0.0, e) -> normalForm e
>             | (e, Constant b) -> normalForm (Plus (Constant b, normalForm
> e))
>             | (Constant a, Plus (Constant b, e)) -> Plus (Constant (a +. b),
> normalForm e)
>             | (Plus (Constant a, e1), Plus (Constant b, e2)) -> Plus
> (Constant (a +. b), normalForm (Plus (normalForm e1, normalForm e2)))
>             | (Variable a, Variable b) when a = b -> Times (Constant 2.0,
> Variable a)
>             | (Times (Constant n, Variable b), Variable a) when a = b ->
> Times (Constant (n +. 1.0), Variable a)
>             | (Variable a, Times (Constant n, Variable b)) when a = b ->
> Times (Constant (n +. 1.0), Variable a)
>             | (Times (Constant n, Variable a), Times (Constant m, Variable
> b)) when a = b -> Times (Constant (n +. m), Variable a)
>             | other -> Plus (e1, e2)
>         end
>     | Times (e1, e2) ->
>         begin
>         match (normalForm e1, normalForm e2) with
>             | (Constant a, Constant b) -> Constant (a *. b)
>             | (Constant 0.0, e) -> Constant 0.0
>             | (Constant 1.0, e) -> e
>             | (e, Constant b) -> normalForm (Times (Constant b, normalForm
> e))
>             | (Constant a, Times (Constant b, e)) -> Times (Constant (a *.
> b), e)
>             | other -> Times (e1, e2)
>          end
>     | x -> x
> let (++) = fun x y -> Plus (x, y)
> let ( ** ) = fun x y -> Times (x, y)
> let ( --) = fun x y -> Minus (x, y)
> let f = function fl -> Constant fl
> let x = Variable "x"
> let h = fun i -> f i ** x -- f i ** f i ** x ++ (f 3.0 ++ f i)
> let e = List.fold_left (fun t i -> Plus (t, h i)) (f 0.0) [1.0; 2.0; 3.0;
> 4.0; 5.0]
> normalForm e
>
>         Diego Olivier
>


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] How to simplify an arithmetic expression ?
  2011-10-02 14:08 ` Gabriel Scherer
@ 2011-10-02 15:09   ` Ernesto Posse
  2011-10-02 16:32     ` Xavier Leroy
  0 siblings, 1 reply; 10+ messages in thread
From: Ernesto Posse @ 2011-10-02 15:09 UTC (permalink / raw)
  To: caml-list

> On Sun, Oct 2, 2011 at 1:51 PM, Diego Olivier Fernandez Pons
> <dofp.ocaml@gmail.com> wrote:
>>     OCaml list,
>> It's easy to encapsulate a couple of arithmetic simplifications into a
>> function that applies them bottom up to an expression represented as a tree
>> let rec simplify = function
>>     | Plus (e1, e2) ->
>>         match (simplify e1, simplify e2) with
>>              | (Constant 0, _) -> e2
>> With a couple well known tricks like pushing constants to the left side and
>> so on...
>> How can I however guarantee that
>>     1. My final expression reaches a kind of minimal normal form
>>     2. My set of simplifications is optimal in the sense it doesn't traverse
>> subtrees without need
>> Here is my current simplifier and I have no way of telling if it really
>> simplifies the expressions as much as possible and if it does useless passes
>> or not
>> type expr =
>>     | Constant of float
>>     | Plus of expr * expr
>>     | Minus of expr * expr
>>     | Times of expr * expr
>>     | Variable of string
>> let rec normalForm = function
>>     | Minus (e1, e2) -> normalForm (Plus (normalForm e1, Times (Constant
>> (-1.0), normalForm e2)))
>>     | Plus (e1, e2) ->
>>         begin
>>         match (normalForm e1, normalForm e2) with
>>             | (Constant a, Constant b) -> Constant (a +. b)
>>             | (Constant 0.0, e) -> normalForm e
>>             | (e, Constant b) -> normalForm (Plus (Constant b, normalForm
>> e))
>>             | (Constant a, Plus (Constant b, e)) -> Plus (Constant (a +. b),
>> normalForm e)
>>             | (Plus (Constant a, e1), Plus (Constant b, e2)) -> Plus
>> (Constant (a +. b), normalForm (Plus (normalForm e1, normalForm e2)))
>>             | (Variable a, Variable b) when a = b -> Times (Constant 2.0,
>> Variable a)
>>             | (Times (Constant n, Variable b), Variable a) when a = b ->
>> Times (Constant (n +. 1.0), Variable a)
>>             | (Variable a, Times (Constant n, Variable b)) when a = b ->
>> Times (Constant (n +. 1.0), Variable a)
>>             | (Times (Constant n, Variable a), Times (Constant m, Variable
>> b)) when a = b -> Times (Constant (n +. m), Variable a)
>>             | other -> Plus (e1, e2)
>>         end
>>     | Times (e1, e2) ->
>>         begin
>>         match (normalForm e1, normalForm e2) with
>>             | (Constant a, Constant b) -> Constant (a *. b)
>>             | (Constant 0.0, e) -> Constant 0.0
>>             | (Constant 1.0, e) -> e
>>             | (e, Constant b) -> normalForm (Times (Constant b, normalForm
>> e))
>>             | (Constant a, Times (Constant b, e)) -> Times (Constant (a *.
>> b), e)
>>             | other -> Times (e1, e2)
>>          end
>>     | x -> x
>> let (++) = fun x y -> Plus (x, y)
>> let ( ** ) = fun x y -> Times (x, y)
>> let ( --) = fun x y -> Minus (x, y)
>> let f = function fl -> Constant fl
>> let x = Variable "x"
>> let h = fun i -> f i ** x -- f i ** f i ** x ++ (f 3.0 ++ f i)
>> let e = List.fold_left (fun t i -> Plus (t, h i)) (f 0.0) [1.0; 2.0; 3.0;
>> 4.0; 5.0]
>> normalForm e
>>
>>         Diego Olivier
>>


On Sun, Oct 2, 2011 at 10:08 AM, Gabriel Scherer
<gabriel.scherer@gmail.com> wrote:
> In my experience, the OCaml code doing recursive call and pattern
> matching is a relatively bad way to reason about such rewrite systems.

Why? In general, reduction in (pure) functional languages is
rewriting. Diego's function does not seem to have side-effects, so why
would this be a bad way?

As I understand it, this style is very much one of the reasons why ML
was designed this way. If we are talking about optimization, then yes,
there may be better ways of doing this, but if we are talking about
correctness, readability, and reasoning, then I don't see why this
style would be considered bad.

> Your questions are extremely pertinent, and relatively difficult to
> answer in general.
>
> For a start, I think your code indeed repeats useless traversals. This
> can be seen syntactically by the nesting of two normalForm calls, such
> as:
>
>  | (e, Constant b) -> normalForm (Plus (Constant b, normalForm e))
>
> You reduce e to a normal form, then repeat the reduction on some
> expression containing e. The outer call will surely re-traverse (the
> normal form of) e, which is useless here.

Another problem might be that the "rewriting" rules do not seem to be
exhaustive although I haven't checked very carefully.


> One approach I like for such simplifications is the "normalization by
> evaluation" approach. The idea is to define a different representation
> of normal forms of your system as "semantic values"; I mean a
> representation that has a meaning in itself and not just "what's left
> after this arbitrary transformation"; in your case, that could be
> multivariate polynomials (defined as an independent datatype). Then
> you express your normalization algorithm as an evaluation of your
> expression into semantic values; you can reify them back into the
> expression datatype, and if you did everything right you get normal
> forms (in particular, normalizing a reified value will return exactly
> this value). The main difficulty is to understand what are the normal
> forms you're looking for; then the code is relatively easy and can be
> made efficient.

I wonder if such an approach (normalization by evaluation) might be a
bit of an overkill. In general, whenever you have an algebraic
structure with normal forms, normal forms can be obtained by
equational reasoning: using the algebra's laws as rewriting rules. So
in principle at least, shouldn't Diego's problem be solvable this way,
without the need for a special semantic domain for normal forms? When
would the normalization by evaluation approach be preferable? Can you
show a small example?




-- 
Ernesto Posse

Modelling and Analysis in Software Engineering
School of Computing
Queen's University - Kingston, Ontario, Canada


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] How to simplify an arithmetic expression ?
  2011-10-02 15:09   ` Ernesto Posse
@ 2011-10-02 16:32     ` Xavier Leroy
  2011-10-02 16:48       ` Gabriel Scherer
  0 siblings, 1 reply; 10+ messages in thread
From: Xavier Leroy @ 2011-10-02 16:32 UTC (permalink / raw)
  To: caml-list

On Sun, Oct 2, 2011 at 10:08 AM, Gabriel Scherer wrote:
> One approach I like for such simplifications is the "normalization by
> evaluation" approach.

NBE is neat, but I'm skeptical that it will work out of the box here:
if you apply NBE to a standard evaluator for arithmetic expressions,
it's not going to take advantage of associativity and distributivity
the way Diego wants.

On 10/02/2011 05:09 PM, Ernesto Posse wrote:
> In general, whenever you have an algebraic
> structure with normal forms, normal forms can be obtained by
> equational reasoning: using the algebra's laws as rewriting rules. 

Yes, writing down a system of equations is the first thing to do.
But, to obtain a normalization procedure, you need to orient those
rules and complete them (in the sense of Knuth-Bendix completion) with
extra rules to derive a confluent, terminating rewriting system.

Here is a good, down-to-earth introduction to Knuth-Bendix completion:

A.J.J. Dick, "An Introduction to Knuth-Bendix Completion"
http://comjnl.oxfordjournals.org/content/34/1/2.full.pdf

And here is a solid textbook on rewriting systems:

Franz Baader and Tobias Nipkow. "Term Rewriting and All That".
http://www4.in.tum.de/~nipkow/TRaAT/

Hope this helps,

- Xavier Leroy

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] How to simplify an arithmetic expression ?
  2011-10-02 16:32     ` Xavier Leroy
@ 2011-10-02 16:48       ` Gabriel Scherer
  2011-10-02 17:07         ` Gabriel Scherer
  0 siblings, 1 reply; 10+ messages in thread
From: Gabriel Scherer @ 2011-10-02 16:48 UTC (permalink / raw)
  To: Xavier Leroy; +Cc: caml-list

On Sun, Oct 2, 2011 at 6:32 PM, Xavier Leroy <Xavier.Leroy@inria.fr> wrote:
> NBE is neat, but I'm skeptical that it will work out of the box here:
> if you apply NBE to a standard evaluator for arithmetic expressions,
> it's not going to take advantage of associativity and distributivity
> the way Diego wants.

My idea was to use a semantic domain which is a quotient over those
associativity and distributivity laws. If you choose a canonical
representation of multivariate polynomials (sums of product of some
variables and a coefficient) and compute on them, you get
associativity and distributivity for free. But indeed, the rewriting
that happens implicitly may not implement the exact same rules Diego
had in mind. In particular, canonical polynomial representations may
be much bigger than the input expression, due to applying
distributivity systematically.

Not all rewrite systems are suitable for NbE. Most reasonable semantic
domains probably induce very strong rewrite rules, or none at all. For
the middle ground, finding a suitable semantic domain is probably just
as hard as completing the rewrite system as you suggest.

> On 10/02/2011 05:09 PM, Ernesto Posse wrote:
> If we are talking about optimization, then yes,
> there may be better ways of doing this, but if we are talking about
> correctness, readability, and reasoning, then I don't see why this
> style would be considered bad.

"Optimization" is important here. By calling the deep-recursive
transformation twice in a case, you get an exponential algorithm which
can be so slow and memory-hungry that impracticality borders
incorrectness.


> On 10/02/2011 05:09 PM, Ernesto Posse wrote:
> So in principle at least, shouldn't Diego's problem be solvable this way,
> without the need for a special semantic domain for normal forms? When
> would the normalization by evaluation approach be preferable? Can you
> show a small example?

Yes, implementing the rewrite system directly is possible and probably
a more precise way to get a result (in particular if you already know
the rewrite rules you wish to have, but not the semantic domain their
normal forms correspond to). I'm not sure it's simpler.

Below is a quick tentative implementation of NbE, on a slightly
restricted expression type (I removed the not-so-interesting Minus
nodes).
You can normalize an expression `e` with `reify (eval e)`.
`show (eval e)` is a representation whose toplevel printing is more
redable, which helps testing.

  type var = string
  type expr =
      | Constant of float
      | Plus of expr * expr
      | Times of expr * expr
      | Variable of var

  (* multivariate polynomials: maps from multiset of variables to coefficients
     2*X²*Y + 3*X + 1 => {["X","X","Y"]↦2, ["X"]↦3, ∅↦1}
  *)
  module MultiVar = struct
    (* multisets naively implemented as sorted lists *)
    type t = var list
    let compare = Pervasives.compare
  end
  module Poly = Map.Make(MultiVar)
  type value = float Poly.t

  let sort vars = List.sort String.compare vars

  let constant x = Poly.singleton [] x
  let variable v = Poly.singleton [v] 1.

  (* BatOption.default *)
  let default d = function
    | None -> d
    | Some x -> x

  let plus p1 p2 =
    let add_opt _vars c1 c2 =
      Some (default 0. c1 +. default 0. c2) in
    Poly.merge add_opt p1 p2

  let times p1 p2 = (* naive implementation *)
    let p2_times_monome vars coeff acc =
      let add_monome v c acc =
        let monome = Poly.singleton (sort (vars @ v)) (c *. coeff) in
        plus monome acc in
      Poly.fold add_monome p2 acc in
    Poly.fold p2_times_monome p1 Poly.empty

  (* evaluate expressions into values *)
  let rec eval = function
    | Constant x -> constant x
    | Variable v -> variable v
    | Plus(e1, e2) -> plus (eval e1) (eval e2)
    | Times(e1, e2) -> times (eval e1) (eval e2)

  let show p = Poly.fold (fun vars coeff acc -> (vars, coeff)::acc) p []

  (* translate values back into expressions *)
  let reify p =
    let monome vars coeff =
      let times_var acc var = Times (acc, Variable var) in
      List.fold_left times_var (Constant coeff) vars in
    (* extract the first elem before summing,
       to avoid a dummy 0. initial accumulator *)
    if Poly.is_empty p then Constant 0.
    else
      let (v,c) = Poly.min_binding p in
      let p' = Poly.remove v p in
      Poly.fold (fun v c acc -> Plus(monome v c, acc)) p' (monome v c)



> On 10/02/2011 05:09 PM, Ernesto Posse wrote:
>> In general, whenever you have an algebraic
>> structure with normal forms, normal forms can be obtained by
>> equational reasoning: using the algebra's laws as rewriting rules.
>
> Yes, writing down a system of equations is the first thing to do.
> But, to obtain a normalization procedure, you need to orient those
> rules and complete them (in the sense of Knuth-Bendix completion) with
> extra rules to derive a confluent, terminating rewriting system.
>
> Here is a good, down-to-earth introduction to Knuth-Bendix completion:
>
> A.J.J. Dick, "An Introduction to Knuth-Bendix Completion"
> http://comjnl.oxfordjournals.org/content/34/1/2.full.pdf
>
> And here is a solid textbook on rewriting systems:
>
> Franz Baader and Tobias Nipkow. "Term Rewriting and All That".
> http://www4.in.tum.de/~nipkow/TRaAT/
>
> Hope this helps,
>
> - Xavier Leroy
>
> --
> Caml-list mailing list.  Subscription management and archives:
> https://sympa-roc.inria.fr/wws/info/caml-list
> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
> Bug reports: http://caml.inria.fr/bin/caml-bugs
>
>


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] How to simplify an arithmetic expression ?
  2011-10-02 11:51 [Caml-list] How to simplify an arithmetic expression ? Diego Olivier Fernandez Pons
  2011-10-02 14:08 ` Gabriel Scherer
@ 2011-10-02 16:56 ` Xavier Clerc
  2011-10-05 13:09 ` Goswin von Brederlow
  2 siblings, 0 replies; 10+ messages in thread
From: Xavier Clerc @ 2011-10-02 16:56 UTC (permalink / raw)
  To: Diego Olivier Fernandez Pons; +Cc: caml-list

[-- Attachment #1: Type: text/plain, Size: 903 bytes --]

----- Mail original -----
> De: "Diego Olivier Fernandez Pons" <dofp.ocaml@gmail.com>
> À: "caml-list" <caml-list@inria.fr>
> Envoyé: Dimanche 2 Octobre 2011 13:51:13
> Objet: [Caml-list] How to simplify an arithmetic expression ?
> OCaml list,
> It's easy to encapsulate a couple of arithmetic simplifications into a
> function that applies them bottom up to an expression represented as a
> tree
Not absolutely sure it will fit your needs, but it will at least advertise a nice tool... The "moca" preprocessor allows you to add properties to constructors of a sum type (e. g. associativity, commutativity, etc.). Example from the webpage (*): type t = private      | Zero      | One      | Opp of t      | Add of t * t        begin          associative          commutative          neutral (Zero)          opposite (Opp)        end   ;; Regards, Xavier Clerc (*) http://moca.inria.fr/eng.htm

[-- Attachment #2: Type: text/html, Size: 1331 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] How to simplify an arithmetic expression ?
  2011-10-02 16:48       ` Gabriel Scherer
@ 2011-10-02 17:07         ` Gabriel Scherer
  2011-10-05 13:17           ` Goswin von Brederlow
  0 siblings, 1 reply; 10+ messages in thread
From: Gabriel Scherer @ 2011-10-02 17:07 UTC (permalink / raw)
  To: caml-list

> Below is a quick tentative implementation of NbE, on a slightly
> restricted expression type (I removed the not-so-interesting Minus
> nodes).

Sorry, I forgot to give a small example of what the implementation
does. Really the obvious thing, but it may not be so obvious just
looking at the code.
Here is an example of (X*2+Y)*(3+(Y*2+Z)) being 'rewritten' to
(1*Y)*Z+((2*Y)*Y+(3*Y+((2*X)*Z+((4*X)*Y+6*X)))).
In a more readable way, it computes that (2X+Y)*(3+2Y+Z) equals
YZ+2Y²+3Y+2XZ+4XY+6X.
(The ordering of the result monomials is arbitrary, as implemented by
Pervasives.compare, but a more human-natural order could be
implemented.)

  # test1;;
  - : expr = Plus (Times (Variable "X", Constant 2.), Variable "Y")
  # test2;;
  - : expr =
  Plus (Constant 3., Plus (Times (Variable "Y", Constant 2.), Variable "Z"))
  # show (eval test1);;
  - : (Poly.key * float) list = [(["Y"], 1.); (["X"], 2.)]
  # show (eval test2);;
  - : (Poly.key * float) list = [(["Z"], 1.); (["Y"], 2.); ([], 3.)]
  # show (eval (Times(test1,test2)));;
  - : (Poly.key * float) list =
  [(["Y"; "Z"], 1.); (["Y"; "Y"], 2.); (["Y"], 3.); (["X"; "Z"], 2.);
   (["X"; "Y"], 4.); (["X"], 6.)]
  # reify (eval (Times(test1,test2)));;
  - : expr =
  Plus (Times (Times (Constant 1., Variable "Y"), Variable "Z"),
   Plus (Times (Times (Constant 2., Variable "Y"), Variable "Y"),
    Plus (Times (Constant 3., Variable "Y"),
     Plus (Times (Times (Constant 2., Variable "X"), Variable "Z"),
      Plus (Times (Times (Constant 4., Variable "X"), Variable "Y"),
       Times (Constant 6., Variable "X"))))))


On Sun, Oct 2, 2011 at 6:48 PM, Gabriel Scherer
<gabriel.scherer@gmail.com> wrote:
> On Sun, Oct 2, 2011 at 6:32 PM, Xavier Leroy <Xavier.Leroy@inria.fr> wrote:
>> NBE is neat, but I'm skeptical that it will work out of the box here:
>> if you apply NBE to a standard evaluator for arithmetic expressions,
>> it's not going to take advantage of associativity and distributivity
>> the way Diego wants.
>
> My idea was to use a semantic domain which is a quotient over those
> associativity and distributivity laws. If you choose a canonical
> representation of multivariate polynomials (sums of product of some
> variables and a coefficient) and compute on them, you get
> associativity and distributivity for free. But indeed, the rewriting
> that happens implicitly may not implement the exact same rules Diego
> had in mind. In particular, canonical polynomial representations may
> be much bigger than the input expression, due to applying
> distributivity systematically.
>
> Not all rewrite systems are suitable for NbE. Most reasonable semantic
> domains probably induce very strong rewrite rules, or none at all. For
> the middle ground, finding a suitable semantic domain is probably just
> as hard as completing the rewrite system as you suggest.
>
>> On 10/02/2011 05:09 PM, Ernesto Posse wrote:
>> If we are talking about optimization, then yes,
>> there may be better ways of doing this, but if we are talking about
>> correctness, readability, and reasoning, then I don't see why this
>> style would be considered bad.
>
> "Optimization" is important here. By calling the deep-recursive
> transformation twice in a case, you get an exponential algorithm which
> can be so slow and memory-hungry that impracticality borders
> incorrectness.
>
>
>> On 10/02/2011 05:09 PM, Ernesto Posse wrote:
>> So in principle at least, shouldn't Diego's problem be solvable this way,
>> without the need for a special semantic domain for normal forms? When
>> would the normalization by evaluation approach be preferable? Can you
>> show a small example?
>
> Yes, implementing the rewrite system directly is possible and probably
> a more precise way to get a result (in particular if you already know
> the rewrite rules you wish to have, but not the semantic domain their
> normal forms correspond to). I'm not sure it's simpler.
>
> Below is a quick tentative implementation of NbE, on a slightly
> restricted expression type (I removed the not-so-interesting Minus
> nodes).
> You can normalize an expression `e` with `reify (eval e)`.
> `show (eval e)` is a representation whose toplevel printing is more
> redable, which helps testing.
>
>  type var = string
>  type expr =
>      | Constant of float
>      | Plus of expr * expr
>      | Times of expr * expr
>      | Variable of var
>
>  (* multivariate polynomials: maps from multiset of variables to coefficients
>     2*X²*Y + 3*X + 1 => {["X","X","Y"]↦2, ["X"]↦3, ∅↦1}
>  *)
>  module MultiVar = struct
>    (* multisets naively implemented as sorted lists *)
>    type t = var list
>    let compare = Pervasives.compare
>  end
>  module Poly = Map.Make(MultiVar)
>  type value = float Poly.t
>
>  let sort vars = List.sort String.compare vars
>
>  let constant x = Poly.singleton [] x
>  let variable v = Poly.singleton [v] 1.
>
>  (* BatOption.default *)
>  let default d = function
>    | None -> d
>    | Some x -> x
>
>  let plus p1 p2 =
>    let add_opt _vars c1 c2 =
>      Some (default 0. c1 +. default 0. c2) in
>    Poly.merge add_opt p1 p2
>
>  let times p1 p2 = (* naive implementation *)
>    let p2_times_monome vars coeff acc =
>      let add_monome v c acc =
>        let monome = Poly.singleton (sort (vars @ v)) (c *. coeff) in
>        plus monome acc in
>      Poly.fold add_monome p2 acc in
>    Poly.fold p2_times_monome p1 Poly.empty
>
>  (* evaluate expressions into values *)
>  let rec eval = function
>    | Constant x -> constant x
>    | Variable v -> variable v
>    | Plus(e1, e2) -> plus (eval e1) (eval e2)
>    | Times(e1, e2) -> times (eval e1) (eval e2)
>
>  let show p = Poly.fold (fun vars coeff acc -> (vars, coeff)::acc) p []
>
>  (* translate values back into expressions *)
>  let reify p =
>    let monome vars coeff =
>      let times_var acc var = Times (acc, Variable var) in
>      List.fold_left times_var (Constant coeff) vars in
>    (* extract the first elem before summing,
>       to avoid a dummy 0. initial accumulator *)
>    if Poly.is_empty p then Constant 0.
>    else
>      let (v,c) = Poly.min_binding p in
>      let p' = Poly.remove v p in
>      Poly.fold (fun v c acc -> Plus(monome v c, acc)) p' (monome v c)
>
>
>
>> On 10/02/2011 05:09 PM, Ernesto Posse wrote:
>>> In general, whenever you have an algebraic
>>> structure with normal forms, normal forms can be obtained by
>>> equational reasoning: using the algebra's laws as rewriting rules.
>>
>> Yes, writing down a system of equations is the first thing to do.
>> But, to obtain a normalization procedure, you need to orient those
>> rules and complete them (in the sense of Knuth-Bendix completion) with
>> extra rules to derive a confluent, terminating rewriting system.
>>
>> Here is a good, down-to-earth introduction to Knuth-Bendix completion:
>>
>> A.J.J. Dick, "An Introduction to Knuth-Bendix Completion"
>> http://comjnl.oxfordjournals.org/content/34/1/2.full.pdf
>>
>> And here is a solid textbook on rewriting systems:
>>
>> Franz Baader and Tobias Nipkow. "Term Rewriting and All That".
>> http://www4.in.tum.de/~nipkow/TRaAT/
>>
>> Hope this helps,
>>
>> - Xavier Leroy
>>
>> --
>> Caml-list mailing list.  Subscription management and archives:
>> https://sympa-roc.inria.fr/wws/info/caml-list
>> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
>> Bug reports: http://caml.inria.fr/bin/caml-bugs
>>
>>
>


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] How to simplify an arithmetic expression ?
  2011-10-02 11:51 [Caml-list] How to simplify an arithmetic expression ? Diego Olivier Fernandez Pons
  2011-10-02 14:08 ` Gabriel Scherer
  2011-10-02 16:56 ` Xavier Clerc
@ 2011-10-05 13:09 ` Goswin von Brederlow
  2 siblings, 0 replies; 10+ messages in thread
From: Goswin von Brederlow @ 2011-10-05 13:09 UTC (permalink / raw)
  To: Diego Olivier Fernandez Pons; +Cc: caml-list

Diego Olivier Fernandez Pons <dofp.ocaml@gmail.com> writes:

>     OCaml list,
>
> It's easy to encapsulate a couple of arithmetic simplifications into a function
> that applies them bottom up to an expression represented as a tree
>
> let rec simplify = function
>     | Plus (e1, e2) ->
>         match (simplify e1, simplify e2) with
>              | (Constant 0, _) -> e2 
>
> With a couple well known tricks like pushing constants to the left side and so
> on...
>
> How can I however guarantee that
>     1. My final expression reaches a kind of minimal normal form

First you have to define what you mean by that.

For a (good) compiler a minimal normal form would be one that uses the
least number of instructions (optimize size) or least number of cpu
cycles (optimize speed) to execute. And that is quite a complex problem.

One simplification that will probably destroy any idea of a simple
minimal normal form is probably eliminating common subexpressions.
Some optimizations will require complex heuristics or much larger
patterns.

>     2. My set of simplifications is optimal in the sense it doesn't traverse
> subtrees without need

That you have to think through. I don't think you can get the compiler
to tell you when a traversal isn't needed. You could annotate your
syntax tree with traversal counts and check if the count goes up too
much somewhere. If you do extra unneeded traversals then I expect them
to become exponential quite quickly so it should be easy to spot by
running a bunch of expressions through your code.

>
> Here is my current simplifier and I have no way of telling if it really
> simplifies the expressions as much as possible and if it does useless passes or
> not

It doesn't.

1 - 1 -> 0
a + b + 2 * (a + b) -> 3 * a + 3 * b or 3 * (a + b)?
(a + 1) * (a - 1) -> a * a - 1
9 * a -> 8 * a + a?

The last would be something a compiler does to optimize speed since a
muliplication by a power of 2 is a shift and shift + add is faster than
multiplication.

MfG
        Goswin

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] How to simplify an arithmetic expression ?
  2011-10-02 17:07         ` Gabriel Scherer
@ 2011-10-05 13:17           ` Goswin von Brederlow
  2011-10-05 21:31             ` Gabriel Scherer
  0 siblings, 1 reply; 10+ messages in thread
From: Goswin von Brederlow @ 2011-10-05 13:17 UTC (permalink / raw)
  To: Gabriel Scherer; +Cc: caml-list

Gabriel Scherer <gabriel.scherer@gmail.com> writes:

>> Below is a quick tentative implementation of NbE, on a slightly
>> restricted expression type (I removed the not-so-interesting Minus
>> nodes).
>
> Sorry, I forgot to give a small example of what the implementation
> does. Really the obvious thing, but it may not be so obvious just
> looking at the code.
> Here is an example of (X*2+Y)*(3+(Y*2+Z)) being 'rewritten' to
> (1*Y)*Z+((2*Y)*Y+(3*Y+((2*X)*Z+((4*X)*Y+6*X)))).

(1*Y)? That certainly isn't optimal.

MfG
        Goswin

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] How to simplify an arithmetic expression ?
  2011-10-05 13:17           ` Goswin von Brederlow
@ 2011-10-05 21:31             ` Gabriel Scherer
  0 siblings, 0 replies; 10+ messages in thread
From: Gabriel Scherer @ 2011-10-05 21:31 UTC (permalink / raw)
  To: Goswin von Brederlow; +Cc: caml-list

On Wed, Oct 5, 2011 at 3:17 PM, Goswin von Brederlow <goswin-v-b@web.de> wrote:
> (1*Y)? That certainly isn't optimal.

The inefficiency is in the code translating the semantic value back
into the expression type, not in the evaluation code.
Feel free to add the few lines needed to spot that corner case and
behave accordingly, as I did for the initialization of the sum (to
avoid "+ 0" everywhere).
Note that this doesn't stop the simplification to be involutive, so
still a valid simplification/normalization; whether the normal form
actually respects our sense of "optimality" is a different question.

On Wed, Oct 5, 2011 at 3:17 PM, Goswin von Brederlow <goswin-v-b@web.de> wrote:
> Gabriel Scherer <gabriel.scherer@gmail.com> writes:
>
>>> Below is a quick tentative implementation of NbE, on a slightly
>>> restricted expression type (I removed the not-so-interesting Minus
>>> nodes).
>>
>> Sorry, I forgot to give a small example of what the implementation
>> does. Really the obvious thing, but it may not be so obvious just
>> looking at the code.
>> Here is an example of (X*2+Y)*(3+(Y*2+Z)) being 'rewritten' to
>> (1*Y)*Z+((2*Y)*Y+(3*Y+((2*X)*Z+((4*X)*Y+6*X)))).
>
> (1*Y)? That certainly isn't optimal.
>
> MfG
>        Goswin
>


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2011-10-05 21:31 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-10-02 11:51 [Caml-list] How to simplify an arithmetic expression ? Diego Olivier Fernandez Pons
2011-10-02 14:08 ` Gabriel Scherer
2011-10-02 15:09   ` Ernesto Posse
2011-10-02 16:32     ` Xavier Leroy
2011-10-02 16:48       ` Gabriel Scherer
2011-10-02 17:07         ` Gabriel Scherer
2011-10-05 13:17           ` Goswin von Brederlow
2011-10-05 21:31             ` Gabriel Scherer
2011-10-02 16:56 ` Xavier Clerc
2011-10-05 13:09 ` Goswin von Brederlow

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).