diff --git a/include/mp/common.h b/include/mp/common.h index fb45456dc..8f2d17bf7 100644 --- a/include/mp/common.h +++ b/include/mp/common.h @@ -126,6 +126,9 @@ enum { namespace sol { /** Solution status. */ enum Status { + + NOT_CHECKED = -200, + /** Unknown status. */ UNKNOWN = -1, diff --git a/include/mp/convert/backend.h b/include/mp/convert/backend.h index 97950fbb0..36f8df1d1 100644 --- a/include/mp/convert/backend.h +++ b/include/mp/convert/backend.h @@ -208,6 +208,7 @@ class BasicBackend : MP_DISPATCH( WrapupSolve() ); MP_DISPATCH( ObtainSolutionStatus() ); + MP_DISPATCH( CalculateDerivedResults() ); MP_DISPATCH( ReportSolution() ); if (MP_DISPATCH( timing() )) MP_DISPATCH( PrintTimingInfo() ); @@ -226,6 +227,7 @@ class BasicBackend : solve_status = MP_DISPATCH( ConvertSolutionStatus(*MP_DISPATCH( interrupter() ), solve_code) ); } + void CalculateDerivedResults() { } void ReportSolution() { MP_DISPATCH( ReportSuffixes() ); @@ -249,9 +251,7 @@ class BasicBackend : DeclareAndReportIntSuffix(suf_constatus, stt); } - void ReportCustomSuffixes() { - - } + void ReportCustomSuffixes() { } void ReportPrimalDualValues() { fmt::MemoryWriter writer; @@ -285,8 +285,21 @@ class BasicBackend : /// ///////////////////////////////////////////////////////////////////////////////// - int solve_code=0; + /////////////////////////////// SOLUTION STATUS ///////////////////////////////// + bool IsProblemInfOrUnb() const { + assert( IsSolStatusRetrieved() ); + return sol::INFEASIBLE<=solve_code && + sol::UNBOUNDED>=solve_code; + } + + bool IsSolStatusRetrieved() const { + return sol::NOT_CHECKED!=solve_code; + } + + int solve_code=sol::NOT_CHECKED; std::string solve_status; + + /////////////////////////////// STORING SOLUTON AND STATS /////////////////////// double obj_value = std::numeric_limits::quiet_NaN(); std::vector solution, dual_solution; @@ -328,9 +341,13 @@ class BasicBackend : GetCQ().DeclareAndReportIntSuffix(suf, values); } + void DeclareAndReportDblSuffix(const SuffixDef& suf, + const std::vector& values) { + GetCQ().DeclareAndReportDblSuffix(suf, values); + } + + ///////////////////////////// OPTIONS ///////////////////////////////// - /// TODOs - /// - hide all Solver stuff behind an abstract interface protected: using Solver::AddOption; diff --git a/include/mp/convert/converter_flat_query.h b/include/mp/convert/converter_flat_query.h index 4a1804210..07932502c 100644 --- a/include/mp/convert/converter_flat_query.h +++ b/include/mp/convert/converter_flat_query.h @@ -29,6 +29,12 @@ class FlatConverterQuery : public ConverterQuery { GetOutputModel().DeclareAndReportIntSuffix(suf, values); } + + void DeclareAndReportDblSuffix(const SuffixDef& suf, + const std::vector& values) override { + GetOutputModel().DeclareAndReportDblSuffix(suf, values); + } + void HandleSolution(int status, fmt::CStringRef msg, const double *x, const double * y, double obj) override { GetCvt().HandleSolution(status, msg, x, y, obj); diff --git a/include/mp/convert/converter_query.h b/include/mp/convert/converter_query.h index 68f1c0caf..67667d7ad 100644 --- a/include/mp/convert/converter_query.h +++ b/include/mp/convert/converter_query.h @@ -24,6 +24,9 @@ class ConverterQuery { virtual void DeclareAndReportIntSuffix(const SuffixDef& suf, const std::vector& values) = 0; + virtual void DeclareAndReportDblSuffix(const SuffixDef& suf, + const std::vector& values) = 0; + virtual void HandleSolution(int, fmt::CStringRef, const double *, const double *, double) = 0; diff --git a/include/mp/convert/mip_backend.h b/include/mp/convert/mip_backend.h new file mode 100644 index 000000000..3e204aafd --- /dev/null +++ b/include/mp/convert/mip_backend.h @@ -0,0 +1,127 @@ +/* + Abstract MIP solver backend wrapper. + + Copyright (C) 2020 AMPL Optimization Inc + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both that the copyright notice and this permission notice and warranty + disclaimer appear in supporting documentation. + + The author and AMPL Optimization Inc disclaim all warranties with + regard to this software, including all implied warranties of + merchantability and fitness. In no event shall the author be liable + for any special, indirect or consequential damages or any damages + whatsoever resulting from loss of use, data or profits, whether in an + action of contract, negligence or other tortious action, arising out + of or in connection with the use or performance of this software. + + */ + +#ifndef MIPBACKEND_H_ +#define MIPBACKEND_H_ + +#include "mp/convert/backend.h" + + +namespace mp { + +/// MIP backend wrapper. +/// The MIP wrapper provides common functionality relative to MIP solvers; +/// it implements the common suffixes and the logic shared across all MIP +/// solvers +template +class MIPBackend : + public BasicBackend +{ + using BaseBackend = BasicBackend; + + struct Options { + int exportIIS_; + int returnMipGap_; + }; + Options mipStoredOptions_; + +public: + + void InitOptions() { + BaseBackend::InitOptions(); + MP_DISPATCH( InitMIPOptions() ); + } + + void InitMIPOptions() { + this->AddStoredOption("iisfind", + "Whether to find and export the IIS. " + "Default = 0 (don't export).", + mipStoredOptions_.exportIIS_); + this->AddStoredOption("return_mipgap", + "Whether to return mipgap suffixes or include mipgap values\n\ + (|objectve - best_bound|) in the solve_message: sum of\n\ + 1 = return relmipgap suffix (relative to |obj|);\n\ + 2 = return absmipgap suffix (absolute mipgap);\n\ + 4 = suppress mipgap values in solve_message.\n\ + Default = 0. The suffixes are on the objective and problem.\n\ + Returned suffix values are +Infinity if no integer-feasible\n\ + solution has been found, in which case no mipgap values are\n\ + reported in the solve_message.", + mipStoredOptions_.returnMipGap_); + } + + void CalculateDerivedResults() { + BasicBackend::CalculateDerivedResults(); + if (MP_DISPATCH( IsProblemInfOrUnb() ) && + mipStoredOptions_.exportIIS_) + MP_DISPATCH( ComputeIIS() ); + if (mipStoredOptions_.returnMipGap_) + MP_DISPATCH( ComputeMipGap() ); + + } + + // Override for standard MIP calculations + /** + * Compute the IIS and relevant values + **/ + void ComputeIIS(); + /** + * Get IIS values for constraints + **/ + void ConsIIS(std::vector& stt) { stt.clear(); } + /** + * Get IIS values for variables + **/ + void VarsIIS(std::vector& stt) { stt.clear(); } + + void ComputeMipGap() {} + double MIPGap() { throw std::runtime_error("Not implemented"); } + + const SuffixDef sufIISCon = { "iis", suf::CON | suf::OUTPUT }; + const SuffixDef sufIISVar = { "iis", suf::VAR | suf::OUTPUT }; + + const SuffixDef sufRelMipGapObj = { "relmipgap", suf::OBJ | suf::OUTPUT }; + const SuffixDef sufRelMipGapProb = { "relmipgap", suf::PROBLEM | suf::OUTPUT }; + + void ReportStandardSuffixes() { + BasicBackend::ReportStandardSuffixes(); + std::vector stt; + std::vector dbl; + if (mipStoredOptions_.exportIIS_) + { + MP_DISPATCH(ConsIIS(stt)); + this->DeclareAndReportIntSuffix(sufIISCon, stt); + MP_DISPATCH(VarsIIS(stt)); + this->DeclareAndReportIntSuffix(sufIISVar, stt); + } + if (mipStoredOptions_.returnMipGap_) + { + dbl.clear(); + dbl.push_back(MP_DISPATCH( MIPGap() )); + this->DeclareAndReportDblSuffix(sufRelMipGapObj, dbl); + this->DeclareAndReportDblSuffix(sufRelMipGapProb, dbl); + } + } + +}; +} // namespace mp + +#endif // MIPBACKEND_H_ diff --git a/include/mp/convert/model_adapter.h b/include/mp/convert/model_adapter.h index 57935ef18..59dcae25c 100644 --- a/include/mp/convert/model_adapter.h +++ b/include/mp/convert/model_adapter.h @@ -36,7 +36,7 @@ class ModelAdapter : public Model { } template - void DeclareAndReportSuffix(const SuffixDef& sufdef, + void DeclareAndReportSuffix(const SuffixDef& sufdef, const std::vector& values) { if (values.empty()) return; diff --git a/solvers/gurobidirect/gurobibackend.cc b/solvers/gurobidirect/gurobibackend.cc index 7deac7873..6136b10bc 100644 --- a/solvers/gurobidirect/gurobibackend.cc +++ b/solvers/gurobidirect/gurobibackend.cc @@ -88,7 +88,8 @@ int GurobiBackend::NumberOfObjectives() const { void GurobiBackend::PrimalSolution(std::vector &x) { int num_vars = NumberOfVariables(); x.resize(num_vars); - int error = GRBgetdblattrarray(model, GRB_DBL_ATTR_X, 0, num_vars, x.data()); + int error = GRBgetdblattrarray(model, GRB_DBL_ATTR_X, + 0, num_vars, x.data()); if (error) x.clear(); } @@ -96,7 +97,8 @@ void GurobiBackend::PrimalSolution(std::vector &x) { void GurobiBackend::DualSolution(std::vector &pi) { int num_cons = NumberOfConstraints(); pi.resize(num_cons); - int error = GRBgetdblattrarray(model, GRB_DBL_ATTR_PI, 0, num_cons, pi.data()); + int error = GRBgetdblattrarray(model, GRB_DBL_ATTR_PI, + 0, num_cons, pi.data()); if (error) pi.clear(); } @@ -108,7 +110,8 @@ double GurobiBackend::ObjectiveValue() const { void GurobiBackend::VarStatii(std::vector &stt) { int num_vars = NumberOfVariables(); stt.resize(num_vars); - int error = GRBgetintattrarray(model, GRB_INT_ATTR_VBASIS, 0, num_vars, stt.data()); + int error = GRBgetintattrarray(model, GRB_INT_ATTR_VBASIS, + 0, num_vars, stt.data()); if (error) stt.clear(); } @@ -116,7 +119,8 @@ void GurobiBackend::VarStatii(std::vector &stt) { void GurobiBackend::ConStatii(std::vector &stt) { int num_cons = NumberOfConstraints(); stt.resize(num_cons); - int error = GRBgetintattrarray(model, GRB_INT_ATTR_CBASIS, 0, num_cons, stt.data()); + int error = GRBgetintattrarray(model, GRB_INT_ATTR_CBASIS, + 0, num_cons, stt.data()); if (error) stt.clear(); } @@ -152,7 +156,7 @@ std::string GurobiBackend::ConvertSolutionStatus( default: // Fall through. if (interrupter.Stop()) { - solve_code = 600; + solve_code = sol::INTERRUPTED; return "interrupted"; } int solcount; @@ -196,10 +200,11 @@ void GurobiBackend::AddVariable(Variable var) { void GurobiBackend::AddLinearObjective( const LinearObjective& lo ) { if (1>=NumberOfObjectives()) { GRB_CALL( GRBsetintattr(model, GRB_INT_ATTR_MODELSENSE, - obj::Type::MAX==lo.get_sense() ? GRB_MAXIMIZE : GRB_MINIMIZE) ); + obj::Type::MAX==lo.get_sense() ? GRB_MAXIMIZE : GRB_MINIMIZE) ); GRB_CALL( GRBsetdblattrlist(model, GRB_DBL_ATTR_OBJ, lo.get_num_terms(), - (int*)lo.get_vars().data(), (double*)lo.get_coefs().data()) ); + (int*)lo.get_vars().data(), + (double*)lo.get_coefs().data()) ); } else { throw std::runtime_error("Multiple objectives not supported"); // TODO @@ -219,28 +224,28 @@ void GurobiBackend::AddQuadraticObjective(const QuadraticObjective &qo) { } } - void GurobiBackend::AddConstraint( const LinearConstraint& lc ) { GRB_CALL( GRBaddrangeconstr(model, lc.nnz(), - (int*)lc.pvars(), (double*)lc.pcoefs(), lc.lb(), lc.ub(), NULL) ); + (int*)lc.pvars(), (double*)lc.pcoefs(), + lc.lb(), lc.ub(), NULL) ); } void GurobiBackend::AddConstraint( const QuadraticConstraint& qc ) { const auto& qt = qc.GetQPTerms(); if (qc.lb()==qc.ub()) GRB_CALL( GRBaddqconstr(model, qc.nnz(), (int*)qc.pvars(), (double*)qc.pcoefs(), - qt.num_terms(), (int*)qt.vars1(), (int*)qt.vars2(), (double*)qt.coefs(), - GRB_EQUAL, qc.lb(), NULL) ); + qt.num_terms(), (int*)qt.vars1(), (int*)qt.vars2(), + (double*)qt.coefs(), GRB_EQUAL, qc.lb(), NULL) ); else { // Let solver deal with lb>~ub etc. if (qc.lb()>MinusInfinity()) { GRB_CALL( GRBaddqconstr(model, qc.nnz(), (int*)qc.pvars(), (double*)qc.pcoefs(), - qt.num_terms(), (int*)qt.vars1(), (int*)qt.vars2(), (double*)qt.coefs(), - GRB_GREATER_EQUAL, qc.lb(), NULL) ); + qt.num_terms(), (int*)qt.vars1(), (int*)qt.vars2(), + (double*)qt.coefs(), GRB_GREATER_EQUAL, qc.lb(), NULL) ); } if (qc.ub()& stt) { + int num_vars = NumberOfVariables(); + stt.resize(num_vars); + int error = GRBgetintattrarray(model, GRB_INT_ATTR_IIS_LB, + 0, stt.size(), stt.data()); + if (error) + stt.clear(); +} + +void GurobiBackend::ConsIIS(std::vector& stt) { + int nl = GetGrbIntAttribute(GRB_INT_ATTR_NUMSOS) + + GetGrbIntAttribute(GRB_INT_ATTR_NUMQCONSTRS) + + GetGrbIntAttribute(GRB_INT_ATTR_NUMGENCONSTRS); + stt.resize((std::size_t)NumberOfConstraints() + nl); + int error = GRBgetintattrarray(model, GRB_INT_ATTR_IIS_CONSTR, + 0, stt.size()-nl, stt.data()+nl); + if (error) + stt.clear(); +} + +double GurobiBackend::MIPGap() { + return GetGrbDblAttribute(GRB_DBL_ATTR_MIPGAP); +} } // namespace mp diff --git a/solvers/gurobidirect/gurobibackend.h b/solvers/gurobidirect/gurobibackend.h index 4101fceae..3ba72182d 100644 --- a/solvers/gurobidirect/gurobibackend.h +++ b/solvers/gurobidirect/gurobibackend.h @@ -22,14 +22,14 @@ extern "C" { #include -#include "mp/convert/backend.h" +#include "mp/convert/mip_backend.h" #include "mp/convert/std_constr.h" namespace mp { -class GurobiBackend : public BasicBackend +class GurobiBackend : public MIPBackend { - using BaseBackend = BasicBackend; + using BaseBackend = MIPBackend; //////////////////// [[ The public interface ]] ////////////////////// public: @@ -122,6 +122,12 @@ class GurobiBackend : public BasicBackend void VarStatii(std::vector& stt); void ConStatii(std::vector& stt); + + void VarsIIS(std::vector& stt); + void ConsIIS(std::vector& stt); + + double MIPGap(); + /// Solution attributes double NodeCount() const; double Niterations() const; @@ -132,6 +138,8 @@ class GurobiBackend : public BasicBackend private: GRBenv *env = NULL; GRBmodel *model = NULL; + + int optimstatus; // Stores gurobi optimization status after SolveAndReportIntermediateResults public: void OpenSolver(); @@ -149,9 +157,13 @@ class GurobiBackend : public BasicBackend /// for direct access struct Options { std::string exportFile_; + int exportIIS_; }; + Options storedOptions_; + + public: /// These methods access Gurobi options. Used by AddSolverOption() void GetSolverOption(const char* key, int& value) const; @@ -161,6 +173,11 @@ class GurobiBackend : public BasicBackend void GetSolverOption(const char* key, std::string& value) const; void SetSolverOption(const char* key, const std::string& value); + + // Calculate MIP backend related quantities + void ComputeIIS(); + void ComputeMIPGap() {} + }; } // namespace mp