14
$\begingroup$

I want to simplify my function f1 to f2, but f2 can't be compiled. How can I make it compilable?

f1 = Compile[{{arr, _Real, 2}},
   Module[{a, b, c, d}, {a, b, c, d} = arr;
    {{a, (a + b)/2, (a + c)/2, (a + d)/2}, {(a + b)/2, b, (b + c)/2, (
      b + d)/2}, {(a + c)/2, (b + c)/2, c, (c + d)/2}, {(a + d)/2, (
      b + d)/2, (c + d)/2, d}}
    ], RuntimeAttributes -> {Listable}];

v = {{{0, 0, .6}, {-.3, -.5, -.2}, {-.3, .5, -.2}, {.6, 0, -.2}}};
n = 3;
p = Nest[f1, v, n]~Flatten~n;
Graphics3D[Tetrahedron /@ p]


f2 = Compile[{{arr, _Real, 2}},
   Module[{a, b, c, d}, {a, b, c, d} = arr;
    With[{t = Evaluate@Map[Mean, Partition[Tuples[{a, b, c, d}, 2], 4], {2}]},
     t
     ]
    ], RuntimeAttributes -> {Listable}];

enter image description here

$\endgroup$
4

2 Answers 2

21
$\begingroup$

This is a tricky case indeed, because what you basically ask for is compile-time evaluation (macro-style). Generally, the answer is to use meta-programming, to assemble the compiled expression at run-time. The reason your attempt did not work is that the expression you want to evaluate is too deep for Evaluate to be effective.

Solution using in-place evaluation (Trott-Strzebonski technique)

This is perhaps the most straight-forward solution. You take the code you want to compile, wrap it in Hold, then evaluate certain parts in-place, and then call ReleaseHold on the result. Here is the code:

f2 =
  Block[{a, b, c, d},
    ReleaseHold[
      Hold[
        Compile[{{arr, _Real, 2}}, 
          Module[{a, b, c, d}, 
           {a, b, c, d} = arr;
           Map[Mean, Partition[Tuples[{a, b, c, d}, 2], 4], {2}]], 
           RuntimeAttributes -> {Listable}
        ]
      ] /. p_Map :> With[{eval = p}, eval /; True]
    ]]

which results in exactly the same function as if you hand-coded it.

Solution using nested injector pattern

You can also use the nested injector pattern, as follows:

f3 = 
  Block[{a, b, c, d},
    Map[Mean, Partition[Tuples[{a, b, c, d}, 2], 4], {2}] /. expr_ :>
       (Hold[{a, b, c, d}, {a, b, c, d} = arr; expr] /. Hold[code__] :>
           Compile[{{arr, _Real, 2}}, Module[code],RuntimeAttributes -> {Listable}])];

What happens here is that we double-inject the (properly evaluated) pieces of code into the rest of the expression, to form a complete body of Compile. The reason why we need a nested injector pattern is that not only do we need to prevent the premature evaluation of Compile, but also we need to prevent Module-variables renaming by RuleDelayed during the rule application for the first level of injection.

Solution based on code freezing

In this answer, I posted a (very simplified) version of a code-generation framework I once developed, which is based on an idea of "code freezing". The basic idea is that we replace all symbols in a given expression with some dummy symbols temporarily, except some symbols which are left unchanged. We then evaluate the resulting expression, where, due to the evaluation model of Mathematica, only those heads which we left unchanged will possibly evaluate. We then wrap the result in Hold and perform reverse replacements.

This method is more general than the above two, and works in a completely general way. Provided that you load the functions defined in the answer I linked, into Mathematica, the solution using this technique is simply

f4 = 
   ReleaseHold@FrozenCodeEval[
     Hold[
       Compile[{{arr, _Real, 2}}, 
          Module[{a, b, c, d}, {a, b, c, d} = arr;
            Map[Mean, Partition[Tuples[{a, b, c, d}, 2], 4], {2}]
          ], 
          RuntimeAttributes -> {Listable}]
     ],
     {Map, Mean, Partition, Tuples}] 

You can confirm that the resulting function is the same as in the other solutions.

Remarks

There are other ways of solving this problem, of course. If we compare the first two I described above, then you can see that while the nested injector pattern is perhaps a bit more economical, it is harder to understand and one has to deal with more problems (variable renaming), than for a more straight-forward approach based on im-place evaluation.

Both approaches are, however, poor substitutes for true macros. The approach based on code freezing is much closer in spirit, power and generality to what true macros can provide (although, to fully leverage the code freezing idea, one needs slightly more complex version of the code freezing framework).

In any case, the variety of possibilities here do IMO illustrate the fact that Mathematica has more than enough metaprogramming capabilities to perform various forms of JIT compilation, compile-time metaprogramming and the like. At some point, I hope that some metaprogramming libraries will emerge, for doing these things in a more systematic and automatic fashion, and not reinvent the wheel every time.

$\endgroup$
2
  • $\begingroup$ Excellent, thanks! $\endgroup$
    – chyanog
    Mar 28, 2013 at 16:31
  • $\begingroup$ @chyanog No problem. Glad I could help. $\endgroup$ Mar 28, 2013 at 16:57
6
$\begingroup$

Other solutions:

f2 = "Compile[{{arr,_Real,2}},
    Module[{a,b,c,d},{a,b,c,d}=arr;" <> 
    ToString[
     InputForm@
      Map[Mean, Partition[Tuples[{a, b, c, d}, 2], 4], {2}]] <> 
    "],RuntimeAttributes->{Listable}]" // ToExpression;

f3 = Compile[{{arr, _Real, 2}},
     Module[{a, b, c, d}, {a, b, c, d} = arr; #],
     RuntimeAttributes -> {Listable}] &@
   Map[Mean, Partition[Tuples[{a, b, c, d}, 2], 4], {2}];


f4 = With[{expr = Map[Mean, Partition[Tuples[{a, b, c, d}, 2], 4], {2}], 
    v = {a, b, c, d}},
   Compile[{{arr, _Real, 2}},
    Module[v, v = arr; expr],
    RuntimeAttributes -> {Listable}]
   ];
$\endgroup$
1
  • $\begingroup$ +1. While I generally try to avoid the ToString- ToExpression method, the last one with pure function is a really good one. I should've recalled that myself. $\endgroup$ Mar 29, 2013 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.