Getting info from CPLEX output

I’m using JuMP to solve a large number of instancies with CPLEX and I would like to use the information output by CPLEX such as the number of Flow cuts applied or time spent during branch & bound. Is there any elegant way to get these informations in variables?

As of now, my solution is to redirect my stdout to a file so that CPLEX prints everything there and do a grep (+ cast to Float) on the resulting file. It’s a quick and dirty solution but I didn’t find anything else either in the CPLEX documentation or in the JuMP one.

Is there a better solution?

JuMP is a great way to interact with solvers, such as CPLEX. Loosely speaking, JuMP will “convert” CPLEX’s C API calls into JuMP calls, which can be solver independent (the same function for many solvers) or solver dependent (each solver will have particular functions).
I am not aware of the functionality you are asking for, however, if you find some info about how to do that in CPLEX’s manual we can help you with a (hopefully) nice way to that with JuMP + Julia.

It might be clearer with an example. After running the optimize!() function, CPLEX prints out information such as

julia> optimize!(model)
CPXPARAM_TimeLimit 3600
CPXPARAM_Threads 1
Warning: Non-integral bounds for integer variables rounded.
1 of 1 MIP starts provided solutions.
MIP start ‘m1’ defined initial solution with objective 1003023.1305.
Warning: Non-integral bounds for integer variables rounded.
Tried aggregator 1 time.
MIP Presolve eliminated 20 rows and 0 columns.
MIP Presolve modified 10 coefficients.
Reduced MIP has 1448 rows, 9520 columns, and 45848 nonzeros.
Reduced MIP has 428 binaries, 0 generals, 0 SOSs, and 0 indicators.
Presolve time = 0.00 sec. (27.40 ticks)
Tried aggregator 1 time.
Reduced MIP has 1448 rows, 9520 columns, and 45848 nonzeros.
Reduced MIP has 428 binaries, 0 generals, 0 SOSs, and 0 indicators.
Presolve time = 0.02 sec. (25.30 ticks)
Probing time = 0.00 sec. (6.45 ticks)
Clique table members: 20.
MIP emphasis: balance optimality and feasibility.
MIP search method: dynamic search.
Parallel mode: none, using 1 thread.
Root relaxation solution time = 0.00 sec. (19.96 ticks)

    Nodes                                         Cuts/

Node Left Objective IInf Best Integer Best Bound ItCnt Gap

  • 0+    0                      1003023.1305 -9110496.3688              --- 
    0     0   937733.4054    78  1003023.1305   937733.4054      672    6.51%
    0     0   960438.9696    95  1003023.1305     Cuts: 285      770    4.25%
    0     0   967788.9910    92  1003023.1305     Cuts: 193      842    3.51%
    0     0   972277.3234   113  1003023.1305     Cuts: 134      940    3.07%
    0     0   975773.3675    98  1003023.1305     Cuts: 130     1039    2.72%
    0     0   978571.6876   101  1003023.1305     Cuts: 124     1122    2.44%
    0     0   980584.2773   126  1003023.1305     Cuts: 108     1221    2.24%
    0     0   983789.9356    72  1003023.1305     Cuts: 116     1347    1.92%
    0     0   985012.8528    92  1003023.1305      Cuts: 65     1418    1.80%
    0     0   985850.9575    96  1003023.1305      Cuts: 69     1494    1.71%
    0     0   986708.7299    96  1003023.1305      Cuts: 67     1575    1.63%
    
  • 0+    0                       992729.7930   986708.7299             0.61%
    0     0   986922.4191    74   992729.7930      Cuts: 61     1643    0.58%
    0     0   987047.8536    86   992729.7930      Cuts: 67     1706    0.57%
    0     0   987160.9933    62   992729.7930      Cuts: 46     1771    0.56%
    0     0   987241.1345   101   992729.7930      Cuts: 34     1837    0.55%
    0     0   987461.5478    88   992729.7930      Cuts: 59     1890    0.53%
    0     0   987706.3188   100   992729.7930      Cuts: 72     1989    0.51%
    0     0   987831.4118    83   992729.7930      Cuts: 56     2066    0.49%
    0     0   987919.9779    98   992729.7930      Cuts: 47     2125    0.48%
    0     2   987919.9779    98   992729.7930   987920.8848     2125    0.48%
    

