Skip to content
Snippets Groups Projects
test_LP_solvers.cpp 17.96 KiB
/*
 * Copyright 2015, LAAS-CNRS
 * Author: Andrea Del Prete
 */

#ifdef CLP_FOUND
#include "ClpSimplex.hpp"
#include "CoinTime.hpp"
#include "CoinBuild.hpp"
#include "CoinModel.hpp"
#include <centroidal-dynamics-lib/solver_LP_clp.hh>
#endif

#include <qpOASES.hpp>
#include <centroidal-dynamics-lib/solver_LP_qpoases.hh>
#include <centroidal-dynamics-lib/logger.hh>

#include <iostream>
#include <iomanip>

using namespace std;
using namespace robust_equilibrium;
USING_NAMESPACE_QPOASES

#define EPS 1e-6

#ifdef CLP_FOUND
/** Example addRows.cpp */
void test_addRows()
{
  const int N_ROWS_TO_ADD = 30000;
  try
  {
    // Empty model
    ClpSimplex  model;

    // Objective - just nonzeros
    int objIndex[] = {0, 2};
    double objValue[] = {1.0, 4.0};
    // Upper bounds - as dense vector
    double upper[] = {2.0, COIN_DBL_MAX, 4.0};

    // Create space for 3 columns
    model.resize(0, 3);
    // Fill in
    int i;
    // Virtuous way
    // First objective
    for (i = 0; i < 2; i++)
      model.setObjectiveCoefficient(objIndex[i], objValue[i]);
    // Now bounds (lower will be zero by default but do again)
    for (i = 0; i < 3; i++) {
      model.setColumnLower(i, 0.0);
      model.setColumnUpper(i, upper[i]);
    }
    /*
         We could also have done in non-virtuous way e.g.
         double * objective = model.objective();
         and then set directly
       */
    // Faster to add rows all at once - but this is easier to show
    // Now add row 1 as >= 2.0
    int row1Index[] = {0, 2};
    double row1Value[] = {1.0, 1.0};
    model.addRow(2, row1Index, row1Value,
                 2.0, COIN_DBL_MAX);
    // Now add row 2 as == 1.0
    int row2Index[] = {0, 1, 2};
    double row2Value[] = {1.0, -5.0, 1.0};
    model.addRow(3, row2Index, row2Value,
                 1.0, 1.0);
    // solve
    model.dual();

    /*
         Adding one row at a time has a significant overhead so let's
         try a more complicated but faster way

         First time adding in 10000 rows one by one
       */
    model.allSlackBasis();
    ClpSimplex modelSave = model;
    double time1 = CoinCpuTime();
    int k;
    for (k = 0; k < N_ROWS_TO_ADD; k++) {
      int row2Index[] = {0, 1, 2};
      double row2Value[] = {1.0, -5.0, 1.0};
      model.addRow(3, row2Index, row2Value,
                   1.0, 1.0);
    }
    printf("Time for 10000 addRow is %g\n", CoinCpuTime() - time1);
    model.dual();
    model = modelSave;
    // Now use build
    CoinBuild buildObject;
    time1 = CoinCpuTime();
    for (k = 0; k < N_ROWS_TO_ADD; k++) {
      int row2Index[] = {0, 1, 2};
      double row2Value[] = {1.0, -5.0, 1.0};
      buildObject.addRow(3, row2Index, row2Value,
                         1.0, 1.0);
    }
    model.addRows(buildObject);
    printf("Time for 10000 addRow using CoinBuild is %g\n", CoinCpuTime() - time1);
    model.dual();
    model = modelSave;
    int del[] = {0, 1, 2};
    model.deleteRows(2, del);
    // Now use build +-1
    CoinBuild buildObject2;
    time1 = CoinCpuTime();
    for (k = 0; k < N_ROWS_TO_ADD; k++) {
      int row2Index[] = {0, 1, 2};
      double row2Value[] = {1.0, -1.0, 1.0};
      buildObject2.addRow(3, row2Index, row2Value,
                          1.0, 1.0);
    }
    model.addRows(buildObject2, true);
    printf("Time for 10000 addRow using CoinBuild+-1 is %g\n", CoinCpuTime() - time1);
    model.dual();
    model = modelSave;
    model.deleteRows(2, del);
    // Now use build +-1
    CoinModel modelObject2;
    time1 = CoinCpuTime();
    for (k = 0; k < N_ROWS_TO_ADD; k++) {
      int row2Index[] = {0, 1, 2};
      double row2Value[] = {1.0, -1.0, 1.0};
      modelObject2.addRow(3, row2Index, row2Value,
                          1.0, 1.0);
    }
    model.addRows(modelObject2, true);
    printf("Time for 10000 addRow using CoinModel+-1 is %g\n", CoinCpuTime() - time1);
    model.dual();
    model = ClpSimplex();
    // Now use build +-1
    CoinModel modelObject3;
    time1 = CoinCpuTime();
    for (k = 0; k < N_ROWS_TO_ADD; k++) {
      int row2Index[] = {0, 1, 2};
      double row2Value[] = {1.0, -1.0, 1.0};
      modelObject3.addRow(3, row2Index, row2Value,
                          1.0, 1.0);
    }
    model.loadProblem(modelObject3, true);
    printf("Time for 10000 addRow using CoinModel load +-1 is %g\n", CoinCpuTime() - time1);
    model.writeMps("xx.mps");
    model.dual();
    model = modelSave;
    // Now use model
    CoinModel modelObject;
    time1 = CoinCpuTime();
    for (k = 0; k < N_ROWS_TO_ADD; k++) {
      int row2Index[] = {0, 1, 2};
      double row2Value[] = {1.0, -5.0, 1.0};
      modelObject.addRow(3, row2Index, row2Value,
                         1.0, 1.0);
    }
    model.addRows(modelObject);
    printf("Time for 10000 addRow using CoinModel is %g\n", CoinCpuTime() - time1);
    model.dual();
    model.writeMps("b.mps");
    // Method using least memory - but most complicated
    time1 = CoinCpuTime();
    // Assumes we know exact size of model and matrix
    // Empty model
    ClpSimplex  model2;
    {
      // Create space for 3 columns and 10000 rows
      int numberRows = N_ROWS_TO_ADD;
      int numberColumns = 3;
      // This is fully dense - but would not normally be so
      int numberElements = numberRows * numberColumns;
      // Arrays will be set to default values
      model2.resize(numberRows, numberColumns);
      double * elements = new double [numberElements];
      CoinBigIndex * starts = new CoinBigIndex [numberColumns+1];
      int * rows = new int [numberElements];;
      int * lengths = new int[numberColumns];
      // Now fill in - totally unsafe but ....
      // no need as defaults to 0.0 double * columnLower = model2.columnLower();
      double * columnUpper = model2.columnUpper();
      double * objective = model2.objective();
      double * rowLower = model2.rowLower();
      double * rowUpper = model2.rowUpper();
      // Columns - objective was packed
      for (k = 0; k < 2; k++) {
        int iColumn = objIndex[k];
        objective[iColumn] = objValue[k];
      }
      for (k = 0; k < numberColumns; k++)
        columnUpper[k] = upper[k];
      // Rows
      for (k = 0; k < numberRows; k++) {
        rowLower[k] = 1.0;
        rowUpper[k] = 1.0;
      }
      // Now elements
      double row2Value[] = {1.0, -5.0, 1.0};
      CoinBigIndex put = 0;
      for (k = 0; k < numberColumns; k++) {
        starts[k] = put;
        lengths[k] = numberRows;
        double value = row2Value[k];
        for (int i = 0; i < numberRows; i++) {
          rows[put] = i;
          elements[put] = value;
          put++;
        }
      }
      starts[numberColumns] = put;
      // assign to matrix
      CoinPackedMatrix * matrix = new CoinPackedMatrix(true, 0.0, 0.0);
      matrix->assignMatrix(true, numberRows, numberColumns, numberElements,
                           elements, rows, starts, lengths);
      ClpPackedMatrix * clpMatrix = new ClpPackedMatrix(matrix);
      model2.replaceMatrix(clpMatrix, true);
      printf("Time for 10000 addRow using hand written code is %g\n", CoinCpuTime() - time1);
      // If matrix is really big could switch off creation of row copy
      // model2.setSpecialOptions(256);
    }
    model2.dual();
    model2.writeMps("a.mps");
    // Print column solution
    int numberColumns = model.numberColumns();

    // Alternatively getColSolution()
    double * columnPrimal = model.primalColumnSolution();
    // Alternatively getReducedCost()
    double * columnDual = model.dualColumnSolution();
    // Alternatively getColLower()
    double * columnLower = model.columnLower();
    // Alternatively getColUpper()
    double * columnUpper = model.columnUpper();
    // Alternatively getObjCoefficients()
    double * columnObjective = model.objective();

    int iColumn;

    std::cout << "               Primal          Dual         Lower         Upper          Cost"
              << std::endl;

    for (iColumn = 0; iColumn < numberColumns; iColumn++) {
      double value;
      std::cout << std::setw(6) << iColumn << " ";
      value = columnPrimal[iColumn];
      if (fabs(value) < 1.0e5)
        std::cout << setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value;
      else
        std::cout << setiosflags(std::ios::scientific) << std::setw(14) << value;
      value = columnDual[iColumn];
      if (fabs(value) < 1.0e5)
        std::cout << setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value;
      else
        std::cout << setiosflags(std::ios::scientific) << std::setw(14) << value;
      value = columnLower[iColumn];
      if (fabs(value) < 1.0e5)
        std::cout << setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value;
      else
        std::cout << setiosflags(std::ios::scientific) << std::setw(14) << value;
      value = columnUpper[iColumn];
      if (fabs(value) < 1.0e5)
        std::cout << setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value;
      else
        std::cout << setiosflags(std::ios::scientific) << std::setw(14) << value;
      value = columnObjective[iColumn];
      if (fabs(value) < 1.0e5)
        std::cout << setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value;
      else
        std::cout << setiosflags(std::ios::scientific) << std::setw(14) << value;

      std::cout << std::endl;
    }
    std::cout << "--------------------------------------" << std::endl;
    // Test CoinAssert
    std::cout << "If Clp compiled without NDEBUG below should give assert, if with NDEBUG or COIN_ASSERT CoinError" << std::endl;
    model = modelSave;
    model.deleteRows(2, del);
    // Deliberate error
    model.deleteColumns(1, del + 2);
    // Now use build +-1
    CoinBuild buildObject3;
    time1 = CoinCpuTime();
    for (k = 0; k < N_ROWS_TO_ADD; k++) {
      int row2Index[] = {0, 1, 2};
      double row2Value[] = {1.0, -1.0, 1.0};
      buildObject3.addRow(3, row2Index, row2Value,
                          1.0, 1.0);
    }
    model.addRows(buildObject3, true);
  } catch (CoinError e) {
    e.print();
    if (e.lineNumber() >= 0)
      std::cout << "This was from a CoinAssert" << std::endl;
  }
}

