First, NIntegrate[f1[x], {x, xmin, xmax}]
usually proceeds by constructing an Experimental`NumericalFunction
from the expression for f1[x]
. This will circumvent an attempt to memoize f1
in the OP's manner, f1[x_] := f1[x] =...
. One can prevent this by memoizing the function with ?NumericQ
checks via f2[x_?NumericQ] := f2[x] = ...
. One thing to consider is that the NumericalFunction
constructed in each case is different, and if one of them is more efficient, I would bet it's the first one; see below for evidence of this. Anton Antonov alluded to this in a comment.
Second, as Mr.Wizard has pointed out, NIntegrate
on a list of integrands just call NIntegrate
on each integrand. To expand further, the reason there is no speed-up is that the first integral samples only 116721
points, whereas the second samples 1063425
. The time it takes to do the second integral dwarfs the first. Memoizing the first integral isn't going to help much, even if all the saved values are reused. (The reason for the difference is that the Sqrt
creates some singularities, as well as complex values, which makes the second integral more difficult to compute.)
Code for testing the sampling:
ClearAll[g];
g[x_, y_, z_] := g[x, y, z] = Exp[Sin[x]] + Cos[y + z];
{pts1, pts2} =
Last@Last@Reap@
NIntegrate[#, {x, 0, 10}, {y, 0, 10}, {z, 0, 10},
Method -> {"LocalAdaptive", "SymbolicProcessing" -> 0},
PrecisionGoal -> 7,
EvaluationMonitor :> Sow[{x, y, z}]] & /@
{g[x, y, z], Sqrt[g[x, y, z]] + x}; // AbsoluteTiming
(* {10.162, Null} *)
ClearAll[g];
g[x_, y_, z_] := g[x, y, z] = Exp[Sin[x]] + Cos[y + z];
ptsall = Last@Last@Reap@
NIntegrate[{Sqrt[g[x, y, z]] + x, g[x, y, z]},
{x, 0, 10}, {y, 0, 10}, {z, 0, 10},
Method -> {"LocalAdaptive", "SymbolicProcessing" -> 0},
PrecisionGoal -> 7,
EvaluationMonitor :> Sow[{x, y, z}]]; // AbsoluteTiming
(* {10.1304, Null} *)
Length /@ {pts1, pts2}
Total@%
(*
{116721, 1063425}
1180146
*)
Length@ptsall (* same as the preceeding Total *)
(* 1180146 *)
Code for estimating how much time could be saved by memoizing ~120K sample values, if it worked as expected (about half a second):
ClearAll[g];
g[x_, y_, z_] := g[x, y, z] = Exp[Sin[x]] + Cos[y + z];
Table[g[x, y, z], {x, 0., 10, 1./8}, {y, 0., 10, 1./4}, {z, 0., 10, 1./4}]
// Flatten // Length // AbsoluteTiming
Table[Sqrt[g[x, y, z]] + x, {x, 0., 10, 1./8}, {y, 0., 10, 1./4}, {z, 0., 10, 1./4}]
// Flatten // Length // AbsoluteTiming
(*
{0.81421, 136161}
{0.243886, 136161}
*)
Code to compare the Experimental`NumericalFunction
of the symbolic integrand g
and the ?NumericQ
version:
ClearAll[g];
g[x_, y_, z_] := g[x, y, z] = Exp[Sin[x]] + Cos[y + z];
NIntegrate[g[x, y, z], {x, 0, 10}, {y, 0, 10}, {z, 0, 10},
Method -> {"LocalAdaptive", "SymbolicProcessing" -> 0},
PrecisionGoal -> 7,
IntegrationMonitor :> ((numfnSymbolic = First[#]["NumericalFunction"]) &)];
ClearAll[g];
g[x_?NumericQ, y_?NumericQ, z_?NumericQ] := g[x, y, z] = Exp[Sin[x]] + Cos[y + z];
NIntegrate[g[x, y, z], {x, 0, 10}, {y, 0, 10}, {z, 0, 10},
Method -> {"LocalAdaptive", "SymbolicProcessing" -> 0},
PrecisionGoal -> 7,
IntegrationMonitor :> ((numfnNumeric = First[#]["NumericalFunction"]) &)];
Table[numfnSymbolic[x, y, z], {x, 0., 10, 1./8}, {y, 0., 10, 1./8}, {z, 0., 10, 1./8}]
// Flatten // Length // AbsoluteTiming
Table[numfnNumeric[x, y, z], {x, 0., 10, 1./8}, {y, 0., 10, 1./8}, {z, 0., 10, 1./8}]
// Flatten // Length // AbsoluteTiming
(*
{0.747187, 531441}
{3.70936, 531441}
*)
Note this suggests that the difference between the OP's g
and a truly memoized g
with ?NumericQ
should be around 6 sec. Well, it is:
ClearAll[g];
g[x_?NumericQ, y_?NumericQ, z_?NumericQ] := g[x, y, z] = Exp[Sin[x]] + Cos[y + z];
NIntegrate[{g[x, y, z], Sqrt[g[x, y, z]] + x}, {x, 0, 10}, {y, 0, 10}, {z, 0, 10},
Method -> {"LocalAdaptive", "SymbolicProcessing" -> 0},
PrecisionGoal -> 7]; // AbsoluteTiming
(* {16.2901, Null} *)
One caveat: In the Table[]
comparison, I tested just one numerical function (from one integration subregion). I cannot say authoritatively that all numerical functions are the same. (Help, anyone?)
g[x, y, z]
andSqrt[g[x, y, z]] + x
will be independently sampled, and unless the chosen meshes are nearly identical, which is unlikely, memoization will not help. If I am further remembering right this essentially means you cannot really improve the code as forcing regular sampling would defeat the efficiency of adaptive sampling. $\endgroup$NIntegrate
that uses the function evaluations off
only once. A concrete example for using thef
's values only once can be seen in is this package for Lebesgue Integration explained here. $\endgroup${#, g[#]+h[x]}&[f[x]]
"? $\endgroup$