# NIntegrate-ing a compiled function

I'm trying to integrate numerically in 6 dimensions a very long expression and I read about the option to NIntegrate a compiled function which should be faster. However, this is not the case. I have tried a simple example with just 3 variables.

g[x_, y_, z_] := Sin[x]^2 y Exp[z];
f = Compile[{{x, _Real}, {y, _Real}, {z, _Real}}, g[x, y, z]];
f2[x_?NumericQ, y_?NumericQ, z_?NumericQ] := f[x, y, z];


And now the integrals and the output:

NIntegrate[f2[x, y, z], {x, 0, 100}, {y, 0, 10}, {z, 0, 9}]//Timing

{2.335434,2.03437*10^7}

NIntegrate[g[x, y, z], {x, 0, 100}, {y, 0, 10}, {z, 0, 9}]//Timing

{0.316207,2.03437*10^7}


Can I do anything to improve the speed of the integral of the compiled function?

• Define f2 = Compile[{{x, _Real}, {y, _Real}, {z, _Real}}, Sin[x]^2*y*Exp[z] 1, RuntimeOptions -> "EvaluateSymbolically" -> False] and it helps a bit. But I suspect the rest of it comes from NIntegrate's symbolic preprocessing, which cannot be done for a purely numeric function. – Oleksandr R. May 7 '15 at 13:29
• I omitted the 1 at the end, if it is a typo - please, revisit the post accordingly – Sektor May 7 '15 at 13:35

No fair, you let NIntegrate see the symbolic form of the native expression. If you do the same trick:

     f3[x_?NumericQ, y_?NumericQ, z_?NumericQ] := g[x, y, z];

NIntegrate[g[x, y, z], {x, 0, 100}, {y, 0, 10}, {z, 0, 9}] // Timing
NIntegrate[f2[x, y, z], {x, 0, 100}, {y, 0, 10}, {z, 0, 9}] // Timing
NIntegrate[f3[x, y, z], {x, 0, 100}, {y, 0, 10}, {z, 0, 9}] // Timing


{ 0.513922, 1.77873, 2.30465}

You see the compiled version ( f2 ) is indeed marginally faster than the numeric-only-uncompiled version ( f3 ).

If you count the function evaluations for the three cases like this:

 Length@Last@Last@Reap@
NIntegrate[g[x, y, z], {x, 0, 100}, {y, 0, 10}, {z, 0, 9},
EvaluationMonitor :> Sow[0]]


for the three cases you get:

1354, 134673, 134673

you see the speed of the first case comes from NIntegrate being smart about the quadrature scheme, not the actual performance of the function.

• I took the OP's question to be about integrating a black-box compiled function versus the symbolic one. It's not about the speed of compiled functions per se. So I think it's "fair." On the other hand, your second point is important. That's sort of what I was alluding to in my first point, but you state it (and support it) much more elegantly. (+1) – Michael E2 May 7 '15 at 19:29
• Actually the speed comparison depends heavily on the upper integration limits. For example change the upper x limit from 100 to 20 then the compiled version is faster. So compiling the target function might be benefitial unless MMA has a smart way to treat the integration. But it is certainly worth a try for specific applications. – Display Name Dec 13 '18 at 16:54
• Indeed if you set Method -> {Automatic, "SymbolicProcessing" -> 0} in the numerical integration then the compiled version will always be equally fast. – Display Name Dec 13 '18 at 17:25

Here are some reasons or surmises:

1. I believe some functions are special-cased in NIntegrate; I'm pretty sure this is true for low-degree polynomials.

2. To get the advantage of compiling, use f = Compile[{{x, _Real}, {y, _Real}, {z, _Real}}, Evaluate@g[x, y, z]], but it will still be slower than just using g. Without the Evaluate, the compiled function makes an external call to the uncompiled g. (Inspect with Needs["CompiledFunctionTools"]; CompilePrint[f].)

3. I think another reason is that NIntegrate does arithmetic with extra precision by constructing an ExperimentalNumericalFunction. But NIntegrate does not do this in the case of the compiled function, and it may be costing time by converting results to higher precision or some other sort of numerical fiddling around.

Example

f2tr = Trace[
NIntegrate[f2[x, y, z], {x, 0, 100}, {y, 0, 10}, {z, 0, 9}],
_ExperimentalNumericalFunction,
TraceInternal -> True];

gtr = Trace[
NIntegrate[g[x, y, z], {x, 0, 100}, {y, 0, 10}, {z, 0, 9}],
_ExperimentalNumericalFunction,
TraceInternal -> True];

f2tr
(*
{}
*)

Length@Flatten[gtr]
First@Flatten[gtr]
(*
12537
ExperimentalNumericalFunction[{x,y,z},E^z y Sin[x]^2,"-NumericalFunctionData-"]
*)

• Your first observation is correct, because NIntegrate uses by default the Gauss-Kronrod integration rule for one dimensional integrals. – Anton Antonov Jan 19 '16 at 23:52

With RuntimeOptions -> "EvaluateSymbolically" -> False and Evaluate you don't need an intermediate function and get 3x speedup:

f = Compile[{{x, _Real}, {y, _Real}, {z, _Real}}, g[x, y, z]];
f2[x_Real, y_Real, z_Real] := f[x, y, z];
f3 = Compile[{{x, _Real}, {y, _Real}, {z, _Real}},
Evaluate@g[x, y, z],
RuntimeOptions -> "EvaluateSymbolically" -> False];
Timing@NIntegrate[f2[x, y, z], {x, 0, 100}, {y, 0, 10}, {z, 0, 9}]
Timing@NIntegrate[f3[x, y, z], {x, 0, 100}, {y, 0, 10}, {z, 0, 9}]

{2.74562, 2.03437*10^7}
{0.904806, 2.03437*10^7}
`