1
$\begingroup$

I found somewhat strange behaviour of nested integration timings. Consider the following code:

a1 := NIntegrate[x , {x, 0, 1}];
a2[x_] := NIntegrate[x, {x, 0, 1}];

NIntegrate[a1, {x, 0, 1}] // AbsoluteTiming
NIntegrate[a2[x], {x, 0, 1}] // AbsoluteTiming

The output is usually like this:

{0.0171206, 0.5}

{0.0103186, 0.5}

Normally the evaluation time of a2 is 1.5-2 times faster. The only difference is the definition of the inner integration function - with or without argument. The definition of a2 makes no sense for me, but its real execution speed always higher.. Considering numerous similar calculations with much more complicated functions one can save almost half of the time with this trick. Can someone please explain how it works and whether it will always produce correct results?

UDP: I made the code as simple as I could.

$\endgroup$
4
  • $\begingroup$ a1 and a2 are not dependent on t, and a2's x is a dummy variable. What gives? $\endgroup$ Apr 11, 2016 at 14:14
  • $\begingroup$ @J.M. yea, I accidentally left it. Now I've edited the code again. Thanks. $\endgroup$
    – funnyp0ny
    Apr 11, 2016 at 14:19
  • $\begingroup$ Still not sure how a2 is supposed to work; surely NIntegrate[5 fun[5], {5, -4, 4}, args] for a2[5] does not at all make sense. $\endgroup$ Apr 11, 2016 at 14:25
  • $\begingroup$ @J.M. Right, this definition makes no sense for me also. But surprisingly it works inside NIntegrate and with beneficial speed.. $\endgroup$
    – funnyp0ny
    Apr 11, 2016 at 14:29

1 Answer 1

3
$\begingroup$

The trace shows what happens, but not why, which reasons remain obscure to me. NIntegrate internally evaluates the integrand, so ultimately the outside integral is computed on the integrand 0.5, which is the value of both a1 and a2[x]. The slower way has essentially three evaluations of NIntegrate. The faster way has only two, but it also has several extra erroneous evaluations, which take a negligible amount of time. What appears to be the case is that the integrand is evaluated at the right end point of the interval of integration. In the "fast" way, that results in an illegal NIntegrate call, which evaluates quickly; in the "slow" way, it results in a valid NIntegrate call, which evaluates slowly. It hardly seems like a robust way to speed up NIntegrate.

Trace[
 NIntegrate[a1, {x, 0, 2}],
 _NIntegrate,
 TraceInternal -> True, TraceForward -> True]

Mathematica graphics

Trace[
  NIntegrate[a2[x], {x, 0, 2}],
  _NIntegrate,
  TraceInternal -> True, TraceForward -> True]

Mathematica graphics

Here are the timings of the components. Note that a1 and a2[x] take the same time to evaluate as the first integral below. The extra integrals in the trace of a2[x], show in the second integral below, take very little time. Finally both integrals above end by evaluating the third integral below, which takes the same time as a1 or a2[x].

NIntegrate[x, {x, 0, 1}] // RepeatedTiming
(*  {0.0025, 0.5}  *)

Quiet@NIntegrate[2, {2, 0, 1}] // RepeatedTiming
(*  {4.2*10^-7, NIntegrate[2, {2, 0, 1}]}  *)

NIntegrate[0.5, {x, 0, 2}] // RepeatedTiming
(*  {0.0024, 1.}  *)
$\endgroup$

Your Answer

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

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