The Monte Carlo method
While the Monte Carlo method is not terribly accurate, it is fairly easy to understand. I don't know why the OP did not include the very useful information in the NIntegrate::maxp
warning about the error estimate (the partitioning is suggested by the domain defined by the integrand; see below):
NIntegrate[
Boole[(con1 && con2) || (con3 && con4 && con5)],
{t, 0, 2 Pi}, {u, 0, 2 Pi},
PrecisionGoal -> 30,
Method -> {"MonteCarlo",
"MaxPoints" -> 50000, (* default *)
"Partitioning" -> {8, 8}}] (* seems helpful *)
NIntegrate::maxp
: The integral failed to converge after 50100 integrand evaluations. NIntegrate obtained 32.539178786473315`
and 0.02103113210212079`
for the integral and error estimates.
(* 32.5392 *)
So we know how accurate NIntegrate
calculates the integral to be. If you believe it, it answers the OP's question, because the other integral is much farther away from the integral 32.539
than 0.021
. We will discuss the OP's result of 32.4243
and why the other integral is less reliable below. First let's investigate the reliability of the Monte Carlo result. One nice(?) thing about Method -> "MonteCarlo"
is that it does not BlockRandom[]
the random number generator, so successive calls behave as a random variable. (You can control the reproducibility of the method through the "RandomSeed"
suboption.) On a quick integral like this one, we can investigate the variability of the method.
(* Takes about 6.2 sec for me *)
Through[{Mean, StandardDeviation, MeanCI, MinMax, # &}@
Table[Quiet@
NIntegrate[Boole[(con1 && con2) || (con3 && con4 && con5)],
{t, 0, 2 Pi}, {u, 0, 2 Pi},
PrecisionGoal -> 30,
Method -> {"MonteCarlo",
"MaxPoints" -> 50000, "Partitioning" -> {8, 8}}],
{100}]] // Labeled[
Histogram[Last@#, 9],
Grid@Transpose@{{"Mean", "S.D.", "C.I.", "Range"}, Most@#},
Right] &
About the OP's 32.4243
:
Without the "Partitioning"
, the range in one trial of 100 runs was {32.3329, 32.6765}
, which contains the OP's result, and the error estimate on a single run was about 0.067
corresponding with the wider range. The confidence interval was {32.4917, 32.5185}
, which does not contain the OP's result.
Another consistency test is to raise the number of sample points. We know that the error in Monte Carlo should be proportional to 1/Sqrt[n]
for n
sample points.
NIntegrate[Boole[(con1 && con2) || (con3 && con4 && con5)],
{t, 0, 2 Pi}, {u, 0, 2 Pi},
PrecisionGoal -> 30,
Method -> {"MonteCarlo", "MaxPoints" -> 1000000}]
NIntegrate::maxp
: The integral failed to converge after 1000100 integrand evaluations. NIntegrate obtained 32.51101572370637`
and 0.015049739619971662`
for the integral and error estimates.
(* 32.511 *)
If we compare the error estimate of 0.67
for n = 50000
with the estimate 0.15
for n = 1000000
, we calculate
0.067 Sqrt[50000/1000000]
(* 0.01498... *)
We don't expect strict equality because of the random sampling, but they're very close. The consistency supports the validity of the error estimates.
The (un)reliability of the Multidimensional Rule here
The theory supporting the default integration methods of NIntegrate[]
, such as the Multidimensional Rule used in the second integral in the OP, assume the integrand is analytic over the domain or at least within the interior of it. When there are singularities, a singularity handler may be invoked, but only when the singularity can be identified. If a singularity is not handled, one may expect the initial estimates for the integral and error to be inaccurate. With enough recursion in the global adaptive algorithm, the singularities might be confined to a small enough region that the error over that region contributes negligibly to the overall error. This is easiest when the singularities line up with coordinate boundaries.
In the OP's integral we have a bad singularity (jump discontinuity) along a complicated boundary. I don't think NIntegrate
succeeds in partitioning the domain along this boundary.
Here is the region whose area the integral calculates:
(* Takes about 3 sec for me *)
bmesh = BoundaryDiscretizeRegion[
ImplicitRegion[(con1 && con2) || (con3 && con4 && con5),
{{t, 0, 2 Pi}, {u, 0, 2 Pi}}],
MaxCellMeasure -> "Area" -> 0.001];
Show[bmesh,
GridLines -> {Subdivide[0, 2 Pi, 8], Subdivide[0, 2 Pi, 8]},
Method -> {"GridLinesInFront" -> True}, Frame -> True]
Why "Partitioning" -> {8, 8}
helps:
We can see why "Partitioning" -> {8, 8}
helps in the figure above. The squares at are all white or all blue have zero error, estimated and actual.
Region-based integration
By the way, we get another estimate of the integral from bmesh
above:
Area@bmesh
(* 32.5073 *)
If we look at the region-based integration in NIntegrate[]
, there is no recursive refinement, based on error estimation of the integral. I don't believe there is a numerical error estimate at all, because there is only one integration rule applied and nothing to compare it to (some day, this assumption will be wrong, maybe today, but I think not yet). Therefore no error messages.
We can see below in these codes based on the other answers that PrecisionGoal
is used in DiscretizeRegion
. I do not know what error measure this controls. It is not related to NIntegrate
and it does not seem to be related to the area of the region. Note also that the meshing routines work at machine precision. So while WorkingPrecision
does not have its usual effect, it does change PrecisionGoal
in NIntegration
, which NIntegrate
passes along to DiscretizeRegion
.
(* These take 5-10 sec each *)
ir = ImplicitRegion[{(con1 && con2) || (con3 && con4 && con5),
0 <= u <= 2 Pi, 0 <= t <= 2 Pi}, {u, t}];
dr1 = Trace[
NIntegrate[
Boole[(con1 && con2) || (con3 && con4 && con5)],
{u, t} \[Element] ir, WorkingPrecision -> 16],
dr_DiscretizeRegion :> Return[Hold[dr], Trace],
TraceInternal -> True]
(*
Hold[DiscretizeRegion[ImplicitRegion[<<1>>,{u,t}],
PrecisionGoal -> 7., AccuracyGoal -> \[Infinity]]]
*)
dr2 = Trace[
NIntegrate[
Boole[(con1 && con2) || (con3 && con4 && con5)], {u, t} \[Element]
ir, WorkingPrecision -> 20],
dr_DiscretizeRegion :> Return[Hold[dr], Trace],
TraceInternal -> True];
(*
Hold[DiscretizeRegion[ImplicitRegion[<<1>>,{u,t}],
PrecisionGoal -> 11., AccuracyGoal -> \[Infinity]]]
*)
dr3 = Trace[
NIntegrate[1,
{t, u} \[Element]
ImplicitRegion[(con1 && con2) || (con3 && con4 && con5),
{{t, 0, 2 Pi}, {u, 0, 2 Pi}}]],
dr_DiscretizeRegion :> Return[Hold[dr], Trace],
TraceInternal -> True];
(*
Hold[DiscretizeRegion[ImplicitRegion[<<1>>,{u,t}],
PrecisionGoal -> 7., AccuracyGoal -> \[Infinity]]]
*)
There is a limit to PrecisionGoal
in DiscretizeRegion[]
. If you set it too high (e.g. 14
), you get this message:
DiscretizeRegion::drtol
: Tolerance requested by the AccuracyGoal and PrecisionGoal options is too small to be achieved. Increasing to absolute tolerance 1.324082296537885`*^-7
One can also get this message in the following:
NIntegrate[1, {u, t} \[Element] ir, WorkingPrecision -> 32]
Note that PrecisionGoal -> 13
results in a computation that ran for several minutes before I killed it. You can reproduce the effect in NIntegrate[]
as follows:
NIntegrate[1, {u, t} \[Element] ir, WorkingPrecision -> 13 + 9]
Update: Local adaptive integration
I thought about it earlier, but spent too much time on the above. But something about the region suggested it would be good to try the "LocalAdaptive"
method:
PrintTemporary@Dynamic@Clock@Infinity;
AbsoluteTime[];
NIntegrate[
Boole[(con1 && con2) || (con3 && con4 && con5)],
{t, 0, 2 Pi}, {u, 0, 2 Pi},
MaxRecursion -> 20,
PrecisionGoal -> 6,
Method -> "LocalAdaptive"]
AbsoluteTime[] - %%
(* PrecisionGoal -> integral (* time (sec) *)
6 -> 32.50761472315272` (* 5+s *)
7 -> 32.50798110021814` (* 6s *)
8 -> 32.508212967539805` (* 9s *)
9 -> 32.5082805861034` (* 16s *)
10 -> 32.50829781640405` (* 38s *)
11 -> 32.50830725623989` (* 90s *)
12 -> 32.508309526765856` (* 339s *)
13 -> 32.50830998844479` (* 1040s *)
*)
We can see the rather slow convergence does not quite match the PrecisionGoal
, but the convergence is steady.