Search code examples
c++mathematical-optimizationcplexmixed-integer-programmingoperations-research

Optimization problem in C++ does not yield the same result as in CPLEX


so far I modelled optimization problems directly in IBM ILOG CPLEX Optimization Studio.

Now, I want to try out using C++ to formulate models while using the CPLEX API.

For this purpose, I started out with a very simple problem, just to learn C++ and to get a feel how to use the CPLEX API.

The problem looks like this in CPLEX:

range j = 1..3;

dvar int+ x[i];
dvar float+ y[j];

maximize -3*x[1] - 2*x[2] + 4*x[3] + y[1] - 3*y[2] - 2*y[3];

subject to {
  c1: -2*x[1] + 4*x[2] - x[3] + 3*y[1] - 2*y[2] + 3*y[3] <= -4;
  c2: -x[1] - 2*x[2] + 4*x[3] + 2*y[1] + 4*y[2] - 5*y[3] <= 2;
  c3: forall(i in i) x[i] <= 6;
}

Now, I simply want to program this same model in C++. Here is my current code:

#include <iostream>
#include "ilcplex//ilocplex.h";
using namespace std;

int main() {

    IloEnv env;
    IloModel Model(env);

    IloIntVar x1(env, 0, IloInfinity, "x1");
    IloIntVar x2(env, 0, IloInfinity, "x2");
    IloIntVar x3(env, 0, IloInfinity, "x3");
    IloNumVar y1(env, 0, IloInfinity, ILOFLOAT, "y1");
    IloNumVar y2(env, 0, IloInfinity, ILOFLOAT, "y2");
    IloNumVar y3(env, 0, IloInfinity, ILOFLOAT, "y3");

    Model.add(IloMaximize(env, -3 * x1 - 2 * x2 + 4 * x3 + y1 - 3 * y2 - 2 * y3));

    Model.add((-2 * x1 + 4 * x2 - x3 + 3 * y1 - 2 * y2 + 3 * y3) <= -4);
    Model.add((-x1 - 2 * x2 + 4 * x3 + 2 * y1 + 4 * y2 - 5 * y3) <= 2);
    Model.add(x1 <= 6);
    Model.add(x2 <= 6);
    Model.add(x3 <= 6);

    IloCplex cplex(Model);

    if (!cplex.solve()) {
        env.error() << "Optimization failed" << endl;
        throw(-1);
    }

    double obj = cplex.getObjValue();
    cout << "\n\n\t objective value: " << obj << endl;  

    cout << "x1 = " << cplex.getValue(x1) << endl;
    cout << "x2 = " << cplex.getValue(x2) << endl;
    cout << "x3 = " << cplex.getValue(x3) << endl;
    cout << "y1 = " << cplex.getValue(y1) << endl;
    cout << "y2 = " << cplex.getValue(y2) << endl;
    cout << "y3 = " << cplex.getValue(y3) << endl;
}

Honestly, I've looked at this for a while now and I can't figure out why these two programs don't yield the same solution.

The objective value of the CPLEX program is 2.429 which is the correct solution. The C++ program prints out optimization failed, meaning that it didn't find a solution.

Does anyone see the mistake I made?


