diff --git a/src/solver/GlpkLpSolver.cpp b/src/solver/GlpkLpSolver.cpp index e03f86066..a9d2b4da0 100644 --- a/src/solver/GlpkLpSolver.cpp +++ b/src/solver/GlpkLpSolver.cpp @@ -15,12 +15,14 @@ extern log4cplus::Logger logger; bool GlpkLpSolverOptionsRegistered = storm::settings::Settings::registerNewModule([] (storm::settings::Settings* instance) -> bool { instance->addOption(storm::settings::OptionBuilder("GlpkLpSolver", "glpkoutput", "", "If set, the glpk output will be printed to the command line.").build()); + instance->addOption(storm::settings::OptionBuilder("GurobiLpSolver", "glpkinttol", "", "Sets glpk's precision for integer variables.").addArgument(storm::settings::ArgumentBuilder::createDoubleArgument("value", "The precision to achieve.").setDefaultValueDouble(1e-06).addValidationFunctionDouble(storm::settings::ArgumentValidators::doubleRangeValidatorExcluding(0.0, 1.0)).build()).build()); + return true; }); namespace storm { namespace solver { - GlpkLpSolver::GlpkLpSolver(std::string const& name, ModelSense const& modelSense) : LpSolver(modelSense), lp(nullptr), nextVariableIndex(1), nextConstraintIndex(1), rowIndices(), columnIndices(), coefficientValues() { + GlpkLpSolver::GlpkLpSolver(std::string const& name, ModelSense const& modelSense) : LpSolver(modelSense), lp(nullptr), nextVariableIndex(1), nextConstraintIndex(1), modelContainsIntegerVariables(false), isInfeasibleFlag(false), isUnboundedFlag(false), rowIndices(), columnIndices(), coefficientValues() { // Create the LP problem for glpk. lp = glp_create_prob(); @@ -44,6 +46,10 @@ namespace storm { // Intentionally left empty. } + GlpkLpSolver::GlpkLpSolver(ModelSense const& modelSense) : GlpkLpSolver("", modelSense) { + // Intentionally left empty. + } + GlpkLpSolver::~GlpkLpSolver() { // Dispose of all objects allocated dynamically by glpk. glp_delete_prob(this->lp); @@ -78,12 +84,14 @@ namespace storm { uint_fast64_t GlpkLpSolver::createIntegerVariable(std::string const& name, VariableType const& variableType, double lowerBound, double upperBound, double objectiveFunctionCoefficient) { uint_fast64_t index = this->createContinuousVariable(name, variableType, lowerBound, upperBound, objectiveFunctionCoefficient); glp_set_col_kind(this->lp, index, GLP_IV); + this->modelContainsIntegerVariables = true; return index; } uint_fast64_t GlpkLpSolver::createBinaryVariable(std::string const& name, double objectiveFunctionCoefficient) { uint_fast64_t index = this->createContinuousVariable(name, UNBOUNDED, 0, 1, objectiveFunctionCoefficient); glp_set_col_kind(this->lp, index, GLP_BV); + this->modelContainsIntegerVariables = true; return index; } @@ -128,11 +136,36 @@ namespace storm { } void GlpkLpSolver::optimize() const { + // First, reset the flags. + this->isInfeasibleFlag = false; + this->isUnboundedFlag = false; + // Start by setting the model sense. glp_set_obj_dir(this->lp, this->getModelSense() == MINIMIZE ? GLP_MIN : GLP_MAX); glp_load_matrix(this->lp, rowIndices.size() - 1, rowIndices.data(), columnIndices.data(), coefficientValues.data()); - int error = glp_simplex(this->lp, nullptr); + + int error = 0; + if (this->modelContainsIntegerVariables) { + glp_iocp* parameters = new glp_iocp; + glp_init_iocp(parameters); + parameters->presolve = GLP_ON; + parameters->tol_int = storm::settings::Settings::getInstance()->getOptionByLongName("glpkinttol").getArgument(0).getValueAsDouble(); + error = glp_intopt(this->lp, parameters); + delete parameters; + + // In case the error is caused by an infeasible problem, we do not want to view this as an error and + // reset the error code. + if (error == GLP_ENOPFS) { + this->isInfeasibleFlag = true; + error = 0; + } else if (error == GLP_ENODFS) { + this->isUnboundedFlag = true; + error = 0; + } + } else { + error = glp_simplex(this->lp, nullptr); + } if (error != 0) { LOG4CPLUS_ERROR(logger, "Unable to optimize glpk model (" << error << ")."); @@ -143,26 +176,64 @@ namespace storm { } bool GlpkLpSolver::isInfeasible() const { - int status = glp_get_status(this->lp); - return status == GLP_INFEAS; + if (!this->currentModelHasBeenOptimized) { + throw storm::exceptions::InvalidStateException() << "Illegal call to GlpkLpSolver::isInfeasible: model has not been optimized."; + } + + if (this->modelContainsIntegerVariables) { + return isInfeasibleFlag; + } else { + return glp_get_status(this->lp) == GLP_INFEAS; + } } bool GlpkLpSolver::isUnbounded() const { - int status = glp_get_status(this->lp); - return status == GLP_UNBND; + if (!this->currentModelHasBeenOptimized) { + throw storm::exceptions::InvalidStateException() << "Illegal call to GlpkLpSolver::isUnbounded: model has not been optimized."; + } + + if (this->modelContainsIntegerVariables) { + return isUnboundedFlag; + } else { + return glp_get_status(this->lp) == GLP_UNBND; + } } bool GlpkLpSolver::isOptimal() const { if (!this->currentModelHasBeenOptimized) { return false; } - int status = glp_get_status(this->lp); + + int status = 0; + if (this->modelContainsIntegerVariables) { + status = glp_mip_status(this->lp); + } else { + status = glp_get_status(this->lp); + } return status == GLP_OPT; } int_fast64_t GlpkLpSolver::getIntegerValue(uint_fast64_t variableIndex) const { - double value = glp_get_col_prim(this->lp, static_cast(variableIndex)); - + if (!this->isOptimal()) { + if (this->isInfeasible()) { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from infeasible model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from infeasible model."; + } else if (this->isUnbounded()) { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from unbounded model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from unbounded model."; + } else { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from unoptimized model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from unoptimized model."; + } + } + + double value = 0; + if (this->modelContainsIntegerVariables) { + value = glp_mip_col_val(this->lp, static_cast(variableIndex)); + } else { + value = glp_get_col_prim(this->lp, static_cast(variableIndex)); + } + if (std::abs(value - static_cast(value)) <= storm::settings::Settings::getInstance()->getOptionByLongName("precision").getArgument(0).getValueAsDouble()) { // Nothing to do in this case. } else if (std::abs(value) > storm::settings::Settings::getInstance()->getOptionByLongName("precision").getArgument(0).getValueAsDouble()) { @@ -174,7 +245,25 @@ namespace storm { } bool GlpkLpSolver::getBinaryValue(uint_fast64_t variableIndex) const { - double value = glp_get_col_prim(this->lp, static_cast(variableIndex)); + if (!this->isOptimal()) { + if (this->isInfeasible()) { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from infeasible model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from infeasible model."; + } else if (this->isUnbounded()) { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from unbounded model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from unbounded model."; + } else { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from unoptimized model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from unoptimized model."; + } + } + + double value = 0; + if (this->modelContainsIntegerVariables) { + value = glp_mip_col_val(this->lp, static_cast(variableIndex)); + } else { + value = glp_get_col_prim(this->lp, static_cast(variableIndex)); + } if (std::abs(value - 1) <= storm::settings::Settings::getInstance()->getOptionByLongName("precision").getArgument(0).getValueAsDouble()) { // Nothing to do in this case. @@ -187,7 +276,50 @@ namespace storm { } double GlpkLpSolver::getContinuousValue(uint_fast64_t variableIndex) const { - return glp_get_col_prim(this->lp, static_cast(variableIndex)); + if (!this->isOptimal()) { + if (this->isInfeasible()) { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from infeasible model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from infeasible model."; + } else if (this->isUnbounded()) { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from unbounded model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from unbounded model."; + } else { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from unoptimized model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from unoptimized model."; + } + } + + double value = 0; + if (this->modelContainsIntegerVariables) { + value = glp_mip_col_val(this->lp, static_cast(variableIndex)); + } else { + value = glp_get_col_prim(this->lp, static_cast(variableIndex)); + } + return value; + } + + double GlpkLpSolver::getObjectiveValue() const { + if (!this->isOptimal()) { + if (this->isInfeasible()) { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from infeasible model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from infeasible model."; + } else if (this->isUnbounded()) { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from unbounded model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from unbounded model."; + } else { + LOG4CPLUS_ERROR(logger, "Unable to get glpk solution from unoptimized model."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from unoptimized model."; + } + } + + double value = 0; + if (this->modelContainsIntegerVariables) { + value = glp_mip_obj_val(this->lp); + } else { + value = glp_get_obj_val(this->lp); + } + + return value; } void GlpkLpSolver::writeModelToFile(std::string const& filename) const { diff --git a/src/solver/GlpkLpSolver.h b/src/solver/GlpkLpSolver.h index 87707b283..41ddeeb8c 100644 --- a/src/solver/GlpkLpSolver.h +++ b/src/solver/GlpkLpSolver.h @@ -37,6 +37,14 @@ namespace storm { */ GlpkLpSolver(std::string const& name); + /*! + * Constructs a solver without a name and the given model sense. + * + * @param modelSense A value indicating whether the value of the objective function is to be minimized or + * maximized. + */ + GlpkLpSolver(ModelSense const& modelSense); + /*! * Constructs a solver without a name. By default the objective function is assumed to be minimized, * but this may be altered later using a call to setModelSense. @@ -62,7 +70,8 @@ namespace storm { virtual int_fast64_t getIntegerValue(uint_fast64_t variableIndex) const override; virtual bool getBinaryValue(uint_fast64_t variableIndex) const override; virtual double getContinuousValue(uint_fast64_t variableIndex) const override; - + virtual double getObjectiveValue() const override; + virtual void writeModelToFile(std::string const& filename) const override; private: @@ -75,6 +84,13 @@ namespace storm { // A counter that keeps track of the next free constraint index. uint_fast64_t nextConstraintIndex; + // A flag storing whether the model is an LP or an MILP. + bool modelContainsIntegerVariables; + + // Flags that store whether the MILP was found to be infeasible or unbounded. + mutable bool isInfeasibleFlag; + mutable bool isUnboundedFlag; + // The arrays that store the coefficient matrix of the problem. std::vector rowIndices; std::vector columnIndices; @@ -92,6 +108,10 @@ namespace storm { throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for glpk. Yet, a method was called that requires this support. Please choose a version of support with glpk support."; } + GlpkLpSolver(ModelSense const& modelSense) : LpSolver(modelSense) { + throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for glpk. Yet, a method was called that requires this support. Please choose a version of support with glpk support."; + } + GlpkLpSolver() : LpSolver(MINIMIZE) { throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for glpk. Yet, a method was called that requires this support. Please choose a version of support with glpk support."; } @@ -144,6 +164,10 @@ namespace storm { throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for glpk. Yet, a method was called that requires this support. Please choose a version of support with glpk support."; } + virtual double getObjectiveValue() const override { + throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for glpk. Yet, a method was called that requires this support. Please choose a version of support with glpk support."; + } + virtual void writeModelToFile(std::string const& filename) const override { throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for glpk. Yet, a method was called that requires this support. Please choose a version of support with glpk support."; } diff --git a/src/solver/GurobiLpSolver.cpp b/src/solver/GurobiLpSolver.cpp index e062e9935..4dfa8f89c 100644 --- a/src/solver/GurobiLpSolver.cpp +++ b/src/solver/GurobiLpSolver.cpp @@ -46,6 +46,10 @@ namespace storm { // Intentionally left empty. } + GurobiLpSolver::GurobiLpSolver(ModelSense const& modelSense) : GurobiLpSolver("", modelSense) { + // Intentionally left empty. + } + GurobiLpSolver::GurobiLpSolver() : GurobiLpSolver("", MINIMIZE) { // Intentionally left empty. } @@ -64,7 +68,7 @@ namespace storm { error = GRBsetintparam(env, "Threads", storm::settings::Settings::getInstance()->getOptionByLongName("gurobithreads").getArgument(0).getValueAsUnsignedInteger()); // Enable the following line to force Gurobi to be as precise about the binary variables as required by the given precision option. - error = GRBsetdblparam(env, "IntFeasTol", storm::settings::Settings::getInstance()->getOptionByLongName("precision").getArgument(0).getValueAsDouble()); + error = GRBsetdblparam(env, "IntFeasTol", storm::settings::Settings::getInstance()->getOptionByLongName("gurobiinttol").getArgument(0).getValueAsDouble()); } void GurobiLpSolver::updateModel() const { @@ -198,6 +202,10 @@ namespace storm { } bool GurobiLpSolver::isInfeasible() const { + if (!this->currentModelHasBeenOptimized) { + throw storm::exceptions::InvalidStateException() << "Illegal call to GurobiLpSolver::isInfeasible: model has not been optimized."; + } + int optimalityStatus = 0; int error = GRBgetintattr(model, GRB_INT_ATTR_STATUS, &optimalityStatus); @@ -206,10 +214,39 @@ namespace storm { throw storm::exceptions::InvalidStateException() << "Unable to retrieve optimization status of Gurobi model (" << GRBgeterrormsg(env) << ")."; } + // By default, Gurobi may tell us only that the model is either infeasible or unbounded. To decide which one + // it is, we need to perform an extra step. + if (optimalityStatus == GRB_INF_OR_UNBD) { + std::cout << "here" << std::endl; + error = GRBsetintparam(GRBgetenv(model), GRB_INT_PAR_DUALREDUCTIONS, 0); + if (error) { + LOG4CPLUS_ERROR(logger, "Unable to set Gurobi parameter (" << GRBgeterrormsg(env) << ")."); + throw storm::exceptions::InvalidStateException() << "Unable to set Gurobi parameter (" << GRBgeterrormsg(env) << ")."; + } + + this->optimize(); + + error = GRBgetintattr(model, GRB_INT_ATTR_STATUS, &optimalityStatus); + if (error) { + LOG4CPLUS_ERROR(logger, "Unable to retrieve optimization status of Gurobi model (" << GRBgeterrormsg(env) << ")."); + throw storm::exceptions::InvalidStateException() << "Unable to retrieve optimization status of Gurobi model (" << GRBgeterrormsg(env) << ")."; + } + + error = GRBsetintparam(GRBgetenv(model), GRB_INT_PAR_DUALREDUCTIONS, 1); + if (error) { + LOG4CPLUS_ERROR(logger, "Unable to set Gurobi parameter (" << GRBgeterrormsg(env) << ")."); + throw storm::exceptions::InvalidStateException() << "Unable to set Gurobi parameter (" << GRBgeterrormsg(env) << ")."; + } + } + return optimalityStatus == GRB_INFEASIBLE; } bool GurobiLpSolver::isUnbounded() const { + if (!this->currentModelHasBeenOptimized) { + throw storm::exceptions::InvalidStateException() << "Illegal call to GurobiLpSolver::isUnbounded: model has not been optimized."; + } + int optimalityStatus = 0; int error = GRBgetintattr(model, GRB_INT_ATTR_STATUS, &optimalityStatus); @@ -218,6 +255,30 @@ namespace storm { throw storm::exceptions::InvalidStateException() << "Unable to retrieve optimization status of Gurobi model (" << GRBgeterrormsg(env) << ")."; } + // By default, Gurobi may tell us only that the model is either infeasible or unbounded. To decide which one + // it is, we need to perform an extra step. + if (optimalityStatus == GRB_INF_OR_UNBD) { + error = GRBsetintparam(GRBgetenv(model), GRB_INT_PAR_DUALREDUCTIONS, 0); + if (error) { + LOG4CPLUS_ERROR(logger, "Unable to set Gurobi parameter (" << GRBgeterrormsg(env) << ")."); + throw storm::exceptions::InvalidStateException() << "Unable to set Gurobi parameter (" << GRBgeterrormsg(env) << ")."; + } + + this->optimize(); + + error = GRBgetintattr(model, GRB_INT_ATTR_STATUS, &optimalityStatus); + if (error) { + LOG4CPLUS_ERROR(logger, "Unable to retrieve optimization status of Gurobi model (" << GRBgeterrormsg(env) << ")."); + throw storm::exceptions::InvalidStateException() << "Unable to retrieve optimization status of Gurobi model (" << GRBgeterrormsg(env) << ")."; + } + + error = GRBsetintparam(GRBgetenv(model), GRB_INT_PAR_DUALREDUCTIONS, 1); + if (error) { + LOG4CPLUS_ERROR(logger, "Unable to set Gurobi parameter (" << GRBgeterrormsg(env) << ")."); + throw storm::exceptions::InvalidStateException() << "Unable to set Gurobi parameter (" << GRBgeterrormsg(env) << ")."; + } + } + return optimalityStatus == GRB_UNBOUNDED; } @@ -322,6 +383,30 @@ namespace storm { return value; } + double GurobiLpSolver::getObjectiveValue() const { + if (!this->isOptimal()) { + if (this->isInfeasible()) { + LOG4CPLUS_ERROR(logger, "Unable to get Gurobi solution from infeasible model (" << GRBgeterrormsg(env) << ")."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from infeasible model (" << GRBgeterrormsg(env) << ")."; + } else if (this->isUnbounded()) { + LOG4CPLUS_ERROR(logger, "Unable to get Gurobi solution from unbounded model (" << GRBgeterrormsg(env) << ")."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from unbounded model (" << GRBgeterrormsg(env) << ")."; + } else { + LOG4CPLUS_ERROR(logger, "Unable to get Gurobi solution from unoptimized model (" << GRBgeterrormsg(env) << ")."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution from unoptimized model (" << GRBgeterrormsg(env) << ")."; + } + } + + double value = 0; + int error = GRBgetdblattr(model, GRB_DBL_ATTR_OBJVAL, &value); + if (error) { + LOG4CPLUS_ERROR(logger, "Unable to get Gurobi solution (" << GRBgeterrormsg(env) << ")."); + throw storm::exceptions::InvalidStateException() << "Unable to get Gurobi solution (" << GRBgeterrormsg(env) << ")."; + } + + return value; + } + void GurobiLpSolver::writeModelToFile(std::string const& filename) const { int error = GRBwrite(model, filename.c_str()); if (error) { diff --git a/src/solver/GurobiLpSolver.h b/src/solver/GurobiLpSolver.h index 3e361843e..2057ffed2 100644 --- a/src/solver/GurobiLpSolver.h +++ b/src/solver/GurobiLpSolver.h @@ -41,6 +41,14 @@ namespace storm { */ GurobiLpSolver(std::string const& name); + /*! + * Constructs a solver without a name and the given model sense. + * + * @param modelSense A value indicating whether the value of the objective function is to be minimized or + * maximized. + */ + GurobiLpSolver(ModelSense const& modelSense); + /*! * Constructs a solver without a name. By default the objective function is assumed to be minimized, * but this may be altered later using a call to setModelSense. @@ -66,7 +74,8 @@ namespace storm { virtual int_fast64_t getIntegerValue(uint_fast64_t variableIndex) const override; virtual bool getBinaryValue(uint_fast64_t variableIndex) const override; virtual double getContinuousValue(uint_fast64_t variableIndex) const override; - + virtual double getObjectiveValue() const override; + virtual void writeModelToFile(std::string const& filename) const override; private: @@ -101,6 +110,10 @@ namespace storm { throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for Gurobi. Yet, a method was called that requires this support. Please choose a version of support with Gurobi support."; } + GurobiLpSolver(ModelSense const& modelSense) { + throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for Gurobi. Yet, a method was called that requires this support. Please choose a version of support with Gurobi support."; + } + GurobiLpSolver() { throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for Gurobi. Yet, a method was called that requires this support. Please choose a version of support with Gurobi support."; } @@ -153,6 +166,10 @@ namespace storm { throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for Gurobi. Yet, a method was called that requires this support. Please choose a version of support with Gurobi support."; } + virtual double getObjectiveValue() const override { + throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for Gurobi. Yet, a method was called that requires this support. Please choose a version of support with Gurobi support."; + } + virtual void writeModelToFile(std::string const& filename) const override { throw storm::exceptions::NotImplementedException() << "This version of StoRM was compiled without support for Gurobi. Yet, a method was called that requires this support. Please choose a version of support with Gurobi support."; } diff --git a/src/solver/LpSolver.h b/src/solver/LpSolver.h index 96e8e469c..f827f7462 100644 --- a/src/solver/LpSolver.h +++ b/src/solver/LpSolver.h @@ -152,6 +152,7 @@ namespace storm { * * @param variableIndex The index of the integer variable whose value to query. If this index does not * belong to a previously declared integer variable, the behaviour is undefined. + * @return The value of the integer variable in the optimal solution. */ virtual int_fast64_t getIntegerValue(uint_fast64_t variableIndex) const = 0; @@ -161,6 +162,7 @@ namespace storm { * * @param variableIndex The index of the binary variable whose value to query. If this index does not * belong to a previously declared binary variable, the behaviour is undefined. + * @return The value of the binary variable in the optimal solution. */ virtual bool getBinaryValue(uint_fast64_t variableIndex) const = 0; @@ -170,9 +172,18 @@ namespace storm { * * @param variableIndex The index of the continuous variable whose value to query. If this index does not * belong to a previously declared continuous variable, the behaviour is undefined. + * @return The value of the continuous variable in the optimal solution. */ virtual double getContinuousValue(uint_fast64_t variableIndex) const = 0; + /*! + * Retrieves the value of the objective function. Note that this may only be called, if the model was found + * to be optimal, i.e. iff isOptimal() returns true. + * + * @return The value of the objective function in the optimal solution. + */ + virtual double getObjectiveValue() const = 0; + /*! * Writes the current LP problem to the given file. * diff --git a/test/functional/solver/GlpkLpSolverTest.cpp b/test/functional/solver/GlpkLpSolverTest.cpp new file mode 100644 index 000000000..72d6311a0 --- /dev/null +++ b/test/functional/solver/GlpkLpSolverTest.cpp @@ -0,0 +1,103 @@ +#include "gtest/gtest.h" +#include "storm-config.h" + +#include "src/solver/GlpkLpSolver.h" +#include "src/exceptions/InvalidStateException.h" +#include "src/settings/Settings.h" + +TEST(GlpkLpSolver, Optimize) { +#ifdef STORM_HAVE_GLPK + storm::solver::GlpkLpSolver solver(storm::solver::LpSolver::MAXIMIZE); + uint_fast64_t xIndex; + ASSERT_NO_THROW(xIndex = solver.createBinaryVariable("x", -1)); + uint_fast64_t yIndex; + ASSERT_NO_THROW(yIndex = solver.createIntegerVariable("y", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 2)); + uint_fast64_t zIndex; + ASSERT_NO_THROW(zIndex = solver.createContinuousVariable("z", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 1)); + + ASSERT_NO_THROW(solver.addConstraint("", {xIndex, yIndex, zIndex}, {1, 1, 1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 12)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex, zIndex, xIndex}, {0.5, 1, -1}, storm::solver::LpSolver::BoundType::EQUAL, 5)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex, xIndex}, {1, -1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 5.5)); + + ASSERT_NO_THROW(solver.optimize()); + ASSERT_TRUE(solver.isOptimal()); + ASSERT_FALSE(solver.isUnbounded()); + ASSERT_FALSE(solver.isInfeasible()); + bool xValue = false; + ASSERT_NO_THROW(xValue = solver.getBinaryValue(xIndex)); + ASSERT_EQ(true, xValue); + int_fast64_t yValue = 0; + ASSERT_NO_THROW(yValue = solver.getIntegerValue(yIndex)); + ASSERT_EQ(6, yValue); + double zValue = 0; + ASSERT_NO_THROW(zValue = solver.getContinuousValue(zIndex)); + ASSERT_LT(std::abs(zValue - 3), storm::settings::Settings::getInstance()->getOptionByLongName("precision").getArgument(0).getValueAsDouble()); + double objectiveValue = 0; + ASSERT_NO_THROW(objectiveValue = solver.getObjectiveValue()); + ASSERT_LT(std::abs(objectiveValue - 14), storm::settings::Settings::getInstance()->getOptionByLongName("precision").getArgument(0).getValueAsDouble()); +#else + ASSERT_TRUE(false, "StoRM built without Gurobi support."); +#endif +} + +TEST(GlpkLpSolver, Infeasible) { +#ifdef STORM_HAVE_GLPK + storm::solver::GlpkLpSolver solver(storm::solver::LpSolver::MAXIMIZE); + uint_fast64_t xIndex; + ASSERT_NO_THROW(xIndex = solver.createBinaryVariable("x", -1)); + uint_fast64_t yIndex; + ASSERT_NO_THROW(yIndex = solver.createIntegerVariable("y", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 2)); + uint_fast64_t zIndex; + ASSERT_NO_THROW(zIndex = solver.createContinuousVariable("z", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 1)); + + ASSERT_NO_THROW(solver.addConstraint("", {xIndex, yIndex, zIndex}, {1, 1, 1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 12)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex, zIndex, xIndex}, {0.5, 1, -1}, storm::solver::LpSolver::BoundType::EQUAL, 5)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex, xIndex}, {1, -1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 5.5)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex}, {1}, storm::solver::LpSolver::BoundType::GREATER_EQUAL, 7)); + + ASSERT_NO_THROW(solver.optimize()); + ASSERT_FALSE(solver.isOptimal()); + ASSERT_FALSE(solver.isUnbounded()); + ASSERT_TRUE(solver.isInfeasible()); + bool xValue = false; + ASSERT_THROW(xValue = solver.getBinaryValue(xIndex), storm::exceptions::InvalidStateException); + int_fast64_t yValue = 0; + ASSERT_THROW(yValue = solver.getIntegerValue(yIndex), storm::exceptions::InvalidStateException); + double zValue = 0; + ASSERT_THROW(zValue = solver.getContinuousValue(zIndex), storm::exceptions::InvalidStateException); + double objectiveValue = 0; + ASSERT_THROW(objectiveValue = solver.getObjectiveValue(), storm::exceptions::InvalidStateException); +#else + ASSERT_TRUE(false, "StoRM built without Gurobi support."); +#endif +} + +TEST(GlpkLpSolver, Unbounded) { +#ifdef STORM_HAVE_GLPK + storm::solver::GlpkLpSolver solver(storm::solver::LpSolver::MAXIMIZE); + uint_fast64_t xIndex; + ASSERT_NO_THROW(xIndex = solver.createBinaryVariable("x", -1)); + uint_fast64_t yIndex; + ASSERT_NO_THROW(yIndex = solver.createIntegerVariable("y", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 2)); + uint_fast64_t zIndex; + ASSERT_NO_THROW(zIndex = solver.createContinuousVariable("z", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 1)); + + ASSERT_NO_THROW(solver.addConstraint("", {xIndex, yIndex, zIndex}, {1, 1, -1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 12)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex, xIndex}, {1, -1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 5.5)); + + ASSERT_NO_THROW(solver.optimize()); + ASSERT_FALSE(solver.isOptimal()); + ASSERT_TRUE(solver.isUnbounded()); + ASSERT_FALSE(solver.isInfeasible()); + bool xValue = false; + ASSERT_THROW(xValue = solver.getBinaryValue(xIndex), storm::exceptions::InvalidStateException); + int_fast64_t yValue = 0; + ASSERT_THROW(yValue = solver.getIntegerValue(yIndex), storm::exceptions::InvalidStateException); + double zValue = 0; + ASSERT_THROW(zValue = solver.getContinuousValue(zIndex), storm::exceptions::InvalidStateException); + double objectiveValue = 0; + ASSERT_THROW(objectiveValue = solver.getObjectiveValue(), storm::exceptions::InvalidStateException); +#else + ASSERT_TRUE(false, "StoRM built without Gurobi support."); +#endif +} diff --git a/test/functional/solver/GurobiLpSolverTest.cpp b/test/functional/solver/GurobiLpSolverTest.cpp new file mode 100644 index 000000000..d1cca8e08 --- /dev/null +++ b/test/functional/solver/GurobiLpSolverTest.cpp @@ -0,0 +1,103 @@ +#include "gtest/gtest.h" +#include "storm-config.h" + +#include "src/solver/GurobiLpSolver.h" +#include "src/exceptions/InvalidStateException.h" +#include "src/settings/Settings.h" + +TEST(GurobiLpSolver, Optimize) { +#ifdef STORM_HAVE_GUROBI + storm::solver::GurobiLpSolver solver(storm::solver::LpSolver::MAXIMIZE); + uint_fast64_t xIndex; + ASSERT_NO_THROW(xIndex = solver.createBinaryVariable("x", -1)); + uint_fast64_t yIndex; + ASSERT_NO_THROW(yIndex = solver.createIntegerVariable("y", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 2)); + uint_fast64_t zIndex; + ASSERT_NO_THROW(zIndex = solver.createContinuousVariable("z", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 1)); + + ASSERT_NO_THROW(solver.addConstraint("", {xIndex, yIndex, zIndex}, {1, 1, 1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 12)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex, zIndex, xIndex}, {0.5, 1, -1}, storm::solver::LpSolver::BoundType::EQUAL, 5)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex, xIndex}, {1, -1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 5.5)); + + ASSERT_NO_THROW(solver.optimize()); + ASSERT_TRUE(solver.isOptimal()); + ASSERT_FALSE(solver.isUnbounded()); + ASSERT_FALSE(solver.isInfeasible()); + bool xValue = false; + ASSERT_NO_THROW(xValue = solver.getBinaryValue(xIndex)); + ASSERT_EQ(true, xValue); + int_fast64_t yValue = 0; + ASSERT_NO_THROW(yValue = solver.getIntegerValue(yIndex)); + ASSERT_EQ(6, yValue); + double zValue = 0; + ASSERT_NO_THROW(zValue = solver.getContinuousValue(zIndex)); + ASSERT_LT(std::abs(zValue - 3), storm::settings::Settings::getInstance()->getOptionByLongName("precision").getArgument(0).getValueAsDouble()); + double objectiveValue = 0; + ASSERT_NO_THROW(objectiveValue = solver.getObjectiveValue()); + ASSERT_LT(std::abs(objectiveValue - 14), storm::settings::Settings::getInstance()->getOptionByLongName("precision").getArgument(0).getValueAsDouble()); +#else + ASSERT_TRUE(false, "StoRM built without Gurobi support."); +#endif +} + +TEST(GurobiLpSolver, Infeasible) { +#ifdef STORM_HAVE_GUROBI + storm::solver::GurobiLpSolver solver(storm::solver::LpSolver::MAXIMIZE); + uint_fast64_t xIndex; + ASSERT_NO_THROW(xIndex = solver.createBinaryVariable("x", -1)); + uint_fast64_t yIndex; + ASSERT_NO_THROW(yIndex = solver.createIntegerVariable("y", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 2)); + uint_fast64_t zIndex; + ASSERT_NO_THROW(zIndex = solver.createContinuousVariable("z", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 1)); + + ASSERT_NO_THROW(solver.addConstraint("", {xIndex, yIndex, zIndex}, {1, 1, 1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 12)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex, zIndex, xIndex}, {0.5, 1, -1}, storm::solver::LpSolver::BoundType::EQUAL, 5)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex, xIndex}, {1, -1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 5.5)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex}, {1}, storm::solver::LpSolver::BoundType::GREATER_EQUAL, 7)); + + ASSERT_NO_THROW(solver.optimize()); + ASSERT_FALSE(solver.isOptimal()); + ASSERT_FALSE(solver.isUnbounded()); + ASSERT_TRUE(solver.isInfeasible()); + bool xValue = false; + ASSERT_THROW(xValue = solver.getBinaryValue(xIndex), storm::exceptions::InvalidStateException); + int_fast64_t yValue = 0; + ASSERT_THROW(yValue = solver.getIntegerValue(yIndex), storm::exceptions::InvalidStateException); + double zValue = 0; + ASSERT_THROW(zValue = solver.getContinuousValue(zIndex), storm::exceptions::InvalidStateException); + double objectiveValue = 0; + ASSERT_THROW(objectiveValue = solver.getObjectiveValue(), storm::exceptions::InvalidStateException); +#else + ASSERT_TRUE(false, "StoRM built without Gurobi support."); +#endif +} + +TEST(GurobiLpSolver, Unbounded) { +#ifdef STORM_HAVE_GUROBI + storm::solver::GurobiLpSolver solver(storm::solver::LpSolver::MAXIMIZE); + uint_fast64_t xIndex; + ASSERT_NO_THROW(xIndex = solver.createBinaryVariable("x", -1)); + uint_fast64_t yIndex; + ASSERT_NO_THROW(yIndex = solver.createIntegerVariable("y", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 2)); + uint_fast64_t zIndex; + ASSERT_NO_THROW(zIndex = solver.createContinuousVariable("z", storm::solver::LpSolver::VariableType::LOWER_BOUND, 0, 0, 1)); + + ASSERT_NO_THROW(solver.addConstraint("", {xIndex, yIndex, zIndex}, {1, 1, -1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 12)); + ASSERT_NO_THROW(solver.addConstraint("", {yIndex, xIndex}, {1, -1}, storm::solver::LpSolver::BoundType::LESS_EQUAL, 5.5)); + + ASSERT_NO_THROW(solver.optimize()); + ASSERT_FALSE(solver.isOptimal()); + ASSERT_TRUE(solver.isUnbounded()); + ASSERT_FALSE(solver.isInfeasible()); + bool xValue = false; + ASSERT_THROW(xValue = solver.getBinaryValue(xIndex), storm::exceptions::InvalidStateException); + int_fast64_t yValue = 0; + ASSERT_THROW(yValue = solver.getIntegerValue(yIndex), storm::exceptions::InvalidStateException); + double zValue = 0; + ASSERT_THROW(zValue = solver.getContinuousValue(zIndex), storm::exceptions::InvalidStateException); + double objectiveValue = 0; + ASSERT_THROW(objectiveValue = solver.getObjectiveValue(), storm::exceptions::InvalidStateException); +#else + ASSERT_TRUE(false, "StoRM built without Gurobi support."); +#endif +}