Best approach for 'manual' common subexpression elimination


I have working code, but am looking for ways to make it more elegant.

I have a fairly large expression with plenty of repetition and structure. A shortened toy example is

expr = (a/(a + b))^4.5  + (b/(a + b))^3.5 + 
  (a/(a + b))^1.5 + Cosh[a + b]  + 
  Log[a/(a + b)] + Log[b/(a + b)]

I have prepared a set of rules that simplifies the expression, for example like this:

rules = {
  a + b -> sum, 
  a/sum -> g,
  b/sum -> h}

In my actual code I use pattern matching, and also create unique symbols for all the matches, see below for an example.

 Derivative[l_List, i_][δ][xv, ρ] :> 
  Symbol[StringJoin["$δd", StringJoin[ToString /@ l], "x", ToString[i]]]

The simplified expression, and the actual set of replacements is provided by the following function:

replaceone[expr_, rule_] := Block[{newtemps, unique},
  unique = Union@Cases[expr, rule[[1]], Infinity];
  newtemps = Thread[(unique /. rule) -> unique];
  expr /. rule
{newekspr, temps} = Reap[Fold[replaceone, expr, rules]]
temps = Association[temps]

I can then prepare a function for efficient evaluation of the expression, like this, with a nested Block.

exprfun[av_, bv_] :=
 Block[{a = av, b = bv},
  Block[Evaluate[Keys[temps]], KeyValueMap[Set, temps];

I can Compile this function, although I suspect that the result is that the entire expression is expanded and simplified again.

Compile[{a, b}, Evaluate[exprfun[a, b]]]

Is this the best way of doing this?

Can I write a higher order function that takes my original expression, and a rules set, simplifies it and provides a optimised, and possibly compiled function?

What is the benefit of a nested With, and LetL, that I have seen elsewhere, compared with this approach?

Åsmund Hj

Posted 2016-03-10T14:36:11.063

Reputation: 358

And LetL is defined as? – Daniel Lichtblau – 2016-03-10T15:50:10.680

1Next time, when using a function obtained from another post on this site, please link to the original post. – J. M.'s ennui – 2016-03-10T16:32:42.390

@J.M. Yes, of course. Thanks for correcting. – Åsmund Hj – 2016-03-10T18:51:28.433


Have you tried Experimental`OptimizeExpression on your expression? A few refs: (25136), (31614), (32097), (58985)

– Michael E2 – 2016-03-13T17:07:19.610

@MichaelE2 My first attempts on ExperimentalOptimizeExpressionfailed, but I tried it again on a smaller problem, and now it works. Don't know why. Still, it seems a bit inelegant to let the expression explode in complexity, for then to leave toOptimizeExpression` to clean up afterwards. My expression, with an original leafcount of around 150 000 is now calculated in 13 ns, compared to 28ms before compilation. Still, I would really like to use the manual rules as well, as these intermediary results are interesting by their own as well. – Åsmund Hj – 2016-03-13T17:37:16.363

Perhaps a small point, but rules contains typos. nvec/Total[nvec], {i, nc}] and $[Delta]d. – bbgodfrey – 2016-03-14T06:07:13.870

@bbgodfrey Thanks. I fixed the first one, but cannot find the second. – Åsmund Hj – 2016-03-14T06:47:25.840

I should have been more explicit. $[Delta]d should have been $\[Delta]d. Also, a comma was needed between the two rules involving Derivative. I fixed both and also cleaned the format a bit. Please verify that I did not introduce other issues. By the way, nc needs a value. – bbgodfrey – 2016-03-14T14:01:15.307

No answers