Solution

  • Interesting - I tried this with CPLEX 20.1 and VS2022 on Windows as I needed to refresh my C++ CPLEX stuff anyway. I agree that your code as above is reported as infeasible, but a small change as below then seems to solve fine:

    IloIntVar x1(env, 0, 6, "x1");
    IloIntVar x2(env, 0, 6, "x2");
    IloIntVar x3(env, 0, 6, "x3");
    IloNumVar y1(env, 0, IloInfinity, ILOFLOAT, "y1");
    IloNumVar y2(env, 0, IloInfinity, ILOFLOAT, "y2");
    IloNumVar y3(env, 0, IloInfinity, ILOFLOAT, "y3");
    
    Model.add(IloMaximize(env, -3 * x1 - 2 * x2 + 4 * x3 + y1 - 3 * y2 - 2 * y3));
    
    Model.add((-2 * x1 + 4 * x2 - x3 + 3 * y1 - 2 * y2 + 3 * y3) <= -4);
    Model.add((-x1 - 2 * x2 + 4 * x3 + 2 * y1 + 4 * y2 - 5 * y3) <= 2);
    //Model.add(x1 <= 6);
    //Model.add(x2 <= 6);
    //Model.add(x3 <= 6);
    

    i.e. the change is to set the bounds of those x variables to 6 when declaring them rather than separately via constraints. Here is the output log of the revised model:

    Version identifier: 20.1.0.0 | 2020-11-10 | 9bedb6d68
    Found incumbent of value -6.000000 after 0.00 sec. (0.00 ticks)
    Tried aggregator 1 time.
    Reduced MIP has 2 rows, 6 columns, and 12 nonzeros.
    Reduced MIP has 0 binaries, 3 generals, 0 SOSs, and 0 indicators.
    Presolve time = 0.00 sec. (0.00 ticks)
    Tried aggregator 1 time.
    Reduced MIP has 2 rows, 6 columns, and 12 nonzeros.
    Reduced MIP has 0 binaries, 3 generals, 0 SOSs, and 0 indicators.
    Presolve time = 0.00 sec. (0.00 ticks)
    MIP emphasis: balance optimality and feasibility.
    MIP search method: dynamic search.
    Parallel mode: deterministic, using up to 16 threads.
    Root relaxation solution time = 0.00 sec. (0.00 ticks)
    
            Nodes                                         Cuts/
       Node  Left     Objective  IInf  Best Integer    Best Bound    ItCnt     Gap
    
    *     0+    0                           -6.0000                           0.00%
    *     0+    0                           -1.9524                           0.00%
          0     0        4.0000     1       -1.9524        4.0000        3  304.88%
    *     0+    0                            1.9231        4.0000           108.00%
    *     0+    0                            2.2857        4.0000            75.00%
          0     0        2.5000     2        2.2857    MIRcuts: 1        5    9.37%
    *     0+    0                            2.4286        2.5000             2.94%
          0     0        cutoff              2.4286                      5     ---
    Elapsed time = 0.05 sec. (0.05 ticks, tree = 0.01 MB, solutions = 5)
    
    Mixed integer rounding cuts applied:  1
    
    Root node processing (before b&c):
      Real time             =    0.05 sec. (0.05 ticks)
    Parallel b&c, 16 threads:
      Real time             =    0.00 sec. (0.00 ticks)
      Sync time (average)   =    0.00 sec.
      Wait time (average)   =    0.00 sec.
                              ------------
    Total (root+branch&cut) =    0.05 sec. (0.05 ticks)
    
    
             objective value: 2.42857
    x1 = 4
    x2 = -0
    x3 = 5
    y1 = 0.142857
    y2 = 0
    y3 = 2.85714
    

    I expected that this should generate a very similar model after CPLEX' presolve and give the same results, but it clearly does something different. I'd report this to IBM for further investigation.

    Edit: More digging. It looks like it doesn't like declaring the IloIntVars with an upper bound of IloInfinity. If I export the model as an LP file, I get unexpected bounds on those variables, setting them all to zero:

    \ENCODING=ISO-8859-1
    \Problem name: IloCplex
    
    Maximize
     obj1: - 3 x1 - 2 x2 + 4 x3 + y1 - 3 y2 - 2 y3
    Subject To
     c1: - 2 x1 + 4 x2 - x3 + 3 y1 - 2 y2 + 3 y3 <= -4
     c2: - x1 - 2 x2 + 4 x3 + 2 y1 + 4 y2 - 5 y3 <= 2
     c3: x1 <= 6
     c4: x2 <= 6
     c5: x3 <= 6
    Bounds
          x1 = 0
          x2 = 0
          x3 = 0
    Generals
     x1  x2  x3 
    End
    

    If I change the declarations to the following, it seems to work fine:

    IloNumVar x1(env, 0, IloInfinity, ILOINT, "x1");
    IloNumVar x2(env, 0, IloInfinity, ILOINT, "x2");
    IloNumVar x3(env, 0, IloInfinity, ILOINT, "x3");