<p dir="auto">L-BFGS-B does not perform the requested number of iterations for less than n&lt;25 iterations.  It also calls the callback an incorrect (and different!) number of iterations.</p>
<p dir="auto">A sample code,</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="from itertools import count
from scipy.optimize import minimize, rosen

c = count()
def callback(x):
    next(c)

n=25
res = minimize(rosen, (0,0,0), options={'disp': True, 'maxiter': n}, method='L-BFGS-B', callback=callback)
print(res)
print(c)"><pre class="notranslate"><span class="pl-k">from</span> <span class="pl-s1">itertools</span> <span class="pl-k">import</span> <span class="pl-s1">count</span>
<span class="pl-k">from</span> <span class="pl-s1">scipy</span>.<span class="pl-s1">optimize</span> <span class="pl-k">import</span> <span class="pl-s1">minimize</span>, <span class="pl-s1">rosen</span>

<span class="pl-s1">c</span> <span class="pl-c1">=</span> <span class="pl-en">count</span>()
<span class="pl-k">def</span> <span class="pl-en">callback</span>(<span class="pl-s1">x</span>):
    <span class="pl-en">next</span>(<span class="pl-s1">c</span>)

<span class="pl-s1">n</span><span class="pl-c1">=</span><span class="pl-c1">25</span>
<span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-en">minimize</span>(<span class="pl-s1">rosen</span>, (<span class="pl-c1">0</span>,<span class="pl-c1">0</span>,<span class="pl-c1">0</span>), <span class="pl-s1">options</span><span class="pl-c1">=</span>{<span class="pl-s">'disp'</span>: <span class="pl-c1">True</span>, <span class="pl-s">'maxiter'</span>: <span class="pl-s1">n</span>}, <span class="pl-s1">method</span><span class="pl-c1">=</span><span class="pl-s">'L-BFGS-B'</span>, <span class="pl-s1">callback</span><span class="pl-c1">=</span><span class="pl-s1">callback</span>)
<span class="pl-en">print</span>(<span class="pl-s1">res</span>)
<span class="pl-en">print</span>(<span class="pl-s1">c</span>)</pre></div>
<p dir="auto">The behavior is incorrect for n&lt;25, for example n=3 produces</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="    RUNNING THE L-BFGS-B CODE
    
               * * *
    
    Machine precision = 2.220D-16
     N =            3     M =           10
     This problem is unconstrained.
    
    At X0         0 variables are exactly at the bounds
    
    At iterate    0    f=  2.00000D+00    |proj g|=  2.00000D+00
    
    At iterate    1    f=  1.95917D+00    |proj g|=  2.14559D+00
    
    At iterate    2    f=  1.61222D+00    |proj g|=  4.47056D+00
    
    At iterate    3    f=  1.57410D+00    |proj g|=  6.85382D+00
    
    At iterate    4    f=  1.43040D+00    |proj g|=  6.93083D+00
    
    At iterate    5    f=  1.22860D+00    |proj g|=  1.52933D+01 &lt;= 5 iters
    
               * * *
    
    Tit   = total number of iterations
    Tnf   = total number of function evaluations
    Tnint = total number of segments explored during Cauchy searches
    Skip  = number of BFGS updates skipped
    Nact  = number of active bounds at final generalized Cauchy point
    Projg = norm of the final projected gradient
    F     = final function value
    
               * * *
    
       N    Tit     Tnf  Tnint  Skip  Nact     Projg        F
        3      5     12      1     0     0   1.529D+01   1.229D+00
      F =   1.22859593979883                                     &lt;= final F
    
    STOP: TOTAL NO. of ITERATIONS EXCEEDS LIMIT
    
     Cauchy                time 0.000E+00 seconds.
     Subspace minimization time 0.000E+00 seconds.
     Line search           time 0.000E+00 seconds.
    
     Total User time 0.000E+00 seconds.
    
          fun: 1.2285959397988266
     hess_inv: &lt;3x3 LbfgsInvHessProduct with dtype=float64&gt;
          jac: array([ 10.82081735, -15.2932714 ,   5.76619654])
      message: b'STOP: TOTAL NO. of ITERATIONS EXCEEDS LIMIT'
         nfev: 48
          nit: 4                                                 &lt;= internal profiling only counts 4 iters
       status: 1
      success: False
            x: array([ 0.52583399,  0.22054671,  0.07747183])

    count(4)                                                     &lt;= callback only called 4 times"><pre class="notranslate"><code class="notranslate">    RUNNING THE L-BFGS-B CODE
    
               * * *
    
    Machine precision = 2.220D-16
     N =            3     M =           10
     This problem is unconstrained.
    
    At X0         0 variables are exactly at the bounds
    
    At iterate    0    f=  2.00000D+00    |proj g|=  2.00000D+00
    
    At iterate    1    f=  1.95917D+00    |proj g|=  2.14559D+00
    
    At iterate    2    f=  1.61222D+00    |proj g|=  4.47056D+00
    
    At iterate    3    f=  1.57410D+00    |proj g|=  6.85382D+00
    
    At iterate    4    f=  1.43040D+00    |proj g|=  6.93083D+00
    
    At iterate    5    f=  1.22860D+00    |proj g|=  1.52933D+01 &lt;= 5 iters
    
               * * *
    
    Tit   = total number of iterations
    Tnf   = total number of function evaluations
    Tnint = total number of segments explored during Cauchy searches
    Skip  = number of BFGS updates skipped
    Nact  = number of active bounds at final generalized Cauchy point
    Projg = norm of the final projected gradient
    F     = final function value
    
               * * *
    
       N    Tit     Tnf  Tnint  Skip  Nact     Projg        F
        3      5     12      1     0     0   1.529D+01   1.229D+00
      F =   1.22859593979883                                     &lt;= final F
    
    STOP: TOTAL NO. of ITERATIONS EXCEEDS LIMIT
    
     Cauchy                time 0.000E+00 seconds.
     Subspace minimization time 0.000E+00 seconds.
     Line search           time 0.000E+00 seconds.
    
     Total User time 0.000E+00 seconds.
    
          fun: 1.2285959397988266
     hess_inv: &lt;3x3 LbfgsInvHessProduct with dtype=float64&gt;
          jac: array([ 10.82081735, -15.2932714 ,   5.76619654])
      message: b'STOP: TOTAL NO. of ITERATIONS EXCEEDS LIMIT'
         nfev: 48
          nit: 4                                                 &lt;= internal profiling only counts 4 iters
       status: 1
      success: False
            x: array([ 0.52583399,  0.22054671,  0.07747183])

    count(4)                                                     &lt;= callback only called 4 times