void test_small_LP()
{
  ClpSimplex model;
//  model.setLogLevel(1);

  // Objective - just nonzeros
  int objIndex[] = {0, 2};
  double objValue[] = {1.0, 4.0};
  // Upper bounds - as dense vector
  double upper[] = {2.0, COIN_DBL_MAX, 4.0};

  // Create space for 3 columns
  model.resize(0, 3);

  // Can also use maximumIterations
  int integerValue;
  model.getIntParam(ClpMaxNumIteration, integerValue);
  cout << "Value of ClpMaxNumIteration is " << integerValue << endl;
  model.setMaximumIterations(integerValue);

  // Fill in
  int i;
  // Virtuous way
  // First objective
  for (i = 0; i < 2; i++)
    model.setObjectiveCoefficient(objIndex[i], objValue[i]);
  // Now bounds (lower will be zero by default but do again)
  for (i = 0; i < 3; i++) {
    model.setColumnLower(i, 0.0);
    model.setColumnUpper(i, upper[i]);
  }
  /*
      We could also have done in non-virtuous way e.g.
      double * objective = model.objective();
      and then set directly
    */
  // Faster to add rows all at once - but this is easier to show
  // Now add row 1 as >= 2.0
  int row1Index[] = {0, 2};
  double row1Value[] = {1.0, 1.0};
  model.addRow(2, row1Index, row1Value,
               2.0, COIN_DBL_MAX);
  // Now add row 2 as == 1.0
  int row2Index[] = {0, 1, 2};
  double row2Value[] = {1.0, -5.0, 1.0};
  model.addRow(3, row2Index, row2Value,
               1.0, 1.0);

  int n = model.getNumCols();
  int m = model.getNumRows();
  cout<<"Problem has "<<n<<" variables and "<<m<<" constraints.\n";

  // solve
  model.dual();

  // Check the solution
  if ( model.isProvenOptimal() )
  {
    cout << "Found optimal solution!" << endl;
    cout << "Objective value is " << model.getObjValue() << endl;
    cout << "Model status is " << model.status() << " after "
              << model.numberIterations() << " iterations - objective is "
              << model.objectiveValue() << endl;
    const double *solution;
    solution = model.getColSolution();
    // We could then print the solution or examine it.
    cout<<"Solution is: ";
    for(int i=0; i<n; i++)
      cout<<solution[i]<<", ";
    cout<<endl;
  }
  else
    cout << "Didn’t find optimal solution." << endl;
}
#endif

int main()
{
  cout <<"Test LP Solvers (1 means ok, 0 means error)\n\n";

  {
    cout<<"TEST QP OASES ON A SMALL 2-VARIABLE LP";
    /* Setup data of first LP. */
    real_t A[1*2] = { 1.0, 1.0 };
    real_t g[2] = { 1.5, 1.0 };
    real_t lb[2] = { 0.5, -2.0 };
    real_t ub[2] = { 5.0, 2.0 };
    real_t lbA[1] = { -1.0 };
    real_t ubA[1] = { 2.0 };

    /* Setting up QProblem object with zero Hessian matrix. */
    QProblem example( 2,1,HST_ZERO );

    Options options;
    //options.setToMPC();
    example.setOptions( options );

    /* Solve first LP. */
    int nWSR = 10;
    int res = example.init( 0,g,A,lb,ub,lbA,ubA, nWSR,0 );
    if(res==0)
      cout<<"[INFO] LP solved correctly\n";
    else
      cout<<"[ERROR] QpOases could not solve the LP problem, error code: "<<res<<endl;
  }

  {
    cout<<"\nTEST READ-WRITE METHODS OF SOLVER_LP_ABSTRACT\n";
    Solver_LP_abstract *solverOases = Solver_LP_abstract::getNewSolver(SOLVER_LP_QPOASES);
    const int n = 3;
    const int m = 4;
    const char* filename = "small_3_x_4_LP.dat";
    VectorX c = VectorX::Random(n);
    VectorX lb = -100*VectorX::Ones(n);
    VectorX ub = 100*VectorX::Ones(n);
    MatrixXX A = MatrixXX::Random(m,n);
    VectorX Alb = -100*VectorX::Ones(m);
    VectorX Aub = 100*VectorX::Ones(m);
    if(!solverOases->writeLpToFile(filename, c, lb, ub, A, Alb, Aub))
    {
      SEND_ERROR_MSG("Error while writing LP to file");
      return -1;
    }
    VectorX c2, lb2, ub2, Alb2, Aub2;
    MatrixXX A2;
    if(!solverOases->readLpFromFile(filename, c2, lb2, ub2, A2, Alb2, Aub2))
    {
      SEND_ERROR_MSG("Error while reading LP from file");
      return -1;
    }

    cout<<"Check number of variables: "<<(c.size()==c2.size())<<endl;
    cout<<"Check number of constraints: "<<(A.rows()==A2.rows())<<endl;
    cout<<"Check gradient vector c: "<<c.isApprox(c2)<<endl;
    cout<<"Check lower bound vector lb: "<<lb.isApprox(lb2)<<endl;
    cout<<"Check upper bound vector ub: "<<ub.isApprox(ub2)<<endl;
    cout<<"Check constraint matrix A: "<<A.isApprox(A2)<<endl;
    cout<<"Check constraint lower bound vector Alb: "<<Alb.isApprox(Alb2)<<endl;
    cout<<"Check constraint upper bound vector Aub: "<<Aub.isApprox(Aub2)<<endl;
  }

  {
    cout<<"\nTEST QP OASES ON SOME LP PROBLEMS\n";
    string file_path = "../test_data/";
    Solver_LP_abstract *solverOases = Solver_LP_abstract::getNewSolver(SOLVER_LP_QPOASES);
    const int PROBLEM_NUMBER = 14;
    string problem_filenames[PROBLEM_NUMBER] = {"DLP_findExtremumOverLine20151103_112611",
                                                "DLP_findExtremumOverLine20151103_115627",
                                                "DLP_findExtremumOverLine20151103_014022",
                                                "DLP_findExtremumOverLine_32_generators",
                                                "DLP_findExtremumOverLine_64_generators",
                                                "DLP_findExtremumOverLine_128_generators",
                                                "DLP_findExtremumOverLine_128_generators_bis",
                                                "LP_findExtremumOverLine20151103_112610",
                                                "LP_findExtremumOverLine20151103_112611",
                                                "LP_findExtremumOverLine20151103_014022",
                                                "LP_findExtremumOverLine_32_generators",
                                                "LP_findExtremumOverLine_64_generators",
                                                "LP_findExtremumOverLine_128_generators",
                                                "LP_findExtremumOverLine_128_generators_bis"};
    VectorX c, lb, ub, Alb, Aub, realSol, sol;
    MatrixXX A;
    for(int i=0; i<PROBLEM_NUMBER; i++)
    {
      string &problem_filename = problem_filenames[i];
      if(!solverOases->readLpFromFile(file_path+problem_filename+".dat", c, lb, ub, A, Alb, Aub))
      {
        SEND_ERROR_MSG("Error while reading LP from file "+problem_filename);
        return -1;
      }
      string solution_filename = problem_filename+"_solution";
      if(!readMatrixFromFile(file_path+solution_filename+".dat", realSol))
      {
        SEND_ERROR_MSG("Error while reading LP solution from file "+solution_filename);
        return -1;
      }
      sol.resize(c.size());
      solverOases->solve(c, lb, ub, A, Alb, Aub, sol);
      if(sol.isApprox(realSol, EPS))
      {
        cout<<"[INFO] Solution of problem "<<problem_filename<<" ("<<c.size()<<" var, "<<A.rows()<<" constr) is equal to the expected value!\n";
      }
      else
      {
        if(fabs(c.dot(sol)-c.dot(realSol))<EPS)
          cout<<"[WARNING] Solution of problem "<<problem_filename<<" ("<<c.size()<<" var, "<<A.rows()<<" constr) is different from expected but it has the same cost\n";
        else
        {
          cout<<"[ERROR] Solution of problem "<<problem_filename<<" ("<<c.size()<<" var, "<<A.rows()<<" constr) is different from the expected value:\n";
          cout<<"\tSolution found    "<<sol.transpose()<<endl;
          cout<<"\tExpected solution "<<realSol.transpose()<<endl;
          cout<<"\tCost found    "<<(c.dot(sol))<<endl;
          cout<<"\tCost expected "<<(c.dot(realSol))<<endl;
        }
      }
    }

    return 0;
  }


#ifdef CLP_FOUND
  test_addRows();
  test_small_LP();

  Solver_LP_abstract *solver = Solver_LP_abstract::getNewSolver(SOLVER_LP_CLP);
  Vector3 c, lb, ub, x;
  MatrixXX A(2,3);
  Vector2 Alb, Aub;
  c << 1.0, 0.0, 4.0;
  lb << 0.0, 0.0, 0.0;
  ub << 2.0, COIN_DBL_MAX, 4.0;
  A << 1.0, 0.0, 1.0,
      1.0, -5.0, 1.0;
  Alb << 2.0, 1.0;
  Aub << COIN_DBL_MAX, 1.0;
  if(solver->solve(c, lb, ub, A, Alb, Aub, x)==LP_STATUS_OPTIMAL)
  {
    cout<<"solver_LP_clp solved the problem\n";
    cout<<"The solution is "<<x.transpose()<<endl;
  }
  else
    cout<<"solver_LP_clp failed to solve the problem\n";
#endif

  return 1;
}