Why does NMinimize not follow MaxIterations?

This happens because all NMinimize's global search methods have a post-processing part that does local search. This is documented, see: "Numerical Nonlinear Global Optimization".

Whether to use local search or not is controlled with the method sub-option "PostProcess". If we put "PostProcess" to False, then the max iterations specification is respected (and message of non-convergence might be given.)

Here is a demonstration:

ClearAll[k1, k2, k3];
function[k1_?NumericQ, k2_?NumericQ, k3_?NumericQ] :=  
  Module[{result},
   result = k1 + k2 + k3;
   i = i + 1;
   result
  ];

i = 0; 
NMinimize[{
  function[k1, k2, k3], 
  100 > k1 > 10 && 1000 > k2 > 100 && 1000 > k3 > 10}, {k1, k2, k3}, 
 MaxIterations -> 10, Method -> {"NelderMead", "PostProcess" -> False}]

(* During evaluation of In[33]:= NMinimize::cvmit: Failed to converge to the requested accuracy or precision within 10 iterations. *)

(* {120.425, {k1 -> 10.0201, k2 -> 100.308, k3 -> 10.0967}} *)

i

(* 20 *)

Here is a run of all methods:

Association@
 Table[m -> (i = 0; 
    NMinimize[{function[k1, k2, k3], 
      100 > k1 > 10 && 1000 > k2 > 100 && 1000 > k3 > 10}, {k1, k2, k3}, 
     MaxIterations -> 10, Method -> {m, "PostProcess" -> False}];
    i), {m, {"Automatic", "DifferentialEvolution", "NelderMead", 
    "RandomSearch", "SimulatedAnnealing"}}]

(*
During evaluation of In[48]:= NMinimize::cvmit: Failed to converge to the requested accuracy or precision within 10 iterations.

During evaluation of In[48]:= NMinimize::cvmit: Failed to converge to the requested accuracy or precision within 10 iterations.

During evaluation of In[48]:= NMinimize::cvmit: Failed to converge to the requested accuracy or precision within 10 iterations.

During evaluation of In[48]:= General::stop: Further output of NMinimize::cvmit will be suppressed during this calculation.
*)

(* <|"Automatic" -> 1104, 
     "DifferentialEvolution" -> 1104, 
     "NelderMead" -> 20, 
     "RandomSearch" -> 46000, 
     "SimulatedAnnealing" -> 233|> *)

enter image description here


Maybe Return in the StepMonitor could be used:

ClearAll[k1, k2, k3, step, function];
i = 0;
function[k1_?NumericQ, k2_?NumericQ, k3_?NumericQ] := 
  Module[{result},
   result = k1 + k2 + k3;
   i = i + 1;
   result];

k = 0;
maxsteps = 10;
NMinimize[
 {function[k1, k2, k3],
  100 > k1 > 10 && 1000 > k2 > 100 && 1000 > k3 > 10},
 {k1, k2, k3},
 StepMonitor :> (
   ++k;
   step[k, i] = {k1, k2, k3};  (* optional; for diagnostics below *)
   If[k >= maxsteps,
    Return[{function[k1, k2, k3],
      Thread[Thread[HoldPattern@{k1, k2, k3}] -> {k1, k2, k3}]}, 
     NMinimize]])
 ]
i
(*
  {120.22,
   {HoldPattern[k1] -> 10.0397,
    HoldPattern[k2] -> 100.095, 
    HoldPattern[k3] -> 10.085}}

  995  <-- function calls
*)

The data is step is not particularly interesting but is merely proof-of-concept.

Length@DownValues@step
DownValues@step
(*  10  *)