Elapsed time = 1.77 sec. (2902.57 ticks, tree = 0.01 MB, solutions = 2)
30 22 988495.5139 68 992729.7930 988068.2933 2716 0.47%
89 57 990548.1597 51 992729.7930 988253.6533 3398 0.45%
120 70 989589.1727 66 992729.7930 988534.2782 3918 0.42%
178 91 989630.9612 58 992729.7930 988878.3892 4525 0.39%
228 108 992527.9493 50 992729.7930 989338.6592 5330 0.34%
283 124 990903.1868 43 992729.7930 989599.2238 6041 0.32%
360 152 992513.4992 33 992729.7930 989942.6504 6892 0.28%
422 148 cutoff 992729.7930 990394.5056 7779 0.24%
493 155 992336.9089 25 992729.7930 990788.1302 8577 0.20%

Cover cuts applied: 2
Implied bound cuts applied: 7
Flow cuts applied: 107
Mixed integer rounding cuts applied: 41
Flow path cuts applied: 39

Root node processing (before b&c):
Real time = 1.77 sec. (2896.93 ticks)
Sequential b&c:
Real time = 1.67 sec. (3203.84 ticks)
------------
Total (root+branch&cut) = 3.44 sec. (6100.77 ticks)

I would like to access this information.
There’s no function directly in the CPLEX API for C that does this as far as I’m aware, so it’s probably not just me missing a JuMP function or a missing bridge.

It’s probably local variables that are destroyed when CPLEX is done running. As these are not Julia variables in the first place, I have little hope that it’s possible to retrieve these information in a neat way but I might be wrong.

As I heard it in a CPLEX workshop: “If it’s not in the C API, then it doesn’t exist.”

If it’s in the C API (either as a specific function or by querying the right attribute), then it should be in the Julia wrapper as well.

1 Like

In the C API you can retrieve the number of applied cuts with CPXgetnumcuts( CPXCENVptr env, CPXCLPptr lp, int cuttype, int * num_p ). However, it is not well documented in JuMP.jl and CPLEX.jl how to obtain the pointers to “env” and “lp”. You can try to find some function which is supported by JuMP and CPLEX here (e.g., CPXgetmiprelgap should be supported) and look up how it is done in the source code of the packages.
If you come up with an solution please share it.

To reach the env and lp pointers, the simplest is to use direct mode, then make your way through the various layers until you reach the CPLEX objects (technically, pointers to the CPLEX objects)

jump_model = JuMP.direct_model(CPLEX.Optimizer())  # JuMP
moi_model = backend(jump_model)                    # MOI
cplex_model = moi_model.inner                      # CPLEX.jl
env, lp = cplex_model.env, cplex_model.lp          # CPLEX (C api)

In most cases, you should be able to query the information you want from cplex_model directly, since most (if not all) C api functions are wrapped in Julia.

Finally, to query the number of cuts, call CPXgetnumcuts (as wrapped in CPLEX.jl) as @mike_k pointed out.

The strategy is similar if you want to access a different attribute.

4 Likes

I wrote a simple function which does the job using CPXgetnumcuts and the full defines of CPLEX.jl for CPLEX version 12.9, and direct_model in JuMP.

function cpx_get_num_cuts( model::Model, cuttype::Int32 )
    num_cuts::Int32 = -1
    moi_model       = backend( model )                     # MOI
    cplex_model     = moi_model.inner                      # CPLEX.jl
    num_cuts        = CPLEX.get_num_cuts( cplex_model, cuttype )
    return num_cuts::Int32
end

After solving simply call, e.g.,

foo = cpx_get_num_cuts( model, CPLEX.CPX_CUT_FRAC )

to obtain the number of added Gomory fractional cuts.

3 Likes