10
$\begingroup$

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?

$\endgroup$
3
  • 6
    $\begingroup$ 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. $\endgroup$ Commented May 7, 2015 at 13:29
  • $\begingroup$ I omitted the 1 at the end, if it is a typo - please, revisit the post accordingly $\endgroup$
    – Sektor
    Commented May 7, 2015 at 13:35
  • $\begingroup$ @OleksandrR. Your comment suggests that we need a version of NIntegrate that would use a symbolic function for preprocessing, and its compiled version to calculate numerical values of the integrand. I tried to build something like this using constructs like bva2D2sub2ci[k_, bv_, p_, \[Psi]_] := If[NumericQ[bv] && NumericQ[p] && NumericQ[\[Psi]],... but at best achieved the same speed as when using an uncompiled integrand. $\endgroup$ Commented Nov 16, 2022 at 3:57

3 Answers 3

6
$\begingroup$

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.

$\endgroup$
3
  • 1
    $\begingroup$ 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) $\endgroup$
    – Michael E2
    Commented May 7, 2015 at 19:29
  • $\begingroup$ 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. $\endgroup$
    – NeverMind
    Commented Dec 13, 2018 at 16:54
  • $\begingroup$ Indeed if you set Method -> {Automatic, "SymbolicProcessing" -> 0} in the numerical integration then the compiled version will always be equally fast. $\endgroup$
    – NeverMind
    Commented Dec 13, 2018 at 17:25
4
$\begingroup$

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 Experimental`NumericalFunction. 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}],
   _Experimental`NumericalFunction,
   TraceInternal -> True];

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

f2tr
(*
  {}
*)

Length@Flatten[gtr]
First@Flatten[gtr]
(*
  12537
  Experimental`NumericalFunction[{x,y,z},E^z y Sin[x]^2,"-NumericalFunctionData-"]
*)
$\endgroup$
1
  • 2
    $\begingroup$ Your first observation is correct, because NIntegrate uses by default the Gauss-Kronrod integration rule for one dimensional integrals. $\endgroup$ Commented Jan 19, 2016 at 23:52
4
$\begingroup$

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}
$\endgroup$

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.