I am trying to address the calculation time of a fairly large expression. (It is a partial derivative from an accurate thermodynamic equation of state, GERG-2004.) There is a great deal of repetition and structure, so I have a good idea of what common subexpressionsworking code, but am looking for ways to identify and calculate separatelymake it more elegant.
I have done 'manual' subexpression elimination by developing a ruleset like the following. For example, the sumfairly large expression with plenty of all componentsrepetition and density occurs frequently. In addition, some partial derivatives occur in multiple expressions, so I want to calculate them separately as well. I have crafted the replacement rules so that they are valid symbolsstructure. A shortened toy example is
nc=2;
nvecexpr = Table[Symbol["n"(a/(a <>+ ToString[i]],b))^4.5 {i, nc}]
+ (b/(a + b))^3.5 +
rules = {nvec(a/Total[nvec](a ->+ xv,b))^1.5
+ Total[nvec]Cosh[a ->+ sumn,b] +
sumn Log[a/v(a ->+ ρ,
b)] Derivative[a_,+ b_][α00[[i_]]][ρ,Log[b/(a t]+ :>b)]
I have prepared a set of rules that simplifies the expression, for example like this:
rules = {
a Symbol[StringJoin["$α00d",+ ToString[i],b "x",-> ToString[a]sum, ToString[b]]]
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]]]
}
I substitute usingThe 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];
Sow[newtemps];
expr /. rule
]
{newekspr, temps} = Reap[Fold[replaceone, eksprexpr, rules]]
temps = Association[temps]
newekspr
is then the original function, with all replacements performed, and temps
is a list of actual replacements done.
How can I createcan then prepare a compilable function that takes the remaining variables (n1, n2, t and v) as parameters, and returns the valuefor efficient evaluation of the expression?
I tried using LetL
, but am unable to combine the list of replacements (in temps
)like this, with the function arguments.
To provide a simple example, this works:nested Block.
attempt1[b_]exprfun[av_, bv_] :=
LetL[Block[{temp1a := av, b += 2bv},
temp2 :=Block[Evaluate[Keys[temps]], 3*bKeyValueMap[Set, rvtemps];
:= temp1 +newekspr]] temp2}, rv]
attempt1[10]
(* 42 *)
but if I provide the first argument tocan LetLCompile
as (or list of Rulesthis function, or anything..although I suspect that the result is that the entire expression is expanded and simplified again.)
steps = Hold[Compile[{temp1 :=a, b + 2}, temp2 := 3*bEvaluate[exprfun[a, rv := temp1 + temp2}]b]]]
How can I then use LetL
Is this the best way of doing this?
attempt2[b_] := LetL[Evaluate[steps], rv]
attempt2[10]
(* LetL[Hold[{temp1 := b + 2, temp2 := 3 b, rv := temp1 + temp2}], rv] *)
Can I write a higher order function that didn't release the holdtakes my original expression, and the following doesn't work either.a rules set, simplifies it and provides a optimised, and possibly compiled function?
attempt4[b_] := LetL[Evaluate[ReleaseHold[steps]], rv];
attempt4[10]
(* During evaluation of In[41]:= With::lvws: Variable Null in local variable specification {Null} requires a value. >> *)
(* With[{Null}, With[{Null}, With[{Null}, rv]]] *)
What is the best waybenefit of a nested With, that also allows me toand Compile
the functionLetL
, that I have seen elsewhere, compared with this approach?