</code></pre></div>
<p dir="auto">If the optimization does not finish early, L-BFGS-B runs 1 iteration more than requested.</p>
<h3 dir="auto">Reproducing code example:</h3>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="import itertools
from scipy import optimize

def f(x):
  x, y = x
  return (1 - x)**2 + 100 * (y - x**2)**2  # Rosenbrock function

counter = itertools.count()

def c(x):
  print next(counter), x, f(x)

optimize.minimize(f, [0, 0], method='l-bfgs-b', callback=c,
                  options={'maxiter': 5})

# Output
0 [  1.74855120e-01  -8.73576809e-08] 0.774343468431
1 [ 0.26113558  0.04906611] 0.582499810584
2 [ 0.32394677  0.11056373] 0.460208907475
3 [ 0.4240506   0.16523825] 0.352977278528
4 [ 0.51688677  0.23667522] 0.326403357767
5 [ 0.52242281  0.26249422] 0.238961333065 &lt;-- This should be the optimized function value
     nit: 6  &lt;-- This is already one too many
 success: False
 message: 'STOP: TOTAL NO. of ITERATIONS EXCEEDS LIMIT'
       x: array([ 0.60246124,  0.35628737])
     fun: 0.16248886013148453 &lt;-- But this is less than reported in the last callback.
     jac: array([ 0.81281454, -1.33443389])
    nfev: 30
  status: 1"><pre class="notranslate"><code class="notranslate">import itertools
from scipy import optimize

def f(x):
  x, y = x
  return (1 - x)**2 + 100 * (y - x**2)**2  # Rosenbrock function

counter = itertools.count()

def c(x):
  print next(counter), x, f(x)

optimize.minimize(f, [0, 0], method='l-bfgs-b', callback=c,
                  options={'maxiter': 5})

# Output
0 [  1.74855120e-01  -8.73576809e-08] 0.774343468431
1 [ 0.26113558  0.04906611] 0.582499810584
2 [ 0.32394677  0.11056373] 0.460208907475
3 [ 0.4240506   0.16523825] 0.352977278528
4 [ 0.51688677  0.23667522] 0.326403357767
5 [ 0.52242281  0.26249422] 0.238961333065 &lt;-- This should be the optimized function value
     nit: 6  &lt;-- This is already one too many
 success: False
 message: 'STOP: TOTAL NO. of ITERATIONS EXCEEDS LIMIT'
       x: array([ 0.60246124,  0.35628737])
     fun: 0.16248886013148453 &lt;-- But this is less than reported in the last callback.
     jac: array([ 0.81281454, -1.33443389])
    nfev: 30
  status: 1
</code></pre></div>
<h3 dir="auto">Scipy/Numpy/Python version information:</h3>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="('0.15.1',
 '1.11.1',
 sys.version_info(major=2, minor=7, micro=6, releaselevel='final', serial=0))"><pre class="notranslate"><code class="notranslate">('0.15.1',
 '1.11.1',
 sys.version_info(major=2, minor=7, micro=6, releaselevel='final', serial=0))
</code></pre></div>
<p dir="auto">The wrong logic is <a href="https://github.com/scipy/scipy/blob/v0.19.1/scipy/optimize/lbfgsb.py#L317">here</a>. It is probably enough to increase the <code class="notranslate">n_iterations</code> counter (and call the callback!) <strong>before</strong> checking it against <code class="notranslate">maxiter</code>. And the check should be a <code class="notranslate">&lt;=</code>, not a <code class="notranslate">&lt;</code>.</p>