Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

I would like to use Compile with functions defined outside Compile.

For example if I have the two basic functions F and G

F[x_] := x + 2
G[x_] := x

And I want to compute F[G[x]] in Compile

compiledFunction = Compile[{{x, _Real, 0}}, F[G[x]] ]

The resulting compiled function calls MainEvaluate

FastCompiledFunctionQ[function_CompiledFunction]:=
(
    Needs["CompiledFunctionTools`"];
    StringFreeQ[CompiledFunctionTools`CompilePrint@function,"MainEvaluate"]
)

compiledFunction // FastCompiledFunctionQ

Returns False, where FastCompiledFunctionQ checks if a compiled function calls MainEvaluate (in order to use normal Mathematica kind of code instead of one compiled using Compile), which is usually slower than compiled code.

Is there a way to go around this ?

More generally I want to compile almost any numerical Mathematica code that calls user defined functions (which themselves can call other user defined functions) and doesn't use symbolic computations.

share|improve this question
    
It calls MainEvaluate, that's the point of the post. Note that the post is meant for more complex cases than the example of the question. –  Faysal Aberkane May 3 '13 at 15:54
    
Hmm, interesting, apparently the inlining only works if the external stuff was defined with Set[]; with SetDelayed[], it always seems to require kernel evaluation. –  J. M. May 3 '13 at 16:04
add comment

4 Answers

up vote 19 down vote accepted

Yes there is a way to use functions that use external non compiled functions.

It uses the step function of Mr.Wizard defined in the post How do I evaluate only one step of an expression?, in order to recursively expand the code that we want to compile until it uses only functions that Mathematica can compile. The technique discussed in the post How to inject an evaluated expression into a held expression? is also used.

The function ExpandCode needs two functions that tell it when a function should be expanded and when a function should be evaluated during the expansion.

Using the functions defined below we can do to solve the question

code = Hold[F[G[x]]]
codeExpand = ExpandCode[code]
compiledFunction2 = Function[codeExpanded, Compile[{{x, _Real}}, codeExpanded], HoldFirst] @@ codeExpand

The $CriteriaFunction used here is that a function name (symbol) should have upper case letters only. Note the use of pure function with HoldFirst attribute in order to avoid evaluation leaks.

And now the function compiledFunction2 doesn't Call MainEvaluate and returns the right answer

compiledFunction2 // FastCompiledFunctionQ
compiledFunction2[2]

A more streamlined version of this for common cases using a function defined below

CompileExpand[{{x, _Real}}, F[G[x]]] // FastCompiledFunctionQ

Here's the main code and some advices are after it.

SetAttributes[STEP, {Flat, OneIdentity, HoldFirst}];
STEP[expr_] :=
    Module[{P},
        P = (P = Return[# (*/. HoldForm[x_] :> Defer[STEP[x]]*), TraceScan] &) &;
        TraceScan[P, expr, TraceDepth -> 1] 
    ];

ReleaseAllHold[expr_,firstLevel_:0,lastLevel_:Infinity] := Replace[expr, (Hold|HoldForm|HoldPattern|HoldComplete)[e___] :> e, {firstLevel, lastLevel}, Heads -> True];

SetAttributes[EVALUATE,HoldFirst];
EVALUATE[x_]:=x;

$CriteriaFunction =Function[symbol,UpperCaseQ@SymbolName@symbol,HoldFirst];
$FullEvalFunction=Function[symbol,symbol===EVALUATE,HoldFirst];

ExpandCode[code_]:=ReleaseAllHold[Quiet@ExpandCodeAux[code,$CriteriaFunction ,$FullEvalFunction], 1];

ExpandCodeAux[code_,criteria_,fullEval_]:=
    code /.
    (expr:(x_Symbol[___]) /; criteria@x :>
        RuleCondition[
            If[fullEval@x,
                expr
                ,
                With[{oneStep = HoldForm@Evaluate@STEP@expr},
                    If[oneStep===HoldForm@expr,
                        oneStep
                        ,
                        ExpandCodeAux[oneStep,criteria,fullEval]
                    ]
                ]
            ]
        ]
    );

SetAttributes[CompileExpand,HoldAll];   
CompileExpand[variables_,code_,otherVariables___]:=
    Function[
        codeExpanded
        ,
        Compile[variables,codeExpanded,otherVariables]
        ,
        HoldFirst
    ] @@ ExpandCode[Hold@code];

FastCompiledFunctionQ[function_CompiledFunction]:=
(
    Needs["CompiledFunctionTools`"];
    StringFreeQ[CompiledFunctionTools`CompilePrint@function,"MainEvaluate"]
)

(*Example*)
SetAttributes[{F,G},HoldAll];
F[x_] := G[x] + 2;
G[x_] := 3 x;
compiledFunction3=CompileExpand[{{x,_Real}},F[G[x]]+EVALUATE@Range@5,CompilationTarget->"WVM"]
compiledFunction3//FastCompiledFunctionQ
compiledFunction3[2]

Comments

  • You need to specify the type of the variables even if they are Real numbers (for example {{x,_Real}} and not x for a function of just one variable).
  • Works with any type of values : DownValues, UpValues, SubValues ... which means you can use auxiliary functions that use the pattern matcher in their definitions instead of just already compiled functions that sometimes don't mix well together, and still be able to compile without calls to MainEvaluate.
  • A function to be expanded can contain calls to other functions that will be expanded.
  • In order to avoid problems the functions that you want to expand should have a HoldAll attribute (SetAttributes[F,HoldAll] for example).
  • Some useful Compile arguments for speed {Parallelization->True,RuntimeAttributes->{Listable},CompilationTarget->"WVM",RuntimeOptions->"Speed",CompilationOptions->{"ExpressionOptimization"->True,"InlineCompiledFunctions"->True,"InlineExternalDefinitions"->True}
  • It can be best to avoid "ExpressionOptimization" when the CompilationTarget target is "WVM" (the compilation is faster, especially as the size of the expanded code can be very big). When it's "C" it's better to optimize the expression.
  • Numeric functions don't have a HoldAll attribute and pose problems if you want to expand a function that is inside a numeric one. You can use InheritedBlock to circumvent this. For example

    blockedFunctions={Abs,Log,Power,Plus,Minus,Times,Max,UnitStep,Exp};
    
    With[{blockedFunctions=blockedFunctions},
        Internal`InheritedBlock[blockedFunctions,
            SetAttributes[#,HoldAll]&/@blockedFunctions;            
            ExpandCode[....]
        ]
    ]
    
  • If you use constant strings in your code you can replace them inside the code expanded with Real numbers (in order to return them together with a Real result in a List which will compile correctly, as you can't mix types in the result of a compiled function). For example

    Module[{cacheString,stringIndex=0.,codeExpandWithStringsReplaced},
       c:cacheString[s_] := c = ++stringIndex;      
       codeExpandWithStringsReplaced=codeExpand/.s_String:>RuleCondition[cacheString[s]];
       ...  
    ]
    

    And then cacheString can be used to convert the results returned by the compiled function back into strings. You need to to access the keys and the values of cacheString, see here.

  • A simple way to fully evaluate an expression during the code expansion is to enclose the expression between an EVALUATE function equal to the identity function.

    SetAttributes[EVALUATE,HoldFirst];
    EVALUATE[x_]:=x;
    $FullEvalFunction = Function[symbol,symbol===EVALUATE,HoldFirst];
    

    for example

    EVALUATE[Range@5]  
    

    EVALUATE also lets you avoid using With in order to insert constant parameters into the compiled code.

  • This code expansion can be used in order to have a fast compiled DSL (Domain Specific Language).

  • If you modify the $CriteriaFunction you can you use Apply. This is an easier way to use Apply with Compile than in this question: Using Apply inside Compile.

    $CriteriaFunction=Function[symbol,UpperCaseQ@SymbolName@symbol||symbol===Apply,HoldFirst];  
    
    f=Compile[{{x,_Real}},F@@{x}]  
    f // FastCompiledFunctionQ (*False*)
    
    f=CompileExpand[{{x,_Real}},F@@{x}]  
    f // FastCompiledFunctionQ (*True*)  
    

    You can also use this syntax instead of redefining $CriteriaFunction.

    f = CompileExpand[{{x, _Real}}, STEP[F @@ {x}]] 
    f // FastCompiledFunctionQ (*True*)  
    
share|improve this answer
    
+1. Related answer. Note particularly the last solution there, based on what I call code freezing - it is similar in spirit to what you are doing here with step, albeit based on an entirely different mechanism of evaluation control. –  Leonid Shifrin May 3 '13 at 16:03
add comment

Use pure functions (Function) and "InlineExternalDefinitions" -> True:

g = #^2 &;
f = # + 1 &;

compiledFunction = 
  Compile[{{x, _Real, 0}}, f@g[x], 
   CompilationOptions -> {"InlineExternalDefinitions" -> True}];

CompilePrint[compiledFunction]


        1 argument
        1 Integer register
        4 Real registers
        Underflow checking off
        Overflow checking off
        Integer overflow checking on
        RuntimeAttributes -> {}

        R0 = A1
        I0 = 1
        Result = R3

1   R1 = R0
2   R2 = Square[ R1]
3   R1 = I0
4   R3 = R2 + R1
5   Return

If f and g are compiled functions themselves, use the "InlineCompiledFunctions" -> True option as well.

As Leonid mentions below, the reason why Set or SetDelayed definitions won't be inlined is that they might contain complex patterns (including patterns with conditions) and thus depend on the type of data passed to them.

share|improve this answer
    
I do wonder why defining with SetDelayed[] doesn't work as I thought it should... –  J. M. May 3 '13 at 16:08
4  
@J.M. Because in general it may have conditions on patterns, so that those definitions can not be expanded. Besides, one function may have several separate definitions with SetDelayed. In general, rule-based functions are currently non-compilable. One of the main reasons is that pattern-matcher is dynamic (data-dependent and also calls main evaluator when Condition or PatternTest are present), and so pattern-matching can not be done at compile-time, or even compiled to something efficient, without additional information which is only available at run-time. JIT might be possible though. –  Leonid Shifrin May 3 '13 at 16:12
    
@Szabolcs Since you mentioned "InlineExternalDefinitions", you may as well mention "InlineCompiledFunctions", which is logically related (although more tangential here). –  Leonid Shifrin May 3 '13 at 16:13
    
Pure functions are fine for simple functions, but don't solve more complex cases, for example if f or g contain calls to other functions. –  Faysal Aberkane May 4 '13 at 9:10
    
@FaysalAberkane You're right, it won't handle nested functions. –  Szabolcs May 4 '13 at 14:50
show 2 more comments

Evaluate will solve this.

F[x_] := x + 2;
G[x_] := x;

ff = Compile[{x}, Evaluate@F[G[x]]];

<<CompiledFunctionTools`
CompilePrint@ff

result

        1 argument
        1 Integer register
        2 Real registers
        Underflow checking off
        Overflow checking off
        Integer overflow checking on
        RuntimeAttributes -> {}

        R0 = A1
        I0 = 2
        Result = R1

1   R1 = I0
2   R1 = R1 + R0
3   Return
share|improve this answer
add comment

Please be gentle with me - this is my first ever post to stackexchange.

Firstly, I'd like to say that I found Faysal's post both fascinating and outstandingly useful (I would upvote it if I could, but I have insufficient reputation). It introduces techniques I'm sure I shall use a great deal. However, it doesn't, quite, cater for all use cases. Where a function is always referenced with its parameter list, then it works for me very well. But, where a function is referenced without explicit arguments, e.g.

FCN /@ ll 

ExpandCode doesn't dig down to expand the function definition, resulting in the inclusion of a "MainEvaluate" in the compiled code. I have attempted to fix this up with the modified version of ExpandCodeAux shown below. I have also slightly modified the criteria function so that it takes a string argument, and checks the putative function symbol name against a list of functions that should be expanded (expandFns) or, alternatively, a list of functions which should NOT be expanded (nonExpandFns). All other code from Faysal's original post remains unchanged. I have tested it with code that includes functions defined as:

FCN1[x_] := <expression in x>

as

FCN2 = Function[{x},<expression in x>]

and as

FCN3 = <expression in #>&

and it produced functional, self-contained fully-compiled code. However, it clearly has many limitations. Specifically, it won't work for function definitions that rely on pattern matching on the argument list to select between different forms. It assumes that the first DownValue definition is the one you want.

I rather fear that my additions have obscured the essential elegance of the original, and I might well have introduced the possibility of incorrect behaviour in some cases. (I always check that the compiled code reproduces test cases run on the interpreted version for this reason). I'm also quite sure that my ham-fisted use of the the various Hold constructs (the result of much experimentation) could be improved upon.

I hope, nevertheless, that this might prove to be of some use.

criteriaFcn = Function[symbName,
     If[nonExpandFns == {},
      Or @@ (StringMatchQ[expandFns, symbName]),
      Not[Or @@ (StringMatchQ[nonExpandFns, symbName])]
      ]
   ] 

SetAttributes[unpattern,HoldAll]
unpattern[args__] :=
  Sequence @@({args} /. Verbatim[Pattern][a_,Verbatim[Blank][]]->a)


ExpandCodeAux[code_, criteria_, fullEval_] := Module[{oneStep},
       code /.
        { (*Caters for cases where a function is referenced with explicit \
    parameters*)
         (x_Symbol[args___] /;
            With[{xref = HoldForm[x]},
             criteria@ToString@xref
             ] :>
           With[
            {eval = If[fullEval@x,
               x@args,
               oneStep = With[{Step = step@x@args}, HoldForm@Step];
               If[oneStep === HoldForm@x@args,
                oneStep,
                ExpandCodeAux[oneStep, criteria, fullEval]]]
             },
            eval /; True
            ]
          ),
         (*For cases where the function is defined as a DownValue,
         but is referenced without its parameters (as in f/@ll) *)
         (x_Symbol /;
            With[{xref = HoldForm[x], dv = DownValues[Unevaluated@x]}, 
             dv != {} && criteria@ToString@xref 
             ] :> 
           With[
            {
             eval =
              (ExpandCodeAux[
                First@With[{dv = DownValues[Unevaluated@x]}, dv] /.
                 Verbatim[RuleDelayed][
                   Verbatim[HoldPattern][HoldPattern[x][args__]], 
                   rhs_] :> 
                  Function[Evaluate@List@unpattern[Unevaluated@args], 
                   HoldForm@rhs], criteria, fullEval])
             },
            eval /; True
            ]
          ),
         (*A symbol to which a pure function has been assigned as one of \
    its OwnValues, as in f = Function[a,a^2].*)
         (x_Symbol /;
            With[{xref = HoldForm[x], ov = OwnValues[Unevaluated@x]},
             Length[ov] > 0 
              && 
              MatchQ[First@ov, 
               Verbatim[RuleDelayed][
                Verbatim[HoldPattern]@HoldPattern@x, _]]
               && criteria@ToString@xref 
             ] :> 
           With
            [
            {eval =
              ExpandCodeAux[(First@
                  With[{ov = OwnValues[Unevaluated@x]}, ov]) /. 
                Verbatim[RuleDelayed][
                  Verbatim[HoldPattern]@HoldPattern[x], rhs_] :> rhs, 
               criteria, fullEval]
             },
            eval /; True
            ]
          )
         }
       ];
share|improve this answer
    
It's interesting to continue trying to expand more features of the language, like I did for Apply, thanks. –  Faysal Aberkane Nov 21 '13 at 14:30
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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