Program Listing for File qp_param.cpp

Return to documentation for file (src/mathopt/mp_param/qp_param.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/mp_param/qp_param.h"

std::ostream &MathOpt::operator<<(std::ostream &os, const MathOpt::QP_Param &Q) {
  os << "Quadratic program with linear inequality constraints: " << '\n';
  os << Q.getNumVars() << " decision variables parametrized by " << Q.getNumParams() << " variables"
      << '\n';
  os << Q.getb().n_rows << " linear inequalities" << '\n' << '\n';
  return os;
}


bool MathOpt::QP_Param::operator==(const QP_Param &Q2) const {
  if (!Utils::isZero(this->Q - Q2.getQ()))
     return false;
  if (!Utils::isZero(this->C - Q2.getC()))
     return false;
  if (!Utils::isZero(this->getA(true) - Q2.getA(true)))
     return false;
  if (!Utils::isZero(this->getB(true) - Q2.getB(true)))
     return false;
  if (!Utils::isZero(this->c - Q2.getc()))
     return false;
  if (!Utils::isZero(this->d - Q2.getd()))
     return false;
  for (unsigned int i = 0; i < this->Bounds.size(); ++i)
     if (this->Bounds.at(i) != Q2.Bounds.at(i))
        return false;
  if (!Utils::isZero(this->getb(true) - Q2.getb(true)))
     return false;
  return true;
}

void MathOpt::QP_Param::makeyQy() {
  if (this->MadeyQy)
     return;
  GRBVar y[this->numVars];
  for (unsigned int i = 0; i < numVars; i++)
     y[i] = this->Model.addVar(
          Bounds.at(i).first, Bounds.at(i).second, 0, GRB_CONTINUOUS, "y_" + std::to_string(i));


  GRBQuadExpr yQy{0};
  for (auto val = Q.begin(); val != Q.end(); ++val) {
     unsigned int i, j;
     double       value = (*val);
     i                  = val.row();
     j                  = val.col();
     yQy += 0.5 * y[i] * value * y[j];
  }
  Model.setObjective(yQy, GRB_MINIMIZE);
  Model.update();
  this->MadeyQy = true;
}


std::unique_ptr<GRBModel> MathOpt::QP_Param::solveFixed(arma::vec x, bool solve) {
  this->makeyQy();
  if (x.size() != this->numParams)
     throw ZEROException(ZEROErrorCode::Assertion,
                                "Mismatch in x size: " + std::to_string(x.size()) +
                                     " != " + std::to_string(numParams));
  std::unique_ptr<GRBModel> model(new GRBModel(this->Model));
  try {
     GRBQuadExpr yQy = model->getObjective()+ arma::as_scalar(this->d.t() * x);
     arma::vec   Cx, Ax;
     Cx = this->C * x;
     Ax = this->A * x;
     GRBVar y[this->numVars];
     for (unsigned int i = 0; i < this->numVars; i++) {
        y[i] = model->getVarByName("y_" + std::to_string(i));
        yQy += (Cx[i] + c[i]) * y[i];
     }
     model->setObjective(yQy , GRB_MINIMIZE);

     Utils::addSparseConstraints(B, b - Ax, y, "Constr_", model.get(), GRB_LESS_EQUAL, nullptr);

     model->update();
     model->set(GRB_IntParam_OutputFlag, 0);
     model->set(GRB_IntParam_NonConvex, 2);
     if (solve)
        model->optimize();
  } catch (GRBException &e) {
     throw ZEROException(e);
  }
  return model;
}


unsigned int MathOpt::QP_Param::KKT(arma::sp_mat &M, arma::sp_mat &N, arma::vec &q) const {
  this->forceDataCheck();
  M = arma::join_cols( // In armadillo join_cols(A, B) is same as [A;B] in
                              // Matlab
                              //  join_rows(A, B) is same as [A B] in Matlab
        arma::join_rows(this->Q, this->getB(true).t()),
        arma::join_rows(-this->getB(true),
                             arma::zeros<arma::sp_mat>(this->numConstr, this->numConstr)));

  ZEROAssert(M.n_cols == (numVars + numConstr + this->B_bounds.n_rows));
  N = arma::join_cols(this->C, -this->getA(true));
  ZEROAssert(N.n_cols == numParams);
  q = arma::join_cols(this->c, this->getb(true));
  ZEROAssert(q.size() == (this->c.size() + this->b.size() + this->b_bounds.size()));
  // q.print();
  return M.n_rows;
}


MathOpt::QP_Param &MathOpt::QP_Param::set(const arma::sp_mat &Q_in,
                                                        const arma::sp_mat &C_in,
                                                        const arma::sp_mat &A_in,
                                                        const arma::sp_mat &B_in,
                                                        const arma::vec    &c_in,
                                                        const arma::vec    &b_in,
                                                        const arma::vec    &d_in = {}) {
  this->MadeyQy = false;
  MP_Param::set(Q_in, C_in, A_in, B_in, c_in, b_in, d_in);
  return *this;
}

MathOpt::QP_Param &MathOpt::QP_Param::set(arma::sp_mat   &&Q_in,
                                                        arma::sp_mat   &&C_in,
                                                        arma::sp_mat   &&A_in,
                                                        arma::sp_mat   &&B_in,
                                                        arma::vec      &&c_in,
                                                        arma::vec &&b_in,
                                                        arma::vec &&d_in = {}) {
  this->MadeyQy = false;
  MP_Param::set(Q_in, C_in, A_in, B_in, c_in, b_in, d_in);
  return *this;
}

MathOpt::QP_Param &MathOpt::QP_Param::set(QP_Objective &&obj, QP_Constraints &&cons) {
  return this->set(std::move(obj.Q),
                         std::move(obj.C),
                         std::move(cons.A),
                         std::move(cons.B),
                         std::move(obj.c),
                         std::move(cons.b),
                         std::move(obj.d));
}

MathOpt::QP_Param &MathOpt::QP_Param::set(const QP_Objective &obj, const QP_Constraints &cons) {
  return this->set(obj.Q, obj.C, cons.A, cons.B, obj.c, cons.b, obj.d);
}


void MathOpt::QP_Param::save(const std::string &filename, bool append) const {

  Utils::appendSave(std::string("QP_Param"), filename, append);
  Utils::appendSave(this->Q, filename, std::string("QP_Param::Q"), false);
  Utils::appendSave(this->getA(true), filename, std::string("QP_Param::A"), false);
  Utils::appendSave(this->getB(true), filename, std::string("QP_Param::B"), false);
  Utils::appendSave(this->C, filename, std::string("QP_Param::C"), false);
  Utils::appendSave(this->getb(true), filename, std::string("QP_Param::b"), false);
  Utils::appendSave(this->c, filename, std::string("QP_Param::c"), false);
  Utils::appendSave(this->d, filename, std::string("QP_Param::d"), false);
  arma::sp_mat BO(this->numVars, 2);
  for (unsigned int i = 0; i < this->numVars; ++i) {
     BO.at(i, 0) = this->Bounds.at(i).first;
     BO.at(i, 1) = this->Bounds.at(i).second;
  }
  Utils::appendSave(BO, filename, std::string("QP_Param::Bounds"), false);
  LOG_S(1) << "Saved QP_Param to file " << filename;
}


long int MathOpt::QP_Param::load(const std::string &filename, long int pos) {

  arma::sp_mat Q_in, A_in, B_in, C_in, BO;
  arma::vec    c_in, b_in, d_in;
  std::string  headercheck;
  pos = Utils::appendRead(headercheck, filename, pos);
  if (headercheck != "QP_Param")
     throw ZEROException(ZEROErrorCode::IOError, "Invalid header");
  pos = Utils::appendRead(Q_in, filename, pos, std::string("QP_Param::Q"));
  pos = Utils::appendRead(A_in, filename, pos, std::string("QP_Param::A"));
  pos = Utils::appendRead(B_in, filename, pos, std::string("QP_Param::B"));
  pos = Utils::appendRead(C_in, filename, pos, std::string("QP_Param::C"));
  pos = Utils::appendRead(b_in, filename, pos, std::string("QP_Param::b"));
  pos = Utils::appendRead(c_in, filename, pos, std::string("QP_Param::c"));
  pos = Utils::appendRead(d_in, filename, pos, std::string("QP_Param::d"));
  pos = Utils::appendRead(BO, filename, pos, std::string("QP_Param::Bounds"));
  if (BO.n_rows > 0) {
     ZEROAssert(BO.n_cols == 2);

     for (unsigned int i = 0; i < B_in.n_cols; ++i)
        this->Bounds.push_back(
             {abs(BO.at(i, 0)) < 1e20 ? BO.at(i, 0) : Utils::getSign(BO.at(i, 0)) * GRB_INFINITY,
              abs(BO.at(i, 1)) < 1e20 ? BO.at(i, 1) : Utils::getSign(BO.at(i, 1)) * GRB_INFINITY});

     int diff = B_in.n_cols - BO.n_rows;
     for (unsigned int i = 0; i < diff; ++i)
        this->Bounds.push_back({0, GRB_INFINITY});
  }
  LOG_S(1) << "Loaded QP_Param to file " << filename;
  this->set(Q_in, C_in, A_in, B_in, c_in, b_in, d_in);
  return pos;
}


MathOpt::QP_Param::QP_Param(const arma::sp_mat &Q_in,
                                     const arma::sp_mat &C_in,
                                     const arma::sp_mat &A_in,
                                     const arma::sp_mat &B_in,
                                     const arma::vec    &c_in,
                                     const arma::vec    &b_in,
                                     const arma::vec    &d_in,
                                     GRBEnv             *env)
     : MP_Param(env), MadeyQy{false}, Model{(*env)} {
  this->MadeyQy = false;
  this->set(Q_in, C_in, A_in, B_in, c_in, b_in, d_in);
  this->size();
  this->forceDataCheck();
}