Program Listing for File mathopt.cpp

Return to documentation for file (src/mathopt/mathopt.cpp)

/* #############################################
 *             This file is part of
 *                    ZERO
 *
 *             Copyright (c) 2020
 *     Released under the Creative Commons
 *         CC BY-NC-SA 4.0 License
 *
 *              Find out more at
 *        https://github.com/ds4dm/ZERO
 * #############################################*/

#include "mathopt/mathopt.h"


unsigned int MathOpt::convexHull(const std::vector<arma::sp_mat *> *Ai,
                                            const std::vector<arma::vec *> *   bi,
                                            arma::sp_mat &                     A,
                                            arma::vec &                        b,
                                            const arma::sp_mat &               Acom,
                                            const arma::vec &                  bcom) {
  // Count number of polyhedra and the space we are in!
  const unsigned int nPoly{static_cast<unsigned int>(Ai->size())};
  // Error check
  ZEROAssert(nPoly > 0);
  // consider
  const unsigned int nC{static_cast<unsigned int>(Ai->front()->n_cols)};
  const unsigned int nComm{static_cast<unsigned int>(Acom.n_rows)};

  ZEROAssert(!(nComm > 0 && Acom.n_cols != nC));
  ZEROAssert(!(nComm > 0 && nComm != bcom.n_rows));
  ZEROAssert(nPoly == bi->size());

  // Count the number of variables in the convex hull.
  unsigned int nFinCons{0}, nFinVar{0};

  for (unsigned int i = 0; i != nPoly; i++) {

     ZEROAssert(Ai->at(i)->n_cols == nC);
     ZEROAssert(Ai->at(i)->n_rows == bi->at(i)->n_rows);
     nFinCons += Ai->at(i)->n_rows;
  }
  // For common constraint copy
  nFinCons += nPoly * nComm;

  const unsigned int FirstCons = nFinCons;

  // 2nd constraint in Eqn 4.31 of Conforti et al. - twice so we have 2 ineq instead of
  // 1 eq constr
  nFinCons += nC * 2;
  // 3rd constr in Eqn 4.31. Again as two ineq constr.
  nFinCons += 2;
  // Common constraints
  // nFinCons += Acom.n_rows;

  nFinVar = nPoly * nC + nPoly + nC; // All x^i variables + delta variables+ original x variables
  A.zeros(nFinCons, nFinVar);
  b.zeros(nFinCons);
  // A.zeros(nFinCons, nFinVar); b.zeros(nFinCons);
  // Implements the first constraint more efficiently using better constructors
  // for sparse matrix
  MathOpt::compConvSize(A, nFinCons, nFinVar, Ai, bi, Acom, bcom);

  bool printEvery = true;
  if (nPoly > 10)
     printEvery = false;

  // Counting rows completed
  /****************** SLOW LOOP BEWARE *******************/
  for (unsigned int i = 0; i < nPoly; i++) {

     if (printEvery || i % 10 == 0)
        LOG_S(3) << "MathOpt::convexHull: Handling Polyhedron " << i + 1 << " out of " << nPoly;
     for (unsigned int j = 0; j < nC; j++) {
        A.at(FirstCons + 2 * j, nC + (i * nC) + j)     = 1;
        A.at(FirstCons + 2 * j + 1, nC + (i * nC) + j) = -1;
     }
     // Third constraint in (4.31)
     A.at(FirstCons + nC * 2, nC + nPoly * nC + i)     = 1;
     A.at(FirstCons + nC * 2 + 1, nC + nPoly * nC + i) = -1;
  }
  /****************** SLOW LOOP BEWARE *******************/
  // Second Constraint RHS
  for (unsigned int j = 0; j < nC; j++) {
     A.at(FirstCons + 2 * j, j)     = -1;
     A.at(FirstCons + 2 * j + 1, j) = 1;
  }
  // Third Constraint RHS
  b.at(FirstCons + nC * 2)     = 1;
  b.at(FirstCons + nC * 2 + 1) = -1;
  LOG_S(1) << "MathOpt::convexHull: Done";
  return nPoly;
}

void MathOpt::compConvSize(arma::sp_mat &                     A,
                                    const unsigned int                 nFinCons,
                                    const unsigned int                 nFinVar,
                                    const std::vector<arma::sp_mat *> *Ai,
                                    const std::vector<arma::vec *> *   bi,
                                    const arma::sp_mat &               Acom,
                                    const arma::vec &                  bcom) {
  const unsigned int nPoly{static_cast<unsigned int>(Ai->size())};
  const unsigned int nC{static_cast<unsigned int>(Ai->front()->n_cols)};
  unsigned int       N{0}; // Total number of nonzero elements in the final matrix
  const unsigned int numCommon{static_cast<unsigned int>(Acom.n_nonzero + bcom.n_rows)};
  for (unsigned int i = 0; i < nPoly; i++) {
     N += Ai->at(i)->n_nonzero;
     N += bi->at(i)->n_rows;
  }
  N += numCommon * nPoly; // The common constraints have to be copied for each polyhedron.

  // Now computed N which is the total number of nonzeros.
  arma::umat locations; // location of nonzeros
  arma::vec  val;       // nonzero values
  locations.zeros(2, N);
  val.zeros(N);

  unsigned int count{0}, rowCount{0}, colCount{nC};
  for (unsigned int i = 0; i < nPoly; i++) {
     for (auto it = Ai->at(i)->begin(); it != Ai->at(i)->end(); ++it) // First constraint
     {
        locations(0, count) = rowCount + it.row();
        locations(1, count) = colCount + it.col();
        val(count)          = *it;
        ++count;
     }
     for (unsigned int j = 0; j < bi->at(i)->n_rows; ++j) // RHS of first constraint
     {
        locations(0, count) = rowCount + j;
        locations(1, count) = nC + nC * nPoly + i;
        val(count)          = -bi->at(i)->at(j);
        ++count;
     }
     rowCount += Ai->at(i)->n_rows;

     // For common constraints
     for (auto it = Acom.begin(); it != Acom.end(); ++it) // First constraint
     {
        locations(0, count) = rowCount + it.row();
        locations(1, count) = colCount + it.col();
        val(count)          = *it;
        ++count;
     }
     for (unsigned int j = 0; j < bcom.n_rows; ++j) // RHS of first constraint
     {
        locations(0, count) = rowCount + j;
        locations(1, count) = nC + nC * nPoly + i;
        val(count)          = -bcom.at(j);
        ++count;
     }
     rowCount += Acom.n_rows;

     colCount += nC;
  }
  A = arma::sp_mat(locations, val, nFinCons, nFinVar);
}


void MathOpt::getDualMembershipLP(std::unique_ptr<GRBModel> &convexModel,
                                             unsigned int &             numV,
                                             const arma::sp_mat &       V,
                                             unsigned int &             numR,
                                             const arma::sp_mat &       R,
                                             const arma::vec &          vertex,
                                             bool                       containsOrigin) {


  ZEROAssert(!(V.n_rows < 1 && R.n_rows < 1));
  ZEROAssert(V.n_cols == vertex.size());

  if (numV == 0 && numR == 0) {
     // Initialize the model
     convexModel->reset(true);
     GRBVar     alpha[V.n_cols];
     GRBVar     a[V.n_cols + 1];
     GRBVar     beta;
     GRBLinExpr expr = 0;
     for (unsigned int i = 0; i < vertex.size(); i++) {
        alpha[i] = convexModel->addVar(
             -GRB_INFINITY, GRB_INFINITY, 0, GRB_CONTINUOUS, "alpha_" + std::to_string(i));


        a[i] = convexModel->addVar(
             0, GRB_INFINITY, 0, GRB_CONTINUOUS, "abs(alpha_" + std::to_string(i) + ")");

        // Abs: a[i] = abs(alpha[i])
        convexModel->addConstr(a[i], GRB_GREATER_EQUAL, alpha[i], "Abs_1_alpha_" + std::to_string(i));
        convexModel->addConstr(
             a[i], GRB_GREATER_EQUAL, -alpha[i], "Abs_2_alpha_" + std::to_string(i));
        expr += a[i];
     }

     beta        = convexModel->addVar(-GRB_INFINITY, GRB_INFINITY, 0, GRB_CONTINUOUS, "beta");
     a[V.n_cols] = convexModel->addVar(0, GRB_INFINITY, 0, GRB_CONTINUOUS, "abs(beta)");
     convexModel->addConstr(a[V.n_cols], GRB_GREATER_EQUAL, beta, "Abs_1_beta");
     convexModel->addConstr(a[V.n_cols], GRB_GREATER_EQUAL, -beta, "Abs_2_beta");

     // Never had a ray to the normalization!
     // expr += a[V.n_cols];

     //   Normalization, to be filled later
     // convexModel->addConstr(0, GRB_LESS_EQUAL, 1, "Normalization");
     // Absolute normalization
     convexModel->addConstr(expr, GRB_EQUAL, 1, "Normalization_Abs");

     if (containsOrigin)
        convexModel->addConstr(beta, GRB_GREATER_EQUAL, 0, "V_Origin");

     // Hyperplanes for vertices
     for (unsigned int i = 0; i < V.n_rows; i++) {
        expr = -beta;
        for (auto j = V.begin_row(i); j != V.end_row(i); ++j)
          expr += (*j) * alpha[j.col()];
        convexModel->addConstr(expr, GRB_LESS_EQUAL, 0, "V_" + std::to_string(i));
     }
     numV = V.n_rows;

     // Without beta-term for vertices
     for (unsigned int i = 0; i < R.n_rows; i++) {
        expr = 0;
        for (auto j = R.begin_row(i); j != R.end_row(i); ++j)
          expr += (*j) * alpha[j.col()];
        convexModel->addConstr(expr, GRB_LESS_EQUAL, 0, "R_" + std::to_string(i));
     }

     numR = R.n_rows;

     // For the eventual Farkas' proof of infeasibility
     convexModel->set(GRB_IntParam_InfUnbdInfo, 1);
     convexModel->set(GRB_IntParam_DualReductions, 0);
     convexModel->set(GRB_IntParam_OutputFlag, 0);
     convexModel->set(GRB_IntParam_SolutionLimit, 1);
     LOG_S(2) << "MathOpt::getDualMembershipLP: created model";
  } else {
     // current number of vertices in the model
     if (numV < V.n_rows) {
        // Then, we need to update the model by adding new constraints
        GRBLinExpr expr = 0;
        GRBVar     beta = convexModel->getVarByName("beta");
        for (unsigned int i = numV; i < V.n_rows; i++) {
          expr = -beta;
          for (auto j = V.begin_row(i); j != V.end_row(i); ++j)
             expr += (*j) * convexModel->getVarByName("alpha_" + std::to_string(j.col()));

          convexModel->addConstr(expr, GRB_LESS_EQUAL, 0, "V_" + std::to_string(i));
        }
        numV = V.n_rows;
     }

     // current number of rays in the model
     if (numR < R.n_rows) {
        // Then, we need to update the model by adding new constraints
        GRBLinExpr expr = 0;
        for (unsigned int i = numR; i < R.n_rows; i++) {
          expr = 0;
          for (auto j = R.begin_row(i); j != R.end_row(i); ++j)
             expr += (*j) * convexModel->getVarByName("alpha_" + std::to_string(j.col()));

          convexModel->addConstr(expr, GRB_LESS_EQUAL, 0, "R_" + std::to_string(i));
        }

        numR = R.n_rows;
     }
  }
  convexModel->update();

  LOG_S(3) << "MathOpt::getDualMembershipLP: updated model";
  convexModel->update();
}

void MathOpt::getPrimalMembershipLP(std::unique_ptr<GRBModel> &convexModel,
                                                unsigned int &             numV,
                                                const arma::sp_mat &       V,
                                                unsigned int &             numR,
                                                const arma::sp_mat &       R,
                                                const arma::vec &          vertex,
                                                bool                       containsOrigin) {

  ZEROAssert(!(V.n_rows < 1 && R.n_rows < 1));
  ZEROAssert(V.n_cols == vertex.size());

  if (numV == 0 && numR == 0) {
     // Initialize the model
     convexModel->reset(true);
     GRBVar     lambda[V.n_rows], mu[R.n_rows], s, w[vertex.size()];
     GRBLinExpr expr = 0;

     // Convex multipliers
     for (unsigned int i = 0; i < V.n_rows; i++)
        lambda[i] =
             convexModel->addVar(0, GRB_INFINITY, 0, GRB_CONTINUOUS, "lambda_" + std::to_string(i));

     // Conic multipliers
     for (unsigned int i = 0; i < R.n_rows; i++)
        mu[i] = convexModel->addVar(0, GRB_INFINITY, 0, GRB_CONTINUOUS, "mu_" + std::to_string(i));

     // Normalization 1
     s = convexModel->addVar(-GRB_INFINITY, GRB_INFINITY, 0, GRB_CONTINUOUS, "s");
     // Normalization 2
     for (unsigned int i = 0; i < vertex.size(); i++)
        w[i] = convexModel->addVar(-1, 1, 0, GRB_CONTINUOUS, "w_" + std::to_string(i));

     convexModel->setObjective(GRBLinExpr{s}, GRB_MAXIMIZE);

     // For any dimension of the input point
     for (unsigned int i = 0; i < vertex.size(); i++) {
        expr = w[i] + s * vertex.at(i); // Reset the expression to s(x*[i]) + w[i]

        // Add term for each vertex
        for (unsigned int j = 0; j < V.n_rows; ++j) {
          GRBVar vars[] = {lambda[j]};
          // Remember the minus
          double coeff[] = {-V.at(j, i)};
          expr.addTerms(coeff, vars, 1);
        }

        // Add term for each ray
        for (unsigned int j = 0; j < R.n_rows; ++j) {
          GRBVar vars[] = {mu[j]};
          // Remember the minus
          double coeff[] = {-R.at(j, i)};
          expr.addTerms(coeff, vars, 1);
        }
        convexModel->addConstr(expr, GRB_EQUAL, 0, "PR_Combination_" + std::to_string(i));
     }


     // Convex Combination
     expr = -s;
     double coeff[V.n_rows];
     for (auto &el : coeff)
        el = 1;
     expr.addTerms(coeff, lambda, V.n_rows);
     convexModel->addConstr(expr, GRB_EQUAL, 0, "Convex_Combination");


     // Update the working sizes
     numV = V.n_rows;
     numR = R.n_rows;

     // For the eventual Farkas' proof of infeasibility
     convexModel->set(GRB_IntParam_InfUnbdInfo, 1);
     convexModel->set(GRB_IntParam_DualReductions, 0);
     convexModel->set(GRB_IntParam_OutputFlag, 0);
     convexModel->set(GRB_IntParam_SolutionLimit, 1);
     LOG_S(2) << "MathOpt::getPrimalMembershipLP: created model";

  } else {
     // Get the constraints for the point-ray combination
     std::vector<GRBConstr> PR_Combs;
     for (unsigned int i = 0; i < vertex.size(); ++i)
        PR_Combs.push_back(convexModel->getConstrByName("PR_Combination_" + std::to_string(i)));

     // Get the s var
     GRBVar              s = convexModel->getVarByName("s");
     std::vector<GRBVar> ss(vertex.size(), s);


     if (numV < V.n_rows) {
        // Then, we need to update the model by adding terms to PR_Combination and Convex_Combination


        // Add the convex combination for points
        std::vector<GRBConstr> Constrs = PR_Combs;
        Constrs.push_back(convexModel->getConstrByName("Convex_Combination"));

        double coeff[vertex.size() + 1];
        // Last coefficient is for the convex combination
        coeff[vertex.size()] = 1;
        for (unsigned int i = numV; i < V.n_rows; i++) {
          // Change the Coefficient wrt to the point
          for (unsigned int j = 0; j < vertex.size(); ++j)
             coeff[j] = V.at(i, j);

          convexModel->addVar(0,
                                     GRB_INFINITY,
                                     0,
                                     GRB_CONTINUOUS,
                                     vertex.size() + 1,
                                     &Constrs[0],
                                     coeff,
                                     "lambda_" + std::to_string(i));
        }
        // Update the working size of the vertices
        numV = V.n_rows;
     }

     // current number of rays in the model
     if (numR < R.n_rows) {
        // Then, we need to update the model by adding new constraints


        double coeff[vertex.size()];
        // coefficients in the point-ray combination
        for (unsigned int i = numR; i < R.n_rows; i++) {
          for (unsigned int j = 0; j < vertex.size(); ++j)
             coeff[j] = R.at(i, j);
          convexModel->addVar(
                0, GRB_INFINITY, 0, GRB_CONTINUOUS, 1, &PR_Combs[0], coeff, "mu_" + std::to_string(i));
        }

        // Update the working size of the rays
        numR = R.n_rows;
     }
     convexModel->chgCoeffs(&PR_Combs[0], &ss[0], &vertex[0], static_cast<int>(vertex.size()));
     LOG_S(3) << "MathOpt::getPrimalMembershipLP: updated model";
  }
  convexModel->reset();
  convexModel->update();
  LOG_S(3) << "MathOpt::getPrimalMembershipLP: ready.";
}