Skip to main content

compile - Compiling more functions that don't call MainEvaluate


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


This returns False, where FastCompiledFunctionQ[] checks if a compiled function calls MainEvaluate in order to use normal Mathematica code instead of compiled code, which is usually slower than compiled code.


Is there a way 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.



Answer



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 advice 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}

  • If you call many times a same relatively big function (for example an interpolation function that you have written), it can be best to use a CompiledFunctionCall as explained in this answer in order to avoid an exploding code size after code expansion.

  • 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 access the keys and the values of cacheString, see here, or you can use and manipulate an Association in V10 instead of a symbol for cacheString.





  • 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 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*)


Comments

Popular posts from this blog

mathematical optimization - Minimizing using indices, error: Part::pkspec1: The expression cannot be used as a part specification

I want to use Minimize where the variables to minimize are indices pointing into an array. Here a MWE that hopefully shows what my problem is. vars = u@# & /@ Range[3]; cons = Flatten@ { Table[(u[j] != #) & /@ vars[[j + 1 ;; -1]], {j, 1, 3 - 1}], 1 vec1 = {1, 2, 3}; vec2 = {1, 2, 3}; Minimize[{Total@((vec1[[#]] - vec2[[u[#]]])^2 & /@ Range[1, 3]), cons}, vars, Integers] The error I get: Part::pkspec1: The expression u[1] cannot be used as a part specification. >> Answer Ok, it seems that one can get around Mathematica trying to evaluate vec2[[u[1]]] too early by using the function Indexed[vec2,u[1]] . The working MWE would then look like the following: vars = u@# & /@ Range[3]; cons = Flatten@{ Table[(u[j] != #) & /@ vars[[j + 1 ;; -1]], {j, 1, 3 - 1}], 1 vec1 = {1, 2, 3}; vec2 = {1, 2, 3}; NMinimize[ {Total@((vec1[[#]] - Indexed[vec2, u[#]])^2 & /@ R...

functions - Get leading series expansion term?

Given a function f[x] , I would like to have a function leadingSeries that returns just the leading term in the series around x=0 . For example: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x)] x and leadingSeries[(1/x + 2 + (1 - 1/x^3)/4)/(4 + x)] -(1/(16 x^3)) Is there such a function in Mathematica? Or maybe one can implement it efficiently? EDIT I finally went with the following implementation, based on Carl Woll 's answer: lds[ex_,x_]:=( (ex/.x->(x+O[x]^2))/.SeriesData[U_,Z_,L_List,Mi_,Ma_,De_]:>SeriesData[U,Z,{L[[1]]},Mi,Mi+1,De]//Quiet//Normal) The advantage is, that this one also properly works with functions whose leading term is a constant: lds[Exp[x],x] 1 Answer Update 1 Updated to eliminate SeriesData and to not return additional terms Perhaps you could use: leadingSeries[expr_, x_] := Normal[expr /. x->(x+O[x]^2) /. a_List :> Take[a, 1]] Then for your examples: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x), x] leadingSeries[Exp[x], x] leadingSeries[(1/x + 2 + (1 - 1/x...

What is and isn't a valid variable specification for Manipulate?

I have an expression whose terms have arguments (representing subscripts), like this: myExpr = A[0] + V[1,T] I would like to put it inside a Manipulate to see its value as I move around the parameters. (The goal is eventually to plot it wrt one of the variables inside.) However, Mathematica complains when I set V[1,T] as a manipulated variable: Manipulate[Evaluate[myExpr], {A[0], 0, 1}, {V[1, T], 0, 1}] (*Manipulate::vsform: Manipulate argument {V[1,T],0,1} does not have the correct form for a variable specification. >> *) As a workaround, if I get rid of the symbol T inside the argument, it works fine: Manipulate[ Evaluate[myExpr /. T -> 15], {A[0], 0, 1}, {V[1, 15], 0, 1}] Why this behavior? Can anyone point me to the documentation that says what counts as a valid variable? And is there a way to get Manpiulate to accept an expression with a symbolic argument as a variable? Investigations I've done so far: I tried using variableQ from this answer , but it says V[1...