Reading the Iteration Log

Every ConicIP solve can narrate itself. Passing verbose = true to conicIP prints one row per interior-point iteration, so you can watch the residuals fall, the duality gap close, and the infeasibility screens stay quiet. This page reads one such log column by column.

The solver always works on the same problem,

minimize    ½yᵀQy - cᵀy
subject to  Ay ≥_K b
            Gy  = d

where ≥_K means Ay - b lies in the cone described by cone_dims. The log is a solver-level feature, so we call conicIP directly rather than going through JuMP. All data below is deterministic, so the output is reproducible.

A small quadratic program

Minimize a strictly convex quadratic over the nonnegative orthant:

using ConicIP, SparseArrays, LinearAlgebra

Q = [2.0 0.5 0.0
     0.5 2.0 0.5
     0.0 0.5 2.0]
c = [1.0, 2.0, 3.0]

# Nonnegativity: y ≥ 0
A = sparse(1.0I, 3, 3)
b = zeros(3)
cone_dims = [("R", 3)]

sol = conicIP(Q, c, A, b, cone_dims; verbose = true)
println("status: ", sol.status)
 > KKT solver: kktsolver_qr (auto, 3.3 nnz/col)

 > INTERIOR POINT SOLVER v0.7 (July 2016)

            Optimality                      Objective              Infeasibility

   Iter  │  prFeas    duFeas    muFeas   │  pobj      dobj      │  icertp    icertd   │  refine   
      1  │  0.0e+00   7.0e-01   2.5e-01  │  -2.5e+00  -4.6e+00  │  NaN       2.5e+00  │  0
      2  │  2.3e-16   5.2e-02   8.8e-03  │  -2.8e+00  -2.9e+00  │  NaN       2.5e+00  │  1
      3  │  2.2e-16   4.2e-04   9.9e-05  │  -2.8e+00  -2.8e+00  │  NaN       2.5e+00  │  1
      4  │  5.6e-17   4.2e-06   9.9e-07  │  -2.8e+00  -2.8e+00  │  NaN       2.5e+00  │  1
      5  │  1.1e-16   4.2e-08   9.9e-09  │  -2.8e+00  -2.8e+00  │  NaN       NaN      │  1

 > EXIT -- Below Tolerance!

status: Optimal

The three optimality columns

The first block of the log tracks the three residuals that define convergence — constraint satisfaction, KKT stationarity, and complementary slackness — each scaled by the size of the data:

‖Ay - s - b‖ / (1 + ‖b‖)      primal feasibility
‖Qy + Gᵀw - Aᵀv - c‖ / (1 + ‖c‖)   dual feasibility
sᵀv / (1 + |cᵀy|)             complementarity

The run above needs five iterations to drive all three below optTol (default 1e-6), gaining about two digits per iteration once the predictor–corrector steps take hold. The same three numbers are returned in the Solution struct:

(prFeas = sol.prFeas, duFeas = sol.duFeas, muFeas = sol.muFeas)
(prFeas = 1.1102230246251565e-16, duFeas = 4.1638812444707854e-8, muFeas = 9.9436527988326e-9)

The objective columns

pobj is ½yᵀQy - cᵀy at the current iterate; dobj is the value of the dual objective there. Weak duality puts dobj ≤ pobj for any feasible pair, and the two squeeze together as complementarity closes. Watching them converge is the quickest read on how far along a solve is:

(pobj = sol.pobj, dobj = sol.dobj, gap = sol.pobj - sol.dobj)
(pobj = -2.7857142857142834, dobj = -2.7857143956857047, gap = 1.0997142130264592e-7)

The infeasibility columns

icertp and icertd are the screens for primal and dual infeasibility — cheap per-iteration tests that ask whether the current iterate looks like a Farkas ray (icertp) or a recession ray (icertd). Each is a scaled residual that has to fall below infeasTol (default 1e-7) before the solver will even consider the iterate as a certificate candidate.

A screen prints NaN when the sign condition that makes a ray meaningful fails — dᵀw - bᵀv < 0 for icertp, cᵀy > 0 for icertd. On a solvable problem like this one both columns should stay NaN or stay large, which is exactly what happens above. Falling below the tolerance only nominates a ray; the claim is made by a separate validator run against the original data. See The Certificate Pipeline for the full story, and Detecting Infeasibility for logs where these columns do fire.

The refinement column

refine counts the iterative-refinement steps spent on that iteration's KKT solve, capped by maxRefinementSteps (default 3). Zero on the first iteration and one thereafter is the healthy pattern. If the refined residual is still large the whole row is printed in red — a signal that the KKT system is badly conditioned and that staticReg or a different kktsolver may be needed.

The exit line

The log ends with a one-line verdict. EXIT -- Below Tolerance! means all three optimality residuals cleared optTol, and the status returned is :Optimal. The other exits — Certificate of Infeasiblity Found!, Certificate of Dual Infeasibility Found!, and Error! — are the subject of the next page.

The same log across cones

Nothing about the log is specific to the nonnegative orthant. Here is a second-order cone program — maximize 1ᵀy over the unit ball, whose answer is 1/√3 in each coordinate — with a cone block ("Q", 4) in place of ("R", 3):

n = 3
Qsoc = spzeros(n, n)                # no quadratic term
csoc = ones(n)                      # maximize 1ᵀy

# SOC block: (1, y₁, y₂, y₃) ∈ Q⁴, i.e. ‖y‖ ≤ 1
Asoc = [spzeros(1, n); sparse(1.0I, n, n)]
bsoc = [-1.0; zeros(n)]

solsoc = conicIP(Qsoc, csoc, Asoc, bsoc, [("Q", n + 1)]; verbose = true)
println("status: ", solsoc.status)
 > KKT solver: kktsolver_qr (auto, 1.0 nnz/col)

 > INTERIOR POINT SOLVER v0.7 (July 2016)

            Optimality                      Objective              Infeasibility

   Iter  │  prFeas    duFeas    muFeas   │  pobj      dobj      │  icertp    icertd   │  refine   
      1  │  8.7e-01   0.0e+00   1.1e+00  │  -3.0e+00  -1.2e+01  │  NaN       1.6e+00  │  0
      2  │  0.0e+00   0.0e+00   1.9e-01  │  -1.6e+00  -2.1e+00  │  NaN       1.1e+00  │  1
      3  │  4.0e-16   2.3e-16   2.0e-03  │  -1.7e+00  -1.7e+00  │  NaN       1.0e+00  │  1
      4  │  8.3e-16   1.5e-14   2.0e-05  │  -1.7e+00  -1.7e+00  │  NaN       1.0e+00  │  1
      5  │  0.0e+00   1.4e-16   2.0e-07  │  -1.7e+00  -1.7e+00  │  NaN       NaN      │  1

 > EXIT -- Below Tolerance!

status: Optimal

The optimizer, as expected:

round.(solsoc.y, digits = 4)
3-element Vector{Float64}:
 0.5774
 0.5774
 0.5774

Same columns, same shape, same exit line: the Nesterov–Todd scaling makes the second-order cone look like the orthant to the outer loop. Mixing cone blocks — [("R", 3), ("Q", 4)] — changes nothing about how the log reads.

Where to go next