Program Listing for File ip_param.cpp
↰ Return to documentation for file (src/mathopt/mp_param/ip_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/ip_param.h"
#include <memory>
std::ostream &MathOpt::operator<<(std::ostream &os, const MathOpt::IP_Param &I) {
os << "Parametrized Integer Program with bi-linear objective: " << '\n';
os << I.getNumVars() << " decision variables" << '\n';
os << I.getb().n_rows << " linear inequalities" << '\n' << '\n';
return os;
}
bool MathOpt::IP_Param::operator==(const IP_Param &IPG2) const {
if (!Utils::isZero(this->getB(true) - IPG2.getB(true)))
return false;
if (!Utils::isZero(this->C - IPG2.getC()))
return false;
if (!Utils::isZero(this->c - IPG2.getc()))
return false;
if (!Utils::isZero(this->d - IPG2.getd()))
return false;
if (!Utils::isZero(this->getb(true) - IPG2.getb(true)))
return false;
for (unsigned int i = 0; i < this->Bounds.size(); ++i)
if (this->Bounds.at(i) != IPG2.Bounds.at(i))
return false;
return Utils::isZero(this->Integers - IPG2.getIntegers());
}
MathOpt::IP_Param &MathOpt::IP_Param::setBounds(const VariableBounds &bound_in) {
MathOpt::MP_Param::setBounds(bound_in);
for (unsigned int i = 0; i < this->numVars; i++) {
auto var = this->IPModel.getVarByName("y_" + std::to_string(i));
var.set(GRB_DoubleAttr_LB,
abs(this->Bounds.at(i).first) < 1e20
? this->Bounds.at(i).first
: Utils::getSign(this->Bounds.at(i).first) * GRB_INFINITY);
var.set(GRB_DoubleAttr_LB,
abs(this->Bounds.at(i).second) < 1e20
? this->Bounds.at(i).second
: Utils::getSign(this->Bounds.at(i).second) * GRB_INFINITY);
}
this->IPModel.update();
this->rewriteBounds();
return *this;
}
bool MathOpt::IP_Param::finalize() {
if (this->Finalized)
return true;
MP_Param::finalize();
try {
GRBVar y[this->numVars];
for (unsigned int i = 0; i < this->numVars; i++) {
y[i] = this->IPModel.addVar(Bounds.at(i).first,
Bounds.at(i).second,
c.at(i),
GRB_CONTINUOUS,
"y_" + std::to_string(i));
}
// Add integralities
for (unsigned int i = 0; i < this->Integers.size(); ++i) {
auto var = y[static_cast<int>(Integers.at(i))];
// Unfortunately, we need to reset the bounds for these variables
var.set(GRB_CharAttr_VType, 'I');
var.set(GRB_DoubleAttr_LB, this->Bounds.at(Integers.at(i)).first);
var.set(GRB_DoubleAttr_UB, this->Bounds.at(Integers.at(i)).second);
}
this->IPModel.update();
Utils::addSparseConstraints(B, b, y, "Constr_", &this->IPModel, GRB_LESS_EQUAL, nullptr);
this->IPModel.update();
this->IPModel.set(GRB_IntParam_OutputFlag, 0);
this->IPModel.set(GRB_IntParam_InfUnbdInfo, 1);
} catch (GRBException &e) {
throw ZEROException(ZEROErrorCode::SolverError,
std::to_string(e.getErrorCode()) + e.getMessage());
}
this->Finalized = true;
return true;
}
void MathOpt::IP_Param::updateModelObjective(const arma::vec &x) {
if (x.size() != this->numParams)
throw ZEROException(ZEROErrorCode::Assertion,
"Invalid argument size: " + std::to_string(x.size()) +
" != " + std::to_string(numParams));
if (!this->Finalized)
throw ZEROException(ZEROErrorCode::Assertion, "The model is not Finalized!");
try {
// Make the linear part of the objective
GRBQuadExpr Objective = arma::as_scalar(this->d.t() * x);
arma::vec Cx;
Cx = this->C * x;
for (unsigned int i = 0; i < this->numVars; i++)
Objective += (Cx[i] + this->c.at(i)) * this->IPModel.getVarByName("y_" + std::to_string(i));
this->IPModel.setObjective(Objective, GRB_MINIMIZE);
this->IPModel.update();
} catch (GRBException &e) {
throw ZEROException(e);
}
}
std::unique_ptr<GRBModel> MathOpt::IP_Param::solveFixed(const arma::vec x, bool solve) {
if (!this->Finalized)
throw ZEROException(ZEROErrorCode::Assertion, "The model is not Finalized!");
try {
this->updateModelObjective(x);
std::unique_ptr<GRBModel> model(new GRBModel(this->IPModel));
if (solve)
model->optimize();
return model;
} catch (GRBException &e) {
throw ZEROException(e);
}
return nullptr;
}
std::unique_ptr<GRBModel> MathOpt::IP_Param::getIPModel(const arma::vec &x, bool relax) {
if (!this->Finalized)
throw ZEROException(ZEROErrorCode::Assertion, "The model is not Finalized!");
try {
this->updateModelObjective(x);
} catch (GRBException &e) {
throw ZEROException(e);
}
if (relax) {
return std::make_unique<GRBModel>(this->IPModel.relax());
} else
return std::make_unique<GRBModel>(this->IPModel);
}
MathOpt::IP_Param &MathOpt::IP_Param::set(const arma::sp_mat &C_in,
const arma::sp_mat &B_in,
const arma::vec &b_in,
const arma::vec &c_in,
const arma::vec &d_in,
const arma::vec &integers_in,
const VariableBounds &Bounds_in) {
ZEROAssert(!integers_in.empty());
this->Q.zeros(c_in.size(), c_in.size());
this->A.zeros(b_in.size(), C_in.n_cols);
this->Finalized = false;
this->Integers = arma::sort(integers_in);
this->Bounds = Bounds_in;
MP_Param::set(Q, C_in, A, B_in, c_in, b_in, d_in);
return *this;
}
MathOpt::IP_Param &MathOpt::IP_Param::set(arma::sp_mat &&C_in,
arma::sp_mat &&B_in,
arma::vec &&b_in,
arma::vec &&c_in,
arma::vec &&d_in,
arma::vec &&integers_in,
VariableBounds &&Bounds_in) {
ZEROAssert(!integers_in.empty());
this->Q.zeros(c_in.size(), c_in.size());
this->A.zeros(b_in.size(), C_in.n_cols);
this->Finalized = false;
this->Integers = std::move(integers_in);
this->Bounds = std::move(Bounds_in);
MP_Param::set(Q, C_in, A, B_in, c_in, b_in, d_in);
return *this;
}
double MathOpt::IP_Param::computeObjective(const arma::vec &y,
const arma::vec &x,
bool checkFeas,
double tol) const {
ZEROAssert(y.n_rows == this->getNumVars());
ZEROAssert(x.n_rows == this->getNumParams());
if (checkFeas)
if (!this->isFeasible(y, x, tol))
return -GRB_INFINITY;
return arma::as_scalar(((C * x).t() + c.t()) * y + this->d.t() * x);
}
bool MathOpt::IP_Param::isFeasible(const arma::vec &y, const arma::vec &x, double tol) const {
arma::vec slack = B * y - b;
if (slack.n_rows) // if infeasible
if (!Utils::isEqual(slack.max(), 0, tol))
return false;
if (y.min() <= -tol) // if infeasible
return false;
for (const auto i : this->Integers) // Integers
if (!Utils::isEqual(y.at(i), trunc(y.at(i)), tol))
return false;
return true;
}
bool MathOpt::IP_Param::addConstraints(const arma::sp_mat &A_in, const arma::vec &b_in) {
ZEROAssert(this->B.n_cols == A_in.n_cols);
this->B = arma::join_cols(this->B, A_in);
this->b = arma::join_cols(this->b, b_in);
this->A = Utils::resizePatch(this->A, this->B.n_rows, this->numParams);
this->size();
// If model hasn't been made, we do not need to update it
if (this->Finalized) {
GRBVar y[numVars];
for (unsigned int i = 0; i < this->numVars; i++) {
y[i] = this->IPModel.getVarByName("y_" + std::to_string(i));
}
Utils::addSparseConstraints(
A_in, b_in, y, "ConstrAdd_", &this->IPModel, GRB_LESS_EQUAL, nullptr);
this->IPModel.update();
}
return true;
}
unsigned int MathOpt::IP_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)));
N = arma::join_cols(this->C, -this->getA(true));
q = arma::join_cols(this->c, arma::join_cols(this->b, this->b_bounds));
ZEROAssert(M.n_cols == (numVars + numConstr + this->B_bounds.n_rows));
ZEROAssert(N.n_cols == numParams);
ZEROAssert(q.size() == (this->c.size() + this->b.size() + this->b_bounds.size()));
return M.n_rows;
}
void MathOpt::IP_Param::presolve() {
return;
if (!this->Finalized)
this->finalize();
try {
auto p = new GRBModel(this->IPModel);
GRBLinExpr linObj = 0;
for (unsigned int i = 0; i < this->numVars; i++)
linObj += (this->c.at(i)) * this->IPModel.getVarByName("y_" + std::to_string(i));
p->setObjective(linObj);
p->set(GRB_IntParam_Presolve, 2);
p->set(GRB_IntParam_DualReductions, 0);
p->set(GRB_IntParam_OutputFlag, 0);
auto presolved = p->presolve();
unsigned int nvar = presolved.get(GRB_IntAttr_NumVars);
unsigned int nconstr = presolved.get(GRB_IntAttr_NumConstrs);
// Constraint matrix and bounds
auto vars = presolved.getVars();
arma::sp_mat pre_B(nconstr, nvar);
pre_B.zeros();
for (int v = 0; v < nvar; ++v) {
auto varCol = presolved.getCol(vars[v]);
auto lb = vars[v].get(GRB_DoubleAttr_LB);
auto ub = vars[v].get(GRB_DoubleAttr_UB);
if (lb != this->Bounds.at(v).first)
this->Bounds.at(v).first = lb;
if (ub != this->Bounds.at(v).second)
this->Bounds.at(v).second = ub;
if (varCol.size() > 0) {
for (int c = 0; c < nconstr; ++c) {
auto val = varCol.getCoeff(c);
if (val > 1e-6 || val < -1e-6)
pre_B.at(c, v) = val;
}
}
}
// LHS and senses
arma::vec pre_b(nconstr);
auto constrs = presolved.getConstrs();
for (int c = 0; c < nconstr; ++c) {
auto sense = constrs[c].get(GRB_CharAttr_Sense);
switch (sense) {
case '<':
pre_b.at(c) = constrs[c].get(GRB_DoubleAttr_RHS);
break;
case '>': {
// Change row sense
pre_b.at(c) = -constrs[c].get(GRB_DoubleAttr_RHS);
pre_B.row(c) = -pre_B.row(c);
} break;
default: {
// Sense is =. We need one more inequality
Utils::resizePatch(pre_B, pre_B.n_rows + 1, pre_B.n_cols + 1);
Utils::resizePatch(pre_b, pre_b.size() + 1);
// Regular coefficient for row c
pre_b.at(c) = constrs[c].get(GRB_DoubleAttr_RHS);
// inverted coefficients for row c+nconstrs
pre_B.row(pre_B.n_rows - 1) = -pre_B.row(c);
pre_b.at(pre_b.size() - 1) = -pre_b.at(c);
}
}
}
// Objective coefficients
GRBLinExpr obj = presolved.getObjective().getLinExpr();
for (int v = 0; v < obj.size(); ++v) {
auto varIndexStr = obj.getVar(v).get(GRB_StringAttr_VarName);
// Replace y_ (first 2 characters) with nothing
auto varIndex = stoi(varIndexStr.replace(varIndexStr.begin(), varIndexStr.begin() + 2, ""));
auto oc = obj.getCoeff(v);
auto pc = this->c.at(varIndex, 0);
if (std::abs(oc - pc) > 1e-5) {
std::cout << "Modified objective coefficients for variable " << varIndex << ": " << pc
<< " became " << oc;
// Update c
this->c.at(varIndex) = oc;
// We need to update C as well
auto ratio = pc / oc;
// Guess the number of other players
auto modulo = this->numParams / this->numVars;
// std::cout << "ratio" << ratio << "\n";
for (unsigned int m = 0; m < modulo; ++m) {
// std::cout << "pre" << this->C.at(v, m * this->numVars + varIndex) << "\n";
this->C.at(v, m * this->numVars + v) =
this->C.at(v, m * this->numVars + varIndex) * ratio;
// std::cout << "post" << this->C.at(v, m * this->numVars + varIndex) << "\n";
}
// throw ZEROException(ZEROErrorCode::SolverError, "Invalid presolve mapping");
}
}
// resize A, assuming it's empty
this->A.zeros(nconstr, this->numParams);
this->b = pre_b;
this->B = pre_B;
this->Finalized = false;
// Reset the model.
auto _constrs = IPModel.getConstrs();
auto _vars = IPModel.getVars();
auto numVars = IPModel.get(GRB_IntAttr_NumVars);
auto numConstrs = IPModel.get(GRB_IntAttr_NumConstrs);
for (unsigned i = 0; i < numConstrs; ++i)
IPModel.remove(_constrs[i]);
for (unsigned i = 0; i < numVars; ++i)
IPModel.remove(_vars[i]);
IPModel.reset(1);
IPModel.update();
this->finalize();
LOG_S(1) << "MathOpt::IP_Param::presolve: done.";
} catch (GRBException &e) {
LOG_S(1) << "MathOpt::IP_Param::presolve: cannot complete presolve.";
}
}
long int MathOpt::IP_Param::load(const std::string &filename, long int pos) {
arma::sp_mat _C, _B, BO;
arma::vec _b, _c, _integers, _d;
std::string headercheck;
pos = Utils::appendRead(headercheck, filename, pos);
if (headercheck != "IP_Param")
throw ZEROException(ZEROErrorCode::IOError, "Invalid header");
pos = Utils::appendRead(_C, filename, pos, std::string("IP_Param::C"));
pos = Utils::appendRead(_B, filename, pos, std::string("IP_Param::B"));
pos = Utils::appendRead(_b, filename, pos, std::string("IP_Param::b"));
pos = Utils::appendRead(_c, filename, pos, std::string("IP_Param::c"));
pos = Utils::appendRead(_d, filename, pos, std::string("IP_Param::d"));
pos = Utils::appendRead(_integers, filename, pos, std::string("IP_Param::Integers"));
pos = Utils::appendRead(BO, filename, pos, std::string("IP_Param::Bounds"));
VariableBounds Bond;
if (BO.n_rows > 0) {
if (BO.n_cols != 2)
throw ZEROException(ZEROErrorCode::IOError, "Invalid bounds object in loaded file");
for (unsigned int i = 0; i < _B.n_cols; ++i)
Bond.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.n_cols - BO.n_rows;
for (unsigned int i = 0; i < diff; ++i)
Bond.push_back({0, GRB_INFINITY});
}
LOG_S(1) << "Loaded IP_Param to file " << filename;
this->set(_C, _B, _b, _c, _d, _integers, Bond);
return pos;
}
void MathOpt::IP_Param::save(const std::string &filename, bool append) const {
Utils::appendSave(std::string("IP_Param"), filename, append);
Utils::appendSave(this->C, filename, std::string("IP_Param::C"), false);
Utils::appendSave(this->getB(true), filename, std::string("IP_Param::B"), false);
Utils::appendSave(this->getb(true), filename, std::string("IP_Param::b"), false);
Utils::appendSave(this->c, filename, std::string("IP_Param::c"), false);
Utils::appendSave(this->d, filename, std::string("IP_Param::d"), false);
Utils::appendSave(this->Integers, filename, std::string("IP_Param::Integers"), 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("IP_Param::Bounds"), false);
LOG_S(1) << "Saved IP_Param to file " << filename;
}
MathOpt::IP_Param::IP_Param(const arma::sp_mat &C_in,
const arma::sp_mat &B_in,
const arma::vec &b_in,
const arma::vec &c_in,
const arma::vec &d_in,
const arma::vec &integers_in,
const VariableBounds &Bounds_in,
GRBEnv *env_in)
: MP_Param(env_in), IPModel{GRBModel(*env_in)} {
this->set(C_in, B_in, b_in, c_in, d_in, integers_in, Bounds_in);
this->forceDataCheck();
}