From 5a53158910b3c88a22845855edd99fca97985e57 Mon Sep 17 00:00:00 2001 From: Travis Rivera Petit Date: Tue, 28 Jul 2026 16:33:40 +0200 Subject: [PATCH 01/35] Port Gurobi code from issue1199 --- src/search/CMakeLists.txt | 13 +- src/search/cmake/FindGurobi.cmake | 44 +++ src/search/lp/gurobi_solver_interface.cc | 361 +++++++++++++++++++++++ src/search/lp/gurobi_solver_interface.h | 55 ++++ src/search/lp/lp_solver.cc | 13 +- src/search/lp/lp_solver.h | 3 +- 6 files changed, 486 insertions(+), 3 deletions(-) create mode 100644 src/search/cmake/FindGurobi.cmake create mode 100644 src/search/lp/gurobi_solver_interface.cc create mode 100644 src/search/lp/gurobi_solver_interface.h diff --git a/src/search/CMakeLists.txt b/src/search/CMakeLists.txt index 701b6ab9ca..f11a68a43a 100644 --- a/src/search/CMakeLists.txt +++ b/src/search/CMakeLists.txt @@ -601,18 +601,29 @@ create_fast_downward_library( if(USE_LP) find_package(Cplex 12) if(CPLEX_FOUND) + message(STATUS "Found CPLEX: ${CPLEX_DIR}") target_compile_definitions(lp_solver INTERFACE HAS_CPLEX) target_link_libraries(lp_solver INTERFACE cplex::cplex) target_sources(lp_solver INTERFACE lp/cplex_solver_interface.h lp/cplex_solver_interface.cc) endif() + find_package(soplex 7.1.0 QUIET) if (SOPLEX_FOUND) - message(STATUS "Found SoPlex: ${SOPLEX_INCLUDE_DIRS}") + message(STATUS "Found SoPlex: ${SOPLEX_DIR}") target_link_libraries(lp_solver INTERFACE libsoplex) target_compile_definitions(lp_solver INTERFACE HAS_SOPLEX) target_sources(lp_solver INTERFACE lp/soplex_solver_interface.h lp/soplex_solver_interface.cc) endif() + + find_package(Gurobi QUIET) + if(Gurobi_FOUND) + message(STATUS "Found Gurobi: ${GUROBI_LIBRARY} ${GUROBI_INCLUDE_DIR}") + target_compile_definitions(lp_solver INTERFACE HAS_GUROBI) + target_link_libraries(lp_solver INTERFACE gurobi::gurobi) + target_sources(lp_solver INTERFACE lp/gurobi_solver_interface.h lp/gurobi_solver_interface.cc) + endif() + endif() create_fast_downward_library( diff --git a/src/search/cmake/FindGurobi.cmake b/src/search/cmake/FindGurobi.cmake new file mode 100644 index 0000000000..ba9471fff2 --- /dev/null +++ b/src/search/cmake/FindGurobi.cmake @@ -0,0 +1,44 @@ +# Find Gurobi and export the target gurobi::gurobi +# +# Usage: +# find_package(Gurobi) +# target_link_libraries( PRIVATE gurobi::gurobi) +# +# Hints: +# -DGurobi_ROOT=... +# -Dgurobi_DIR=... +# env GUROBI_HOME=... (preferred) + +set(HINT_PATHS ${Gurobi_ROOT} ${gurobi_DIR} $ENV{GUROBI_HOME}) + +find_path(GUROBI_INCLUDE_DIR + NAMES gurobi_c.h gurobi_c++.h + HINTS ${HINT_PATHS} + PATH_SUFFIXES include +) + +# For linux. +find_library(GUROBI_LIBRARY + NAMES + gurobi130 # Gurobi 13.0 + gurobi110 + HINTS ${HINT_PATHS} + PATH_SUFFIXES lib +) + +# Check if everything was found and set Gurobi_FOUND. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + Gurobi + REQUIRED_VARS GUROBI_INCLUDE_DIR GUROBI_LIBRARY +) + +if(Gurobi_FOUND AND NOT TARGET gurobi::gurobi) + add_library(gurobi::gurobi UNKNOWN IMPORTED) + set_target_properties(gurobi::gurobi PROPERTIES + IMPORTED_LOCATION "${GUROBI_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${GUROBI_INCLUDE_DIR}" + ) +endif() + +mark_as_advanced(GUROBI_INCLUDE_DIR GUROBI_LIBRARY) diff --git a/src/search/lp/gurobi_solver_interface.cc b/src/search/lp/gurobi_solver_interface.cc new file mode 100644 index 0000000000..a76f5bb151 --- /dev/null +++ b/src/search/lp/gurobi_solver_interface.cc @@ -0,0 +1,361 @@ +#include "gurobi_solver_interface.h" + +#include "lp_solver.h" + +#include "../utils/system.h" + +#include +#include +#include + +/* + Core Gurobi functionality is provided in C API (gurobi_c.h). + We implement the interface using the C API to avoid linking issues. + + The implementation is based on examples from https://www.gurobi.com/documentation/9.5/refman/c_api_overview.html + + We use the following functions from the Gurobi C API: + - GRBsetdblattrarray: sets the values of an array of double attributes. + - model: model object (GRBmodel *) + - attr: attribute to set (const char *) + - first: index of first element to set (int, zero-based) + - len: number of elements to set (int) + - values: a pointer to an array of double values (double *) + - GRBsetdblattrelement: sets the value of a single element of a double attribute array. + - model: model object (GRBmodel *) + - attr: attribute to set (const char *) + - element: index of element to set (int, zero-based) + - newvalue: new value for the element (double) + + The following attributes are used: + - GRB_DBL_ATTR_OBJ: is a double array attribute that contains the objective coefficients for all variables in the model. + - GRB_INT_ATTR_MODELSENSE: is an integer attribute that defines the optimization sense of the model. + - 1 for minimization + - -1 for maximization +*/ + +using namespace std; + +namespace lp { + +namespace { +NO_RETURN void handle_gurobi_error(GRBenv *env, int error_code) { + if (error_code == GRB_ERROR_OUT_OF_MEMORY) { + utils::exit_with(utils::ExitCode::SEARCH_OUT_OF_MEMORY); + } + const char *msg = env ? GRBgeterrormsg(env) : nullptr; + if (msg) { + cerr << msg << endl; + cerr << "Gurobi error code: " << error_code << endl; + } else { + cerr << "Gurobi error: code " << error_code << endl; + } + utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR); +} + +template +void GRB_CALL(GRBenv *env, Func fn, Args &&...args) { + int status = fn(std::forward(args)...); + if (status) { + handle_gurobi_error(env, status); + } +} + +int objective_sense_to_gurobi(LPObjectiveSense sense) { + if (sense == LPObjectiveSense::MINIMIZE) { + return 1; + } else { + return -1; + } +} + +void add_constraint(GRBenv *env, GRBmodel *model, const LPConstraint &constraint) { + const vector &indices = constraint.get_variables(); + const vector &coefficients = constraint.get_coefficients(); + int numnz = static_cast(indices.size()); + int *cind = numnz ? const_cast(indices.data()) : nullptr; + double *cval = numnz ? const_cast(coefficients.data()) : nullptr; + + double rhs = constraint.get_right_hand_side(); + lp::Sense sense = constraint.get_sense(); + + //cerr << "Adding constraint with sense " << (sense == lp::Sense::GE ? "GE" : (sense == lp::Sense::LE ? "LE" : (sense == lp::Sense::EQ ? "EQ" : "UNKNOWN"))) << " and right-hand side " << rhs << endl; + if (sense == lp::Sense::GE) { + GRB_CALL(env, GRBaddconstr, model, numnz, cind, cval, GRB_GREATER_EQUAL, rhs, nullptr); + } else if (sense == lp::Sense::LE) { + GRB_CALL(env, GRBaddconstr, model, numnz, cind, cval, GRB_LESS_EQUAL, rhs, nullptr); + } else if (sense == lp::Sense::EQ) { + GRB_CALL(env, GRBaddconstr, model, numnz, cind, cval, GRB_EQUAL, rhs, nullptr); + } else { + cerr << "Error: Unknown constraint sense." << endl; + utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR); + } + +} +} // Have to add this otherwise the compiler complains. + +GurobiSolverInterface::GurobiSolverInterface(): env(nullptr), model(nullptr), num_permanent_constraints(0), num_temporary_constraints(0), model_dirty(false) { + //GRB_CALL(env, GRBloadenv, &env, ""); + GRBloadenv(&env, ""); + GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_OUTPUTFLAG, 0); + GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_LOGTOCONSOLE, 0); + GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_THREADS, 1); + GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_METHOD, GRB_METHOD_DUAL); +} + +GurobiSolverInterface::~GurobiSolverInterface() { + if (model) { + GRBfreemodel(model); + } + if (env) { + GRBfreeenv(env); + } +} + +void GurobiSolverInterface::load_problem(const LinearProgram &lp) { + if (model) { + GRBfreemodel(model); + model = nullptr; + } + const auto &variables = lp.get_variables(); + int num_vars = variables.size(); + vector obj; + vector lb; + vector ub; + vector vtype; + obj.reserve(num_vars); + lb.reserve(num_vars); + ub.reserve(num_vars); + vtype.reserve(num_vars); + for (const LPVariable &var : variables) { + obj.push_back(var.objective_coefficient); + lb.push_back(var.lower_bound); + ub.push_back(var.upper_bound); + vtype.push_back(var.is_integer ? GRB_INTEGER : GRB_CONTINUOUS); + } + double *obj_ptr = num_vars ? obj.data() : nullptr; + double *lb_ptr = num_vars ? lb.data() : nullptr; + double *ub_ptr = num_vars ? ub.data() : nullptr; + char *vtype_ptr = num_vars ? vtype.data() : nullptr; + GRB_CALL(env, GRBnewmodel, env, &model, "downward", num_vars, obj_ptr, lb_ptr, ub_ptr, vtype_ptr, nullptr); + GRB_CALL(env, GRBsetintattr, model, GRB_INT_ATTR_MODELSENSE, objective_sense_to_gurobi(lp.get_sense())); + const auto &constraints = lp.get_constraints(); + num_permanent_constraints = 0; + num_temporary_constraints = 0; + for (const LPConstraint &constraint : constraints) { + add_constraint(env, model, constraint); + num_permanent_constraints++; + } + GRB_CALL(env, GRBupdatemodel, model); + model_dirty = false; + + // Print model + //cerr << "Model loaded with " << num_vars << " variables and " << constraints.size() << " constraints." << endl; + //for (int i = 0; i < num_vars; ++i) { + // cerr << "Variable " << i << ": obj=" << variables[i].objective_coefficient + // << ", lb=" << variables[i].lower_bound + // << ", ub=" << variables[i].upper_bound + // << ", is_integer=" << variables[i].is_integer + // << endl; + //} + //for (int i = 0; i < constraints.size(); ++i) { + // const auto &c = constraints[i]; + // cerr << "Constraint " << i << ": sense=" + // << (c.get_sense() == lp::Sense::GE ? "GE" : (c.get_sense() == lp::Sense::LE ? "LE" : (c.get_sense() == lp::Sense::EQ ? "EQ" : "UNKNOWN"))) + // << ", rhs=" << c.get_right_hand_side() + // << ", coefficients=["; + // for (size_t j = 0; j < c.get_variables().size(); ++j) { + // cerr << "(" << c.get_variables()[j] << ": " << c.get_coefficients()[j] << ")"; + // if (j + 1 < c.get_variables().size()) { + // cerr << ", "; + // } + // } + // cerr << "]" << endl; + //} +} + +void GurobiSolverInterface::add_temporary_constraints(const named_vector::NamedVector &constraints) { + for (const LPConstraint &constraint : constraints) { + add_constraint(env, model, constraint); + } + model_dirty = true; + num_temporary_constraints += constraints.size(); + //cerr << ">>>>> Added " << constraints.size() << " temporary constraints. Total temporary constraints: " << num_temporary_constraints << endl; +} + +void GurobiSolverInterface::clear_temporary_constraints() { + if (!has_temporary_constraints()) { + return; + } + vector indices(num_temporary_constraints); + iota(indices.begin(), indices.end(), num_permanent_constraints); + GRB_CALL(env, GRBdelconstrs, model, num_temporary_constraints, indices.data()); + model_dirty = true; + num_temporary_constraints = 0; + //cerr << ">>>>> Cleared temporary constraints. Total temporary constraints: " << num_temporary_constraints << endl; +} + +double GurobiSolverInterface::get_infinity() const { + return GRB_INFINITY; +} + +void GurobiSolverInterface::set_objective_coefficients(const vector &coefficients) { + // assert(coefficients.size() == get_num_variables()); + int num_coefficients = coefficients.size(); + if (!num_coefficients) { + return; + } // TODO: is there a more elegant way to handle this? + GRB_CALL(env, GRBsetdblattrarray, model, GRB_DBL_ATTR_OBJ, 0, num_coefficients, const_cast(coefficients.data())); + model_dirty = true; + //cerr << ">>>>> New objective coefficients: ["; + //for (int i = 0; i < num_coefficients; ++i) { + // cerr << coefficients[i]; + // if (i + 1 < num_coefficients) { + // cerr << ", "; + // } + //} + //cerr << "]" << endl; +} + +void GurobiSolverInterface::set_objective_coefficient(int index, double coefficient) { + //assert(index >= 0 && index < get_num_variables()); + GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_OBJ, index, coefficient); + model_dirty = true; + //cerr << ">>>>> New objective coefficient for variable " << index << ": " << coefficient << endl; +} + +void GurobiSolverInterface::set_constraint_rhs(int index, double bound) { + assert(index >= 0 && index < get_num_constraints()); + GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_RHS, index, bound); + model_dirty = true; + //cerr << ">>>>> New right-hand side for constraint " << index << ": " << bound << endl; +} + +void GurobiSolverInterface::set_constraint_sense(int index, lp::Sense sense) { + assert(index >= 0 && index < get_num_constraints()); + if (sense == lp::Sense::GE) { + GRB_CALL(env, GRBsetcharattrelement, model, GRB_CHAR_ATTR_SENSE, index, GRB_GREATER_EQUAL); + } else if (sense == lp::Sense::LE) { + GRB_CALL(env, GRBsetcharattrelement, model, GRB_CHAR_ATTR_SENSE, index, GRB_LESS_EQUAL); + } else if (sense == lp::Sense::EQ) { + GRB_CALL(env, GRBsetcharattrelement, model, GRB_CHAR_ATTR_SENSE, index, GRB_EQUAL); + } else { + cerr << "Error: Unknown constraint sense." << endl; + utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR); + } + model_dirty = true; + //cerr << ">>>>> New sense for constraint " << index << ": " + // << (sense == lp::Sense::GE ? "GE" : (sense == lp::Sense::LE ? "LE" : (sense == lp::Sense::EQ ? "EQ" : "UNKNOWN"))) + // << endl; +} + +void GurobiSolverInterface::set_variable_lower_bound(int index, double bound) { + //assert(index >= 0 && index < get_num_variables()); + GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_LB, index, bound); + model_dirty = true; + //cerr << ">>>>> New lower bound for variable " << index << ": " << bound << endl; +} + +void GurobiSolverInterface::set_variable_upper_bound(int index, double bound) { + //assert(index >= 0 && index < get_num_variables()); + GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_UB, index, bound); + model_dirty = true; + //cerr << ">>>>> New upper bound for variable " << index << ": " << bound << endl; +} + +void GurobiSolverInterface::set_mip_gap(double gap) { + GRB_CALL(env, GRBsetdblparam, env, GRB_DBL_PAR_MIPGAP, gap); +} + +void GurobiSolverInterface::solve() { + if (model_dirty) { + GRB_CALL(env, GRBupdatemodel, model); + model_dirty = false; + } + GRB_CALL(env, GRBoptimize, model); +} + +void GurobiSolverInterface::write_lp(const string &filename) const { + GRB_CALL(env, GRBwrite, model, filename.c_str()); +} + +// TODO: implement more detailed failure analysis +void GurobiSolverInterface::print_failure_analysis() const { + int status = 0; + GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_STATUS, &status); +} + +// TODO: check if a solution is available +bool GurobiSolverInterface::is_infeasible() const { + int status = 0; + GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_STATUS, &status); + //cerr << ">>>>> Is infeasible? " << (status == GRB_INFEASIBLE) << endl; + return status == GRB_INFEASIBLE; +} + +// TODO: check if a solution is available +bool GurobiSolverInterface::is_unbounded() const { + int status = 0; + GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_STATUS, &status); + //cerr << ">>>>> Is unbounded? " << (status == GRB_UNBOUNDED) << endl; + return status == GRB_UNBOUNDED; +} + +// TODO: check if a solution is available +bool GurobiSolverInterface::has_optimal_solution() const { + int status = 0; + GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_STATUS, &status); + //cerr << ">>>>> Has optimal solution? " << (status == GRB_OPTIMAL) << endl; + return status == GRB_OPTIMAL; +} + +// TODO: check if a solution is available +double GurobiSolverInterface::get_objective_value() const { + double value = 0.0; + GRB_CALL(env, GRBgetdblattr, model, GRB_DBL_ATTR_OBJVAL, &value); + //cerr << ">>>>> Objective value: " << value << endl; + return value; +} + +// TODO: check if an optimal solution is available +vector GurobiSolverInterface::extract_solution() const { + int num_variables = 0; + GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_NUMVARS, &num_variables); + vector solution(num_variables); + if (num_variables > 0) { + GRB_CALL(env, GRBgetdblattrarray, model, GRB_DBL_ATTR_X, 0, num_variables, solution.data()); + } + //cerr << ">>>>> Extracted solution: ["; + //for (int i = 0; i < num_variables; ++i) { + // cerr << solution[i]; + // if (i + 1 < num_variables) { + // cerr << ", "; + // } + //} + //cerr << "]" << endl; + return solution; +} + +int GurobiSolverInterface::get_num_variables() const { + int num_variables = 0; + GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_NUMVARS, &num_variables); + //cerr << ">>>>> Number of variables: " << num_variables << endl; + return num_variables; +} + +int GurobiSolverInterface::get_num_constraints() const { + //cerr << ">>>>> Number of constraints: " << (num_permanent_constraints + num_temporary_constraints) << endl; + return num_permanent_constraints + num_temporary_constraints; +} + +bool GurobiSolverInterface::has_temporary_constraints() const { + //cerr << ">>>>> Has temporary constraints? " << (num_temporary_constraints > 0) << endl; + return num_temporary_constraints > 0; +} + +void GurobiSolverInterface::print_statistics() const { + double runtime = 0.0; + GRB_CALL(env, GRBgetdblattr, model, GRB_DBL_ATTR_RUNTIME, &runtime); + cout << "Gurobi runtime: " << runtime << "s" << endl; +} +} diff --git a/src/search/lp/gurobi_solver_interface.h b/src/search/lp/gurobi_solver_interface.h new file mode 100644 index 0000000000..6583c6d3dc --- /dev/null +++ b/src/search/lp/gurobi_solver_interface.h @@ -0,0 +1,55 @@ +#ifndef LP_GUROBI_SOLVER_INTERFACE_H +#define LP_GUROBI_SOLVER_INTERFACE_H + +#include "solver_interface.h" + +#include + +namespace lp { +class GurobiSolverInterface : public SolverInterface { + GRBenv *env; + GRBmodel *model; + int num_permanent_constraints; + int num_temporary_constraints; + bool model_dirty; + +public: + GurobiSolverInterface(); + virtual ~GurobiSolverInterface() override; + + virtual void load_problem(const LinearProgram &lp) override; + virtual void add_temporary_constraints( + const named_vector::NamedVector &constraints) override; + virtual void clear_temporary_constraints() override; + virtual double get_infinity() const override; + + virtual void set_objective_coefficients( + const std::vector &coefficients) override; + virtual void set_objective_coefficient(int index, double coefficient) override; + virtual void set_constraint_rhs(int index, double right_hand_side) override; + virtual void set_constraint_sense(int index, lp::Sense sense) override; + virtual void set_variable_lower_bound(int index, double bound) override; + virtual void set_variable_upper_bound(int index, double bound) override; + + virtual void set_mip_gap(double gap) override; + + virtual void solve() override; + virtual void write_lp(const std::string &filename) const override; + virtual void print_failure_analysis() const override; + virtual bool is_infeasible() const override; + virtual bool is_unbounded() const override; + + virtual bool has_optimal_solution() const override; + + virtual double get_objective_value() const override; + + virtual std::vector extract_solution() const override; + + virtual int get_num_variables() const override; + virtual int get_num_constraints() const override; + virtual bool has_temporary_constraints() const override; + virtual void print_statistics() const override; +}; +} + +#endif diff --git a/src/search/lp/lp_solver.cc b/src/search/lp/lp_solver.cc index b85ff40cd6..ac85830468 100644 --- a/src/search/lp/lp_solver.cc +++ b/src/search/lp/lp_solver.cc @@ -6,6 +6,9 @@ #ifdef HAS_SOPLEX #include "soplex_solver_interface.h" #endif +#ifdef HAS_GUROBI +#include "gurobi_solver_interface.h" +#endif #include "../plugins/plugin.h" @@ -146,6 +149,13 @@ LPSolver::LPSolver(LPSolverType solver_type) { pimpl = make_unique(); #else missing_solver = "SoPlex"; +#endif + break; + case LPSolverType::GUROBI: +#ifdef HAS_GUROBI + pimpl = make_unique(); +#else + missing_solver = "Gurobi"; #endif break; default: @@ -256,5 +266,6 @@ void LPSolver::print_statistics() const { static plugins::TypedEnumPlugin _enum_plugin( {{"cplex", "commercial solver by IBM"}, - {"soplex", "open source solver by ZIB"}}); + {"soplex", "open source solver by ZIB"}, + {"gurobi", "commercial solver by Gurobi"}}); } diff --git a/src/search/lp/lp_solver.h b/src/search/lp/lp_solver.h index 27b6cac132..78ff7c01b0 100644 --- a/src/search/lp/lp_solver.h +++ b/src/search/lp/lp_solver.h @@ -17,7 +17,8 @@ class Options; namespace lp { enum class LPSolverType { CPLEX, - SOPLEX + SOPLEX, + GUROBI }; enum class LPObjectiveSense { From e4c2d5df43e27fe7d901da593fe4bdbd9eb5ed82 Mon Sep 17 00:00:00 2001 From: Travis Rivera Petit Date: Tue, 28 Jul 2026 17:34:07 +0200 Subject: [PATCH 02/35] Update Gurobi backend --- src/search/lp/gurobi_solver_interface.cc | 615 +++++++++++++++-------- 1 file changed, 407 insertions(+), 208 deletions(-) diff --git a/src/search/lp/gurobi_solver_interface.cc b/src/search/lp/gurobi_solver_interface.cc index a76f5bb151..ceed888b10 100644 --- a/src/search/lp/gurobi_solver_interface.cc +++ b/src/search/lp/gurobi_solver_interface.cc @@ -4,110 +4,149 @@ #include "../utils/system.h" -#include +#include #include #include - -/* - Core Gurobi functionality is provided in C API (gurobi_c.h). - We implement the interface using the C API to avoid linking issues. - - The implementation is based on examples from https://www.gurobi.com/documentation/9.5/refman/c_api_overview.html - - We use the following functions from the Gurobi C API: - - GRBsetdblattrarray: sets the values of an array of double attributes. - - model: model object (GRBmodel *) - - attr: attribute to set (const char *) - - first: index of first element to set (int, zero-based) - - len: number of elements to set (int) - - values: a pointer to an array of double values (double *) - - GRBsetdblattrelement: sets the value of a single element of a double attribute array. - - model: model object (GRBmodel *) - - attr: attribute to set (const char *) - - element: index of element to set (int, zero-based) - - newvalue: new value for the element (double) - - The following attributes are used: - - GRB_DBL_ATTR_OBJ: is a double array attribute that contains the objective coefficients for all variables in the model. - - GRB_INT_ATTR_MODELSENSE: is an integer attribute that defines the optimization sense of the model. - - 1 for minimization - - -1 for maximization -*/ +#include using namespace std; namespace lp { namespace { + NO_RETURN void handle_gurobi_error(GRBenv *env, int error_code) { if (error_code == GRB_ERROR_OUT_OF_MEMORY) { utils::exit_with(utils::ExitCode::SEARCH_OUT_OF_MEMORY); } - const char *msg = env ? GRBgeterrormsg(env) : nullptr; - if (msg) { - cerr << msg << endl; - cerr << "Gurobi error code: " << error_code << endl; + + const char *message = env ? GRBgeterrormsg(env) : nullptr; + if (message) { + cerr << "Gurobi error: " << message << endl; } else { - cerr << "Gurobi error: code " << error_code << endl; + cerr << "Gurobi error." << endl; } + cerr << "Gurobi error code: " << error_code << endl; + utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR); } -template -void GRB_CALL(GRBenv *env, Func fn, Args &&...args) { - int status = fn(std::forward(args)...); +template +void GRB_CALL(GRBenv *env, Function function, Args &&...args) { + int status = function(forward(args)...); if (status) { handle_gurobi_error(env, status); } } int objective_sense_to_gurobi(LPObjectiveSense sense) { - if (sense == LPObjectiveSense::MINIMIZE) { - return 1; - } else { - return -1; + switch (sense) { + case LPObjectiveSense::MINIMIZE: + return GRB_MINIMIZE; + case LPObjectiveSense::MAXIMIZE: + return GRB_MAXIMIZE; } + + ABORT("Unknown LP objective sense."); } -void add_constraint(GRBenv *env, GRBmodel *model, const LPConstraint &constraint) { +char constraint_sense_to_gurobi(Sense sense) { + switch (sense) { + case Sense::GE: + return GRB_GREATER_EQUAL; + case Sense::LE: + return GRB_LESS_EQUAL; + case Sense::EQ: + return GRB_EQUAL; + } + + ABORT("Unknown LP constraint sense."); +} + +void add_constraint( + GRBenv *env, GRBmodel *model, const LPConstraint &constraint) { const vector &indices = constraint.get_variables(); const vector &coefficients = constraint.get_coefficients(); - int numnz = static_cast(indices.size()); - int *cind = numnz ? const_cast(indices.data()) : nullptr; - double *cval = numnz ? const_cast(coefficients.data()) : nullptr; - - double rhs = constraint.get_right_hand_side(); - lp::Sense sense = constraint.get_sense(); - - //cerr << "Adding constraint with sense " << (sense == lp::Sense::GE ? "GE" : (sense == lp::Sense::LE ? "LE" : (sense == lp::Sense::EQ ? "EQ" : "UNKNOWN"))) << " and right-hand side " << rhs << endl; - if (sense == lp::Sense::GE) { - GRB_CALL(env, GRBaddconstr, model, numnz, cind, cval, GRB_GREATER_EQUAL, rhs, nullptr); - } else if (sense == lp::Sense::LE) { - GRB_CALL(env, GRBaddconstr, model, numnz, cind, cval, GRB_LESS_EQUAL, rhs, nullptr); - } else if (sense == lp::Sense::EQ) { - GRB_CALL(env, GRBaddconstr, model, numnz, cind, cval, GRB_EQUAL, rhs, nullptr); - } else { - cerr << "Error: Unknown constraint sense." << endl; - utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR); - } + assert(indices.size() == coefficients.size()); + + int num_nonzero = static_cast(indices.size()); + int *index_data = + num_nonzero > 0 ? const_cast(indices.data()) : nullptr; + double *coefficient_data = + num_nonzero > 0 + ? const_cast(coefficients.data()) + : nullptr; + + GRB_CALL( + env, + GRBaddconstr, + model, + num_nonzero, + index_data, + coefficient_data, + constraint_sense_to_gurobi(constraint.get_sense()), + constraint.get_right_hand_side(), + nullptr); } -} // Have to add this otherwise the compiler complains. - -GurobiSolverInterface::GurobiSolverInterface(): env(nullptr), model(nullptr), num_permanent_constraints(0), num_temporary_constraints(0), model_dirty(false) { - //GRB_CALL(env, GRBloadenv, &env, ""); - GRBloadenv(&env, ""); - GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_OUTPUTFLAG, 0); - GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_LOGTOCONSOLE, 0); - GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_THREADS, 1); - GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_METHOD, GRB_METHOD_DUAL); + +int get_model_status(GRBenv *env, GRBmodel *model) { + assert(model); + + int status; + GRB_CALL( + env, + GRBgetintattr, + model, + GRB_INT_ATTR_STATUS, + &status); + return status; +} + +} // namespace + +GurobiSolverInterface::GurobiSolverInterface() + : env(nullptr), + model(nullptr), + num_permanent_constraints(0), + num_temporary_constraints(0), + model_dirty(false) { + int status = GRBloadenv(&env, ""); + if (status) { + handle_gurobi_error(env, status); + } + + GRB_CALL( + env, + GRBsetintparam, + env, + GRB_INT_PAR_OUTPUTFLAG, + 0); + GRB_CALL( + env, + GRBsetintparam, + env, + GRB_INT_PAR_LOGTOCONSOLE, + 0); + GRB_CALL( + env, + GRBsetintparam, + env, + GRB_INT_PAR_THREADS, + 1); + GRB_CALL( + env, + GRBsetintparam, + env, + GRB_INT_PAR_METHOD, + GRB_METHOD_DUAL); } GurobiSolverInterface::~GurobiSolverInterface() { if (model) { GRBfreemodel(model); } - if (env) { + if (env) { GRBfreeenv(env); } } @@ -117,245 +156,405 @@ void GurobiSolverInterface::load_problem(const LinearProgram &lp) { GRBfreemodel(model); model = nullptr; } + const auto &variables = lp.get_variables(); - int num_vars = variables.size(); - vector obj; - vector lb; - vector ub; - vector vtype; - obj.reserve(num_vars); - lb.reserve(num_vars); - ub.reserve(num_vars); - vtype.reserve(num_vars); - for (const LPVariable &var : variables) { - obj.push_back(var.objective_coefficient); - lb.push_back(var.lower_bound); - ub.push_back(var.upper_bound); - vtype.push_back(var.is_integer ? GRB_INTEGER : GRB_CONTINUOUS); + int num_variables = static_cast(variables.size()); + + vector objective_coefficients; + vector lower_bounds; + vector upper_bounds; + vector variable_types; + + objective_coefficients.reserve(num_variables); + lower_bounds.reserve(num_variables); + upper_bounds.reserve(num_variables); + variable_types.reserve(num_variables); + + for (const LPVariable &variable : variables) { + objective_coefficients.push_back( + variable.objective_coefficient); + lower_bounds.push_back(variable.lower_bound); + upper_bounds.push_back(variable.upper_bound); + variable_types.push_back( + variable.is_integer ? GRB_INTEGER : GRB_CONTINUOUS); } - double *obj_ptr = num_vars ? obj.data() : nullptr; - double *lb_ptr = num_vars ? lb.data() : nullptr; - double *ub_ptr = num_vars ? ub.data() : nullptr; - char *vtype_ptr = num_vars ? vtype.data() : nullptr; - GRB_CALL(env, GRBnewmodel, env, &model, "downward", num_vars, obj_ptr, lb_ptr, ub_ptr, vtype_ptr, nullptr); - GRB_CALL(env, GRBsetintattr, model, GRB_INT_ATTR_MODELSENSE, objective_sense_to_gurobi(lp.get_sense())); + + double *objective_data = + num_variables > 0 ? objective_coefficients.data() : nullptr; + double *lower_bound_data = + num_variables > 0 ? lower_bounds.data() : nullptr; + double *upper_bound_data = + num_variables > 0 ? upper_bounds.data() : nullptr; + char *variable_type_data = + num_variables > 0 ? variable_types.data() : nullptr; + + GRB_CALL( + env, + GRBnewmodel, + env, + &model, + "downward", + num_variables, + objective_data, + lower_bound_data, + upper_bound_data, + variable_type_data, + nullptr); + + GRB_CALL( + env, + GRBsetintattr, + model, + GRB_INT_ATTR_MODELSENSE, + objective_sense_to_gurobi(lp.get_sense())); + const auto &constraints = lp.get_constraints(); - num_permanent_constraints = 0; + + num_permanent_constraints = + static_cast(constraints.size()); num_temporary_constraints = 0; + for (const LPConstraint &constraint : constraints) { add_constraint(env, model, constraint); - num_permanent_constraints++; } + GRB_CALL(env, GRBupdatemodel, model); model_dirty = false; - - // Print model - //cerr << "Model loaded with " << num_vars << " variables and " << constraints.size() << " constraints." << endl; - //for (int i = 0; i < num_vars; ++i) { - // cerr << "Variable " << i << ": obj=" << variables[i].objective_coefficient - // << ", lb=" << variables[i].lower_bound - // << ", ub=" << variables[i].upper_bound - // << ", is_integer=" << variables[i].is_integer - // << endl; - //} - //for (int i = 0; i < constraints.size(); ++i) { - // const auto &c = constraints[i]; - // cerr << "Constraint " << i << ": sense=" - // << (c.get_sense() == lp::Sense::GE ? "GE" : (c.get_sense() == lp::Sense::LE ? "LE" : (c.get_sense() == lp::Sense::EQ ? "EQ" : "UNKNOWN"))) - // << ", rhs=" << c.get_right_hand_side() - // << ", coefficients=["; - // for (size_t j = 0; j < c.get_variables().size(); ++j) { - // cerr << "(" << c.get_variables()[j] << ": " << c.get_coefficients()[j] << ")"; - // if (j + 1 < c.get_variables().size()) { - // cerr << ", "; - // } - // } - // cerr << "]" << endl; - //} } -void GurobiSolverInterface::add_temporary_constraints(const named_vector::NamedVector &constraints) { +void GurobiSolverInterface::add_temporary_constraints( + const named_vector::NamedVector &constraints) { + assert(model); + for (const LPConstraint &constraint : constraints) { add_constraint(env, model, constraint); } + + num_temporary_constraints += + static_cast(constraints.size()); model_dirty = true; - num_temporary_constraints += constraints.size(); - //cerr << ">>>>> Added " << constraints.size() << " temporary constraints. Total temporary constraints: " << num_temporary_constraints << endl; } void GurobiSolverInterface::clear_temporary_constraints() { + assert(model); + if (!has_temporary_constraints()) { return; } + + if (model_dirty) { + GRB_CALL(env, GRBupdatemodel, model); + model_dirty = false; + } + vector indices(num_temporary_constraints); - iota(indices.begin(), indices.end(), num_permanent_constraints); - GRB_CALL(env, GRBdelconstrs, model, num_temporary_constraints, indices.data()); - model_dirty = true; + iota( + indices.begin(), + indices.end(), + num_permanent_constraints); + + GRB_CALL( + env, + GRBdelconstrs, + model, + num_temporary_constraints, + indices.data()); + + GRB_CALL(env, GRBupdatemodel, model); + num_temporary_constraints = 0; - //cerr << ">>>>> Cleared temporary constraints. Total temporary constraints: " << num_temporary_constraints << endl; + model_dirty = false; } double GurobiSolverInterface::get_infinity() const { return GRB_INFINITY; } -void GurobiSolverInterface::set_objective_coefficients(const vector &coefficients) { - // assert(coefficients.size() == get_num_variables()); - int num_coefficients = coefficients.size(); - if (!num_coefficients) { +void GurobiSolverInterface::set_objective_coefficients( + const vector &coefficients) { + assert(model); + assert( + coefficients.size() == + static_cast(get_num_variables())); + + if (coefficients.empty()) { return; - } // TODO: is there a more elegant way to handle this? - GRB_CALL(env, GRBsetdblattrarray, model, GRB_DBL_ATTR_OBJ, 0, num_coefficients, const_cast(coefficients.data())); + } + + GRB_CALL( + env, + GRBsetdblattrarray, + model, + GRB_DBL_ATTR_OBJ, + 0, + static_cast(coefficients.size()), + const_cast(coefficients.data())); + model_dirty = true; - //cerr << ">>>>> New objective coefficients: ["; - //for (int i = 0; i < num_coefficients; ++i) { - // cerr << coefficients[i]; - // if (i + 1 < num_coefficients) { - // cerr << ", "; - // } - //} - //cerr << "]" << endl; } -void GurobiSolverInterface::set_objective_coefficient(int index, double coefficient) { - //assert(index >= 0 && index < get_num_variables()); - GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_OBJ, index, coefficient); +void GurobiSolverInterface::set_objective_coefficient( + int index, double coefficient) { + assert(model); + assert(index >= 0 && index < get_num_variables()); + + GRB_CALL( + env, + GRBsetdblattrelement, + model, + GRB_DBL_ATTR_OBJ, + index, + coefficient); + model_dirty = true; - //cerr << ">>>>> New objective coefficient for variable " << index << ": " << coefficient << endl; } -void GurobiSolverInterface::set_constraint_rhs(int index, double bound) { +void GurobiSolverInterface::set_constraint_rhs( + int index, double right_hand_side) { + assert(model); assert(index >= 0 && index < get_num_constraints()); - GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_RHS, index, bound); + + if (model_dirty) { + GRB_CALL(env, GRBupdatemodel, model); + model_dirty = false; + } + + GRB_CALL( + env, + GRBsetdblattrelement, + model, + GRB_DBL_ATTR_RHS, + index, + right_hand_side); + model_dirty = true; - //cerr << ">>>>> New right-hand side for constraint " << index << ": " << bound << endl; } -void GurobiSolverInterface::set_constraint_sense(int index, lp::Sense sense) { +void GurobiSolverInterface::set_constraint_sense( + int index, Sense sense) { + assert(model); assert(index >= 0 && index < get_num_constraints()); - if (sense == lp::Sense::GE) { - GRB_CALL(env, GRBsetcharattrelement, model, GRB_CHAR_ATTR_SENSE, index, GRB_GREATER_EQUAL); - } else if (sense == lp::Sense::LE) { - GRB_CALL(env, GRBsetcharattrelement, model, GRB_CHAR_ATTR_SENSE, index, GRB_LESS_EQUAL); - } else if (sense == lp::Sense::EQ) { - GRB_CALL(env, GRBsetcharattrelement, model, GRB_CHAR_ATTR_SENSE, index, GRB_EQUAL); - } else { - cerr << "Error: Unknown constraint sense." << endl; - utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR); + + if (model_dirty) { + GRB_CALL(env, GRBupdatemodel, model); + model_dirty = false; } + + GRB_CALL( + env, + GRBsetcharattrelement, + model, + GRB_CHAR_ATTR_SENSE, + index, + constraint_sense_to_gurobi(sense)); + model_dirty = true; - //cerr << ">>>>> New sense for constraint " << index << ": " - // << (sense == lp::Sense::GE ? "GE" : (sense == lp::Sense::LE ? "LE" : (sense == lp::Sense::EQ ? "EQ" : "UNKNOWN"))) - // << endl; } -void GurobiSolverInterface::set_variable_lower_bound(int index, double bound) { - //assert(index >= 0 && index < get_num_variables()); - GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_LB, index, bound); +void GurobiSolverInterface::set_variable_lower_bound( + int index, double bound) { + assert(model); + assert(index >= 0 && index < get_num_variables()); + + GRB_CALL( + env, + GRBsetdblattrelement, + model, + GRB_DBL_ATTR_LB, + index, + bound); + model_dirty = true; - //cerr << ">>>>> New lower bound for variable " << index << ": " << bound << endl; } -void GurobiSolverInterface::set_variable_upper_bound(int index, double bound) { - //assert(index >= 0 && index < get_num_variables()); - GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_UB, index, bound); +void GurobiSolverInterface::set_variable_upper_bound( + int index, double bound) { + assert(model); + assert(index >= 0 && index < get_num_variables()); + + GRB_CALL( + env, + GRBsetdblattrelement, + model, + GRB_DBL_ATTR_UB, + index, + bound); + model_dirty = true; - //cerr << ">>>>> New upper bound for variable " << index << ": " << bound << endl; } void GurobiSolverInterface::set_mip_gap(double gap) { - GRB_CALL(env, GRBsetdblparam, env, GRB_DBL_PAR_MIPGAP, gap); + assert(gap >= 0.0); + + GRB_CALL( + env, + GRBsetdblparam, + env, + GRB_DBL_PAR_MIPGAP, + gap); + + if (model) { + GRBenv *model_env = GRBgetenv(model); + GRB_CALL( + model_env, + GRBsetdblparam, + model_env, + GRB_DBL_PAR_MIPGAP, + gap); + } } void GurobiSolverInterface::solve() { + assert(model); + if (model_dirty) { GRB_CALL(env, GRBupdatemodel, model); model_dirty = false; } + GRB_CALL(env, GRBoptimize, model); } -void GurobiSolverInterface::write_lp(const string &filename) const { +void GurobiSolverInterface::write_lp( + const string &filename) const { + assert(model); + + if (model_dirty) { + GRB_CALL(env, GRBupdatemodel, model); + } + GRB_CALL(env, GRBwrite, model, filename.c_str()); } -// TODO: implement more detailed failure analysis void GurobiSolverInterface::print_failure_analysis() const { - int status = 0; - GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_STATUS, &status); + assert(model); + + int status = get_model_status(env, model); + + int solution_count; + GRB_CALL( + env, + GRBgetintattr, + model, + GRB_INT_ATTR_SOLCOUNT, + &solution_count); + + cerr << "Gurobi optimization failed with status " + << status << " and " << solution_count + << " stored solution(s)." << endl; + + switch (status) { + case GRB_INFEASIBLE: + cerr << "The model is infeasible." << endl; + break; + case GRB_UNBOUNDED: + cerr << "The model is unbounded." << endl; + break; + case GRB_INF_OR_UNBD: + cerr << "The model is infeasible or unbounded." << endl; + break; + case GRB_TIME_LIMIT: + cerr << "The time limit was reached." << endl; + break; + case GRB_ITERATION_LIMIT: + cerr << "The iteration limit was reached." << endl; + break; + case GRB_NUMERIC: + cerr << "Gurobi encountered numerical difficulties." << endl; + break; + case GRB_SUBOPTIMAL: + cerr << "Gurobi found a suboptimal solution." << endl; + break; + case GRB_INTERRUPTED: + cerr << "The optimization was interrupted." << endl; + break; + default: + break; + } } -// TODO: check if a solution is available bool GurobiSolverInterface::is_infeasible() const { - int status = 0; - GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_STATUS, &status); - //cerr << ">>>>> Is infeasible? " << (status == GRB_INFEASIBLE) << endl; - return status == GRB_INFEASIBLE; + return get_model_status(env, model) == GRB_INFEASIBLE; } -// TODO: check if a solution is available bool GurobiSolverInterface::is_unbounded() const { - int status = 0; - GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_STATUS, &status); - //cerr << ">>>>> Is unbounded? " << (status == GRB_UNBOUNDED) << endl; - return status == GRB_UNBOUNDED; + return get_model_status(env, model) == GRB_UNBOUNDED; } -// TODO: check if a solution is available bool GurobiSolverInterface::has_optimal_solution() const { - int status = 0; - GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_STATUS, &status); - //cerr << ">>>>> Has optimal solution? " << (status == GRB_OPTIMAL) << endl; - return status == GRB_OPTIMAL; + return get_model_status(env, model) == GRB_OPTIMAL; } -// TODO: check if a solution is available double GurobiSolverInterface::get_objective_value() const { - double value = 0.0; - GRB_CALL(env, GRBgetdblattr, model, GRB_DBL_ATTR_OBJVAL, &value); - //cerr << ">>>>> Objective value: " << value << endl; - return value; + assert(model); + assert(has_optimal_solution()); + + double objective_value; + GRB_CALL( + env, + GRBgetdblattr, + model, + GRB_DBL_ATTR_OBJVAL, + &objective_value); + + return objective_value; } -// TODO: check if an optimal solution is available vector GurobiSolverInterface::extract_solution() const { - int num_variables = 0; - GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_NUMVARS, &num_variables); + assert(model); + assert(has_optimal_solution()); + + int num_variables = get_num_variables(); vector solution(num_variables); + if (num_variables > 0) { - GRB_CALL(env, GRBgetdblattrarray, model, GRB_DBL_ATTR_X, 0, num_variables, solution.data()); + GRB_CALL( + env, + GRBgetdblattrarray, + model, + GRB_DBL_ATTR_X, + 0, + num_variables, + solution.data()); } - //cerr << ">>>>> Extracted solution: ["; - //for (int i = 0; i < num_variables; ++i) { - // cerr << solution[i]; - // if (i + 1 < num_variables) { - // cerr << ", "; - // } - //} - //cerr << "]" << endl; + return solution; } int GurobiSolverInterface::get_num_variables() const { - int num_variables = 0; - GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_NUMVARS, &num_variables); - //cerr << ">>>>> Number of variables: " << num_variables << endl; + assert(model); + + int num_variables; + GRB_CALL( + env, + GRBgetintattr, + model, + GRB_INT_ATTR_NUMVARS, + &num_variables); + return num_variables; } int GurobiSolverInterface::get_num_constraints() const { - //cerr << ">>>>> Number of constraints: " << (num_permanent_constraints + num_temporary_constraints) << endl; return num_permanent_constraints + num_temporary_constraints; } bool GurobiSolverInterface::has_temporary_constraints() const { - //cerr << ">>>>> Has temporary constraints? " << (num_temporary_constraints > 0) << endl; return num_temporary_constraints > 0; } void GurobiSolverInterface::print_statistics() const { - double runtime = 0.0; - GRB_CALL(env, GRBgetdblattr, model, GRB_DBL_ATTR_RUNTIME, &runtime); + assert(model); + + double runtime; + GRB_CALL( + env, + GRBgetdblattr, + model, + GRB_DBL_ATTR_RUNTIME, + &runtime); + cout << "Gurobi runtime: " << runtime << "s" << endl; } -} + +} // namespace lp From a948f775447b11f6283a61e63c15a7555f7c35e1 Mon Sep 17 00:00:00 2001 From: Travis Rivera Petit Date: Tue, 28 Jul 2026 17:35:58 +0200 Subject: [PATCH 03/35] Style --- src/search/lp/gurobi_solver_interface.cc | 197 +++++------------------ src/search/lp/gurobi_solver_interface.h | 3 +- 2 files changed, 38 insertions(+), 162 deletions(-) diff --git a/src/search/lp/gurobi_solver_interface.cc b/src/search/lp/gurobi_solver_interface.cc index ceed888b10..94f5f1e413 100644 --- a/src/search/lp/gurobi_solver_interface.cc +++ b/src/search/lp/gurobi_solver_interface.cc @@ -74,32 +74,19 @@ void add_constraint( int *index_data = num_nonzero > 0 ? const_cast(indices.data()) : nullptr; double *coefficient_data = - num_nonzero > 0 - ? const_cast(coefficients.data()) - : nullptr; + num_nonzero > 0 ? const_cast(coefficients.data()) : nullptr; GRB_CALL( - env, - GRBaddconstr, - model, - num_nonzero, - index_data, - coefficient_data, + env, GRBaddconstr, model, num_nonzero, index_data, coefficient_data, constraint_sense_to_gurobi(constraint.get_sense()), - constraint.get_right_hand_side(), - nullptr); + constraint.get_right_hand_side(), nullptr); } int get_model_status(GRBenv *env, GRBmodel *model) { assert(model); int status; - GRB_CALL( - env, - GRBgetintattr, - model, - GRB_INT_ATTR_STATUS, - &status); + GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_STATUS, &status); return status; } @@ -116,30 +103,10 @@ GurobiSolverInterface::GurobiSolverInterface() handle_gurobi_error(env, status); } - GRB_CALL( - env, - GRBsetintparam, - env, - GRB_INT_PAR_OUTPUTFLAG, - 0); - GRB_CALL( - env, - GRBsetintparam, - env, - GRB_INT_PAR_LOGTOCONSOLE, - 0); - GRB_CALL( - env, - GRBsetintparam, - env, - GRB_INT_PAR_THREADS, - 1); - GRB_CALL( - env, - GRBsetintparam, - env, - GRB_INT_PAR_METHOD, - GRB_METHOD_DUAL); + GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_OUTPUTFLAG, 0); + GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_LOGTOCONSOLE, 0); + GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_THREADS, 1); + GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_METHOD, GRB_METHOD_DUAL); } GurobiSolverInterface::~GurobiSolverInterface() { @@ -171,8 +138,7 @@ void GurobiSolverInterface::load_problem(const LinearProgram &lp) { variable_types.reserve(num_variables); for (const LPVariable &variable : variables) { - objective_coefficients.push_back( - variable.objective_coefficient); + objective_coefficients.push_back(variable.objective_coefficient); lower_bounds.push_back(variable.lower_bound); upper_bounds.push_back(variable.upper_bound); variable_types.push_back( @@ -189,29 +155,17 @@ void GurobiSolverInterface::load_problem(const LinearProgram &lp) { num_variables > 0 ? variable_types.data() : nullptr; GRB_CALL( - env, - GRBnewmodel, - env, - &model, - "downward", - num_variables, - objective_data, - lower_bound_data, - upper_bound_data, - variable_type_data, + env, GRBnewmodel, env, &model, "downward", num_variables, + objective_data, lower_bound_data, upper_bound_data, variable_type_data, nullptr); GRB_CALL( - env, - GRBsetintattr, - model, - GRB_INT_ATTR_MODELSENSE, + env, GRBsetintattr, model, GRB_INT_ATTR_MODELSENSE, objective_sense_to_gurobi(lp.get_sense())); const auto &constraints = lp.get_constraints(); - num_permanent_constraints = - static_cast(constraints.size()); + num_permanent_constraints = static_cast(constraints.size()); num_temporary_constraints = 0; for (const LPConstraint &constraint : constraints) { @@ -230,8 +184,7 @@ void GurobiSolverInterface::add_temporary_constraints( add_constraint(env, model, constraint); } - num_temporary_constraints += - static_cast(constraints.size()); + num_temporary_constraints += static_cast(constraints.size()); model_dirty = true; } @@ -248,17 +201,10 @@ void GurobiSolverInterface::clear_temporary_constraints() { } vector indices(num_temporary_constraints); - iota( - indices.begin(), - indices.end(), - num_permanent_constraints); + iota(indices.begin(), indices.end(), num_permanent_constraints); GRB_CALL( - env, - GRBdelconstrs, - model, - num_temporary_constraints, - indices.data()); + env, GRBdelconstrs, model, num_temporary_constraints, indices.data()); GRB_CALL(env, GRBupdatemodel, model); @@ -273,20 +219,14 @@ double GurobiSolverInterface::get_infinity() const { void GurobiSolverInterface::set_objective_coefficients( const vector &coefficients) { assert(model); - assert( - coefficients.size() == - static_cast(get_num_variables())); + assert(coefficients.size() == static_cast(get_num_variables())); if (coefficients.empty()) { return; } GRB_CALL( - env, - GRBsetdblattrarray, - model, - GRB_DBL_ATTR_OBJ, - 0, + env, GRBsetdblattrarray, model, GRB_DBL_ATTR_OBJ, 0, static_cast(coefficients.size()), const_cast(coefficients.data())); @@ -299,12 +239,7 @@ void GurobiSolverInterface::set_objective_coefficient( assert(index >= 0 && index < get_num_variables()); GRB_CALL( - env, - GRBsetdblattrelement, - model, - GRB_DBL_ATTR_OBJ, - index, - coefficient); + env, GRBsetdblattrelement, model, GRB_DBL_ATTR_OBJ, index, coefficient); model_dirty = true; } @@ -320,18 +255,13 @@ void GurobiSolverInterface::set_constraint_rhs( } GRB_CALL( - env, - GRBsetdblattrelement, - model, - GRB_DBL_ATTR_RHS, - index, + env, GRBsetdblattrelement, model, GRB_DBL_ATTR_RHS, index, right_hand_side); model_dirty = true; } -void GurobiSolverInterface::set_constraint_sense( - int index, Sense sense) { +void GurobiSolverInterface::set_constraint_sense(int index, Sense sense) { assert(model); assert(index >= 0 && index < get_num_constraints()); @@ -341,44 +271,26 @@ void GurobiSolverInterface::set_constraint_sense( } GRB_CALL( - env, - GRBsetcharattrelement, - model, - GRB_CHAR_ATTR_SENSE, - index, + env, GRBsetcharattrelement, model, GRB_CHAR_ATTR_SENSE, index, constraint_sense_to_gurobi(sense)); model_dirty = true; } -void GurobiSolverInterface::set_variable_lower_bound( - int index, double bound) { +void GurobiSolverInterface::set_variable_lower_bound(int index, double bound) { assert(model); assert(index >= 0 && index < get_num_variables()); - GRB_CALL( - env, - GRBsetdblattrelement, - model, - GRB_DBL_ATTR_LB, - index, - bound); + GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_LB, index, bound); model_dirty = true; } -void GurobiSolverInterface::set_variable_upper_bound( - int index, double bound) { +void GurobiSolverInterface::set_variable_upper_bound(int index, double bound) { assert(model); assert(index >= 0 && index < get_num_variables()); - GRB_CALL( - env, - GRBsetdblattrelement, - model, - GRB_DBL_ATTR_UB, - index, - bound); + GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_UB, index, bound); model_dirty = true; } @@ -386,21 +298,11 @@ void GurobiSolverInterface::set_variable_upper_bound( void GurobiSolverInterface::set_mip_gap(double gap) { assert(gap >= 0.0); - GRB_CALL( - env, - GRBsetdblparam, - env, - GRB_DBL_PAR_MIPGAP, - gap); + GRB_CALL(env, GRBsetdblparam, env, GRB_DBL_PAR_MIPGAP, gap); if (model) { GRBenv *model_env = GRBgetenv(model); - GRB_CALL( - model_env, - GRBsetdblparam, - model_env, - GRB_DBL_PAR_MIPGAP, - gap); + GRB_CALL(model_env, GRBsetdblparam, model_env, GRB_DBL_PAR_MIPGAP, gap); } } @@ -415,8 +317,7 @@ void GurobiSolverInterface::solve() { GRB_CALL(env, GRBoptimize, model); } -void GurobiSolverInterface::write_lp( - const string &filename) const { +void GurobiSolverInterface::write_lp(const string &filename) const { assert(model); if (model_dirty) { @@ -432,16 +333,10 @@ void GurobiSolverInterface::print_failure_analysis() const { int status = get_model_status(env, model); int solution_count; - GRB_CALL( - env, - GRBgetintattr, - model, - GRB_INT_ATTR_SOLCOUNT, - &solution_count); + GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_SOLCOUNT, &solution_count); - cerr << "Gurobi optimization failed with status " - << status << " and " << solution_count - << " stored solution(s)." << endl; + cerr << "Gurobi optimization failed with status " << status << " and " + << solution_count << " stored solution(s)." << endl; switch (status) { case GRB_INFEASIBLE: @@ -490,12 +385,7 @@ double GurobiSolverInterface::get_objective_value() const { assert(has_optimal_solution()); double objective_value; - GRB_CALL( - env, - GRBgetdblattr, - model, - GRB_DBL_ATTR_OBJVAL, - &objective_value); + GRB_CALL(env, GRBgetdblattr, model, GRB_DBL_ATTR_OBJVAL, &objective_value); return objective_value; } @@ -509,12 +399,7 @@ vector GurobiSolverInterface::extract_solution() const { if (num_variables > 0) { GRB_CALL( - env, - GRBgetdblattrarray, - model, - GRB_DBL_ATTR_X, - 0, - num_variables, + env, GRBgetdblattrarray, model, GRB_DBL_ATTR_X, 0, num_variables, solution.data()); } @@ -525,12 +410,7 @@ int GurobiSolverInterface::get_num_variables() const { assert(model); int num_variables; - GRB_CALL( - env, - GRBgetintattr, - model, - GRB_INT_ATTR_NUMVARS, - &num_variables); + GRB_CALL(env, GRBgetintattr, model, GRB_INT_ATTR_NUMVARS, &num_variables); return num_variables; } @@ -547,12 +427,7 @@ void GurobiSolverInterface::print_statistics() const { assert(model); double runtime; - GRB_CALL( - env, - GRBgetdblattr, - model, - GRB_DBL_ATTR_RUNTIME, - &runtime); + GRB_CALL(env, GRBgetdblattr, model, GRB_DBL_ATTR_RUNTIME, &runtime); cout << "Gurobi runtime: " << runtime << "s" << endl; } diff --git a/src/search/lp/gurobi_solver_interface.h b/src/search/lp/gurobi_solver_interface.h index 6583c6d3dc..be866e0b15 100644 --- a/src/search/lp/gurobi_solver_interface.h +++ b/src/search/lp/gurobi_solver_interface.h @@ -25,7 +25,8 @@ class GurobiSolverInterface : public SolverInterface { virtual void set_objective_coefficients( const std::vector &coefficients) override; - virtual void set_objective_coefficient(int index, double coefficient) override; + virtual void set_objective_coefficient( + int index, double coefficient) override; virtual void set_constraint_rhs(int index, double right_hand_side) override; virtual void set_constraint_sense(int index, lp::Sense sense) override; virtual void set_variable_lower_bound(int index, double bound) override; From f0769887adefbe38cd54e6039d8ee2369e96fc4f Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Wed, 29 Jul 2026 17:07:07 +0200 Subject: [PATCH 04/35] set gurobi's lp algorithm to default --- src/search/lp/gurobi_solver_interface.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/search/lp/gurobi_solver_interface.cc b/src/search/lp/gurobi_solver_interface.cc index 94f5f1e413..e9e610cf2d 100644 --- a/src/search/lp/gurobi_solver_interface.cc +++ b/src/search/lp/gurobi_solver_interface.cc @@ -106,7 +106,6 @@ GurobiSolverInterface::GurobiSolverInterface() GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_OUTPUTFLAG, 0); GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_LOGTOCONSOLE, 0); GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_THREADS, 1); - GRB_CALL(env, GRBsetintparam, env, GRB_INT_PAR_METHOD, GRB_METHOD_DUAL); } GurobiSolverInterface::~GurobiSolverInterface() { From 485d07dc0eb602cb52a9341c0e50ae1d27f22069 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Thu, 30 Jul 2026 09:23:32 +0200 Subject: [PATCH 05/35] remove unnecessary sense update on rr constraints; remove unnecessary gurobi updates --- src/search/lp/gurobi_solver_interface.cc | 48 +++++++----------------- src/search/lp/gurobi_solver_interface.h | 3 +- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/src/search/lp/gurobi_solver_interface.cc b/src/search/lp/gurobi_solver_interface.cc index e9e610cf2d..7a2b4fe2af 100644 --- a/src/search/lp/gurobi_solver_interface.cc +++ b/src/search/lp/gurobi_solver_interface.cc @@ -97,7 +97,7 @@ GurobiSolverInterface::GurobiSolverInterface() model(nullptr), num_permanent_constraints(0), num_temporary_constraints(0), - model_dirty(false) { + has_pending_constraint_additions(false) { int status = GRBloadenv(&env, ""); if (status) { handle_gurobi_error(env, status); @@ -171,8 +171,7 @@ void GurobiSolverInterface::load_problem(const LinearProgram &lp) { add_constraint(env, model, constraint); } - GRB_CALL(env, GRBupdatemodel, model); - model_dirty = false; + has_pending_constraint_additions = constraints.size() > 0; } void GurobiSolverInterface::add_temporary_constraints( @@ -184,7 +183,9 @@ void GurobiSolverInterface::add_temporary_constraints( } num_temporary_constraints += static_cast(constraints.size()); - model_dirty = true; + if (constraints.size() > 0) { + has_pending_constraint_additions = true; + } } void GurobiSolverInterface::clear_temporary_constraints() { @@ -194,9 +195,9 @@ void GurobiSolverInterface::clear_temporary_constraints() { return; } - if (model_dirty) { + if (has_pending_constraint_additions) { GRB_CALL(env, GRBupdatemodel, model); - model_dirty = false; + has_pending_constraint_additions = false; } vector indices(num_temporary_constraints); @@ -205,10 +206,8 @@ void GurobiSolverInterface::clear_temporary_constraints() { GRB_CALL( env, GRBdelconstrs, model, num_temporary_constraints, indices.data()); - GRB_CALL(env, GRBupdatemodel, model); - num_temporary_constraints = 0; - model_dirty = false; + has_pending_constraint_additions = false; } double GurobiSolverInterface::get_infinity() const { @@ -228,8 +227,6 @@ void GurobiSolverInterface::set_objective_coefficients( env, GRBsetdblattrarray, model, GRB_DBL_ATTR_OBJ, 0, static_cast(coefficients.size()), const_cast(coefficients.data())); - - model_dirty = true; } void GurobiSolverInterface::set_objective_coefficient( @@ -239,8 +236,6 @@ void GurobiSolverInterface::set_objective_coefficient( GRB_CALL( env, GRBsetdblattrelement, model, GRB_DBL_ATTR_OBJ, index, coefficient); - - model_dirty = true; } void GurobiSolverInterface::set_constraint_rhs( @@ -248,32 +243,28 @@ void GurobiSolverInterface::set_constraint_rhs( assert(model); assert(index >= 0 && index < get_num_constraints()); - if (model_dirty) { + if (has_pending_constraint_additions) { GRB_CALL(env, GRBupdatemodel, model); - model_dirty = false; + has_pending_constraint_additions = false; } GRB_CALL( env, GRBsetdblattrelement, model, GRB_DBL_ATTR_RHS, index, right_hand_side); - - model_dirty = true; } void GurobiSolverInterface::set_constraint_sense(int index, Sense sense) { assert(model); assert(index >= 0 && index < get_num_constraints()); - if (model_dirty) { + if (has_pending_constraint_additions) { GRB_CALL(env, GRBupdatemodel, model); - model_dirty = false; + has_pending_constraint_additions = false; } GRB_CALL( env, GRBsetcharattrelement, model, GRB_CHAR_ATTR_SENSE, index, constraint_sense_to_gurobi(sense)); - - model_dirty = true; } void GurobiSolverInterface::set_variable_lower_bound(int index, double bound) { @@ -281,8 +272,6 @@ void GurobiSolverInterface::set_variable_lower_bound(int index, double bound) { assert(index >= 0 && index < get_num_variables()); GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_LB, index, bound); - - model_dirty = true; } void GurobiSolverInterface::set_variable_upper_bound(int index, double bound) { @@ -290,8 +279,6 @@ void GurobiSolverInterface::set_variable_upper_bound(int index, double bound) { assert(index >= 0 && index < get_num_variables()); GRB_CALL(env, GRBsetdblattrelement, model, GRB_DBL_ATTR_UB, index, bound); - - model_dirty = true; } void GurobiSolverInterface::set_mip_gap(double gap) { @@ -308,22 +295,15 @@ void GurobiSolverInterface::set_mip_gap(double gap) { void GurobiSolverInterface::solve() { assert(model); - if (model_dirty) { - GRB_CALL(env, GRBupdatemodel, model); - model_dirty = false; - } - GRB_CALL(env, GRBoptimize, model); + has_pending_constraint_additions = false; } void GurobiSolverInterface::write_lp(const string &filename) const { assert(model); - if (model_dirty) { - GRB_CALL(env, GRBupdatemodel, model); - } - GRB_CALL(env, GRBwrite, model, filename.c_str()); + has_pending_constraint_additions = false; } void GurobiSolverInterface::print_failure_analysis() const { diff --git a/src/search/lp/gurobi_solver_interface.h b/src/search/lp/gurobi_solver_interface.h index be866e0b15..ad11e807cf 100644 --- a/src/search/lp/gurobi_solver_interface.h +++ b/src/search/lp/gurobi_solver_interface.h @@ -11,7 +11,8 @@ class GurobiSolverInterface : public SolverInterface { GRBmodel *model; int num_permanent_constraints; int num_temporary_constraints; - bool model_dirty; + // Indexed attribute changes require pending constraints to be integrated. + mutable bool has_pending_constraint_additions; public: GurobiSolverInterface(); From a02a7f9ad2f8141f316e8b7ed438ede2ae6e6c81 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 31 Jul 2026 09:27:09 +0200 Subject: [PATCH 06/35] add support to gurobi in ubuntu CI --- .github/workflows/ubuntu.yml | 36 +++++++++++++++++++++++++++-- misc/tests/test-standard-configs.py | 8 +++++++ misc/tox.ini | 7 ++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index f3a5d9e668..465fd6b174 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -3,7 +3,7 @@ name: Ubuntu on: push: - branches: [main, release-*] + branches: [main, release-*, issue1199-part2-algs] pull_request: branches: [main, release-*] @@ -31,9 +31,16 @@ jobs: env: CC: ${{ matrix.version.cc }} CXX: ${{ matrix.version.cxx }} + CPLEX_URL: ${{ secrets.CPLEX2211_LINUX_URL }} cplex_DIR: /home/runner/lib/ibm/ILOG/CPLEX_Studio2211/cplex CPLEX_LIB: /home/runner/lib/ibm/ILOG/CPLEX_Studio2211/cplex/bin/x86-64_linux/libcplex2211.so + + GUROBI_URL: ${{ secrets.GUROBI1302_LINUX_URL }} + GUROBI_HOME: /home/runner/lib/gurobi1302/linux64 + GUROBI_LIB: /home/runner/lib/gurobi1302/linux64/lib/libgurobi130.so + LD_LIBRARY_PATH: /home/runner/lib/gurobi1302/linux64/lib + soplex_DIR: /home/runner/lib/soplex-7.1.0 SOPLEX_LIB: /home/runner/lib/soplex-7.1.0/lib/ SOPLEX_INCLUDE: /home/runner/lib/soplex-7.1.0/include/ @@ -70,6 +77,15 @@ jobs: ./cplex_installer -DLICENSE_ACCEPTED=TRUE -DUSER_INSTALL_DIR="$(dirname "${cplex_DIR}")" -i silent rm cplex_installer + # Only install Gurobi if its URL/secret is set. + - name: Install Gurobi + if: ${{ env.GUROBI_URL != 0 }} + run: | + # We redirect output of wget to hide the secret URL. + wget -O gurobi.tar.gz $GUROBI_URL &> /dev/null + tar xfz gurobi.tar.gz -C /home/runner/lib + rm gurobi.tar.gz + # Always install SoPlex - name: Install SoPlex run: | @@ -98,6 +114,9 @@ jobs: if [[ ! -z "${CPLEX_URL}" ]]; then files_to_archive="${files_to_archive} ${CPLEX_LIB}" fi + if [[ ! -z "${GUROBI_URL}" ]]; then + files_to_archive="${files_to_archive} ${GUROBI_LIB}" + fi tar cfz archive.tar.gz -C "/home/runner" $(realpath --relative-to="/home/runner" $files_to_archive) - name: Upload archive @@ -120,6 +139,10 @@ jobs: - {ubuntu: ubuntu-24.04, python: '3.10'} env: CPLEX_URL: ${{ secrets.CPLEX2211_LINUX_URL }} + GUROBI_URL: ${{ secrets.GUROBI1302_LINUX_URL }} + GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} + GRB_LICENSE_FILE: /home/runner/gurobi.lic + LD_LIBRARY_PATH: /home/runner/lib/gurobi1302/linux64/lib steps: - name: Download archive uses: actions/download-artifact@v4.1.8 @@ -172,9 +195,18 @@ jobs: cd misc/ tox -e cplex + - name: Run Gurobi tests + if: ${{ env.GUROBI_URL != 0 }} + run: | + # We redirect output of wget to hide the secret URL. + wget -O "$GRB_LICENSE_FILE" $GUROBI_LICENSE_URL &> /dev/null + chmod 600 "$GRB_LICENSE_FILE" + cd misc/ + tox -e gurobi + - name: Run SoPlex tests run: | cd misc/ tox -e soplex -... +... \ No newline at end of file diff --git a/misc/tests/test-standard-configs.py b/misc/tests/test-standard-configs.py index 412c71b174..21d6a7560b 100644 --- a/misc/tests/test-standard-configs.py +++ b/misc/tests/test-standard-configs.py @@ -64,6 +64,12 @@ def test_configs_cplex(config, debug): run_plan_script(SAS_FILE, config, debug) +@pytest.mark.parametrize("config", sorted(configs.configs_optimal_lp(lp_solver="gurobi").values())) +@pytest.mark.parametrize("debug", [False, True]) +def test_configs_gurobi(config, debug): + run_plan_script(SAS_FILE, config, debug) + + @pytest.mark.parametrize("config", sorted(configs.configs_optimal_lp(lp_solver="soplex").values())) @pytest.mark.parametrize("debug", [False, True]) def test_configs_soplex(config, debug): @@ -72,3 +78,5 @@ def test_configs_soplex(config, debug): def teardown_module(module): cleanup() + + diff --git a/misc/tox.ini b/misc/tox.ini index be7908006c..ad4bf7c880 100644 --- a/misc/tox.ini +++ b/misc/tox.ini @@ -53,6 +53,13 @@ deps = commands = pytest test-standard-configs.py -k test_configs_cplex +[testenv:gurobi] +changedir = {toxinidir}/tests/ +deps = + pytest +commands = + pytest test-standard-configs.py -k test_configs_gurobi + [testenv:soplex] changedir = {toxinidir}/tests/ deps = From 5e616a969b693039a51b613bfc26d419ab2503a6 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 31 Jul 2026 09:51:18 +0200 Subject: [PATCH 07/35] update ubuntu ci --- .github/workflows/ubuntu.yml | 2 +- misc/tests/test-standard-configs.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 465fd6b174..6881412e3f 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -38,7 +38,7 @@ jobs: GUROBI_URL: ${{ secrets.GUROBI1302_LINUX_URL }} GUROBI_HOME: /home/runner/lib/gurobi1302/linux64 - GUROBI_LIB: /home/runner/lib/gurobi1302/linux64/lib/libgurobi130.so + GUROBI_LIB: /home/runner/lib/gurobi1302/linux64/lib LD_LIBRARY_PATH: /home/runner/lib/gurobi1302/linux64/lib soplex_DIR: /home/runner/lib/soplex-7.1.0 diff --git a/misc/tests/test-standard-configs.py b/misc/tests/test-standard-configs.py index 21d6a7560b..a16a543767 100644 --- a/misc/tests/test-standard-configs.py +++ b/misc/tests/test-standard-configs.py @@ -78,5 +78,3 @@ def test_configs_soplex(config, debug): def teardown_module(module): cleanup() - - From be8977f19bdc658415968e4fda04ad8661092a8d Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 31 Jul 2026 12:30:06 +0200 Subject: [PATCH 08/35] update documentation --- BUILD.md | 18 ++++++++++++++++-- src/search/cmake/FindGurobi.cmake | 1 - src/search/lp/gurobi_solver_interface.h | 1 - 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/BUILD.md b/BUILD.md index f897124712..2fa77aaf1b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -16,9 +16,9 @@ During the installation of Visual Studio, the C++ compiler is not installed by d ### Optional: Linear-Programming Solvers -Some planner configurations depend on an LP or MIP solver. We support CPLEX (commercial, [free academic license](http://ibm.com/academic)) and SoPlex (Apache License, no MIP support). You can install one or both solvers without causing conflicts. +Some planner configurations depend on an LP or MIP solver. We support CPLEX (commercial, [free academic license](http://ibm.com/academic)), Gurobi (commercial, [free academic license](https://www.gurobi.com/academics#licenses)), and SoPlex (Apache License, no MIP support). You can install one or both solvers without causing conflicts. -Once LP solvers are installed and the environment variables `cplex_DIR` and/or `soplex_DIR` are set up correctly, Fast Downward automatically includes each solver detected on the system in the build. +Once LP solvers are installed and the appropriate environment variables are set up correctly, Fast Downward automatically includes each solver detected on the system in the build. #### Installing CPLEX @@ -32,6 +32,20 @@ export cplex_DIR=/opt/ibm/ILOG/CPLEX_Studio2211/cplex ``` Note that on Windows, setting up the environment variable might require using `/` instead of the more Windows-common `\`. +#### Installing Gurobi on Ubuntu + +We currently only support Gurobi 13.0.x. After downloading from the website and obtaining a license, you should have a directory called `gurobi130x`, where `x` depends on your patch. Set the following environment variables for Ubuntu and OSX. Instructions for Windows will follow soon. +- `GUROBI_HOME` to the folder where the binaries and headers are located. +- `GRB_LICENSE_FILE` to the path where the Gurobi license file is located. +- `LD_LIBRARY_PATH` to where the Gurobi precompiled libraries are located. + +For example, on Ubuntu, assuming that Gurobi 13.0.2 was installed under `/opt` and the license is at `/home/myuser`: + +```bash +export GUROBI_HOME="/opt/gurobi1302/linux64" +export LD_LIBRARY_PATH="${GUROBI_HOME}/lib:$LD_LIBRARY_PATH" +export GRB_LICENSE_FILE="/home/myuser/gurobi.lic" +``` #### Installing SoPlex on Linux/macOS diff --git a/src/search/cmake/FindGurobi.cmake b/src/search/cmake/FindGurobi.cmake index ba9471fff2..b1cad55de5 100644 --- a/src/search/cmake/FindGurobi.cmake +++ b/src/search/cmake/FindGurobi.cmake @@ -21,7 +21,6 @@ find_path(GUROBI_INCLUDE_DIR find_library(GUROBI_LIBRARY NAMES gurobi130 # Gurobi 13.0 - gurobi110 HINTS ${HINT_PATHS} PATH_SUFFIXES lib ) diff --git a/src/search/lp/gurobi_solver_interface.h b/src/search/lp/gurobi_solver_interface.h index ad11e807cf..4172c15f9c 100644 --- a/src/search/lp/gurobi_solver_interface.h +++ b/src/search/lp/gurobi_solver_interface.h @@ -11,7 +11,6 @@ class GurobiSolverInterface : public SolverInterface { GRBmodel *model; int num_permanent_constraints; int num_temporary_constraints; - // Indexed attribute changes require pending constraints to be integrated. mutable bool has_pending_constraint_additions; public: From 80ea6cae153777a3be31da679c9e3e188d909736 Mon Sep 17 00:00:00 2001 From: Travis Rivera Petit Date: Fri, 31 Jul 2026 17:44:58 +0200 Subject: [PATCH 09/35] Fix gurobi --- src/search/lp/gurobi_solver_interface.cc | 10 +++++----- src/search/lp/gurobi_solver_interface.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/search/lp/gurobi_solver_interface.cc b/src/search/lp/gurobi_solver_interface.cc index 7a2b4fe2af..842939079c 100644 --- a/src/search/lp/gurobi_solver_interface.cc +++ b/src/search/lp/gurobi_solver_interface.cc @@ -50,13 +50,13 @@ int objective_sense_to_gurobi(LPObjectiveSense sense) { ABORT("Unknown LP objective sense."); } -char constraint_sense_to_gurobi(Sense sense) { +char constraint_sense_to_gurobi(LPConstraintSense sense) { switch (sense) { - case Sense::GE: + case LPConstraintSense::GREATER_EQUAL: return GRB_GREATER_EQUAL; - case Sense::LE: + case LPConstraintSense::LESS_EQUAL: return GRB_LESS_EQUAL; - case Sense::EQ: + case LPConstraintSense::EQUAL: return GRB_EQUAL; } @@ -253,7 +253,7 @@ void GurobiSolverInterface::set_constraint_rhs( right_hand_side); } -void GurobiSolverInterface::set_constraint_sense(int index, Sense sense) { +void GurobiSolverInterface::set_constraint_sense(int index, LPConstraintSense sense) { assert(model); assert(index >= 0 && index < get_num_constraints()); diff --git a/src/search/lp/gurobi_solver_interface.h b/src/search/lp/gurobi_solver_interface.h index 4172c15f9c..7641c728a2 100644 --- a/src/search/lp/gurobi_solver_interface.h +++ b/src/search/lp/gurobi_solver_interface.h @@ -28,7 +28,7 @@ class GurobiSolverInterface : public SolverInterface { virtual void set_objective_coefficient( int index, double coefficient) override; virtual void set_constraint_rhs(int index, double right_hand_side) override; - virtual void set_constraint_sense(int index, lp::Sense sense) override; + virtual void set_constraint_sense(int index, LPConstraintSense sense) override; virtual void set_variable_lower_bound(int index, double bound) override; virtual void set_variable_upper_bound(int index, double bound) override; From 3055535dee59038d0b57a27946a642b44ae7adf3 Mon Sep 17 00:00:00 2001 From: Travis Rivera Petit Date: Fri, 31 Jul 2026 17:48:39 +0200 Subject: [PATCH 10/35] Update git actions --- .github/workflows/ubuntu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 6881412e3f..babc6fd6f7 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -3,7 +3,7 @@ name: Ubuntu on: push: - branches: [main, release-*, issue1199-part2-algs] + branches: [main, release-*, issue1199-part2-algs-rebased] pull_request: branches: [main, release-*] From 184f3eb2347557668cb0659676cba652284fcc18 Mon Sep 17 00:00:00 2001 From: Travis Rivera Petit Date: Fri, 31 Jul 2026 18:31:00 +0200 Subject: [PATCH 11/35] New mac github actions --- .github/workflows/mac.yml | 113 +++++++++++++++++++++++++++++++++----- 1 file changed, 99 insertions(+), 14 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 875c12124b..2911759003 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -3,7 +3,7 @@ name: macOS on: push: - branches: [main, release-*] + branches: [main, release-*, issue1199-part2-algs-rebased] pull_request: branches: [main, release-*] @@ -12,47 +12,132 @@ jobs: name: Compile and test planner timeout-minutes: 60 runs-on: ${{ matrix.version.macos }} + strategy: matrix: version: - - {macos: macos-14, python: '3.14'} - - {macos: macos-15, python: '3.14'} + - {macos: macos-14, python: '3.10'} + - {macos: macos-15, python: '3.10'} + + env: + GUROBI_URL: ${{ secrets.GUROBI1302_OSX_URL }} + GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} + + GUROBI_HOME: /Library/gurobi1302/macos_universal2 + GUROBI_LIB: /Library/gurobi1302/macos_universal2/lib + DYLD_LIBRARY_PATH: /Library/gurobi1302/macos_universal2/lib + GRB_LICENSE_FILE: /Users/runner/gurobi.lic + + soplex_DIR: /Users/runner/lib/soplex-7.1.0 + SOPLEX_LIB: /Users/runner/lib/soplex-7.1.0/lib + SOPLEX_INCLUDE: /Users/runner/lib/soplex-7.1.0/include + + HOMEBREW_NO_AUTO_UPDATE: 1 + steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.version.python }} + - name: Install dependencies + run: | + brew install gmp gnu-sed + mkdir -p /Users/runner/lib + + # The secret must contain a direct URL to the macOS .pkg file. + - name: Install Gurobi + if: env.GUROBI_URL != '' + run: | + curl \ + --fail \ + --location \ + --silent \ + --show-error \ + "$GUROBI_URL" \ + --output "$RUNNER_TEMP/gurobi.pkg" + + sudo installer \ + -pkg "$RUNNER_TEMP/gurobi.pkg" \ + -target / + + rm "$RUNNER_TEMP/gurobi.pkg" + + # Fail immediately if Gurobi was not installed where expected. + test -f "$GUROBI_HOME/include/gurobi_c.h" + test -d "$GUROBI_HOME/lib" + + - name: Install SoPlex + run: | + git clone https://github.com/scipopt/soplex.git + cd soplex + git checkout release-710 + cd .. + + cmake \ + -S soplex \ + -B soplex-build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$soplex_DIR" + + cmake --build soplex-build --parallel 2 + cmake --install soplex-build + + rm -rf soplex soplex-build + - name: Compile planner run: | - export CXXFLAGS="-Werror" # Treat compilation warnings as errors. - ./build.py + export CXXFLAGS="-Werror" ./build.py --debug + ./build.py - name: Install tox run: | - pip3 install tox + python3 -m pip install tox - name: Install VAL run: | - brew install gnu-sed git clone https://github.com/KCL-Planning/VAL.git cd VAL git checkout a5565396007eee73ac36527fbf904142b3077c74 - make clean # Remove old build artifacts and binaries. - gsed -i 's/-Werror //g' Makefile # Ignore warnings. + + make clean + gsed -i 's/-Werror //g' Makefile make -j2 - mv validate ../ - cd ../ + + mv validate "$GITHUB_WORKSPACE/" + cd .. rm -rf VAL + echo "$GITHUB_WORKSPACE" >> "$GITHUB_PATH" + - name: Run driver, translator and search tests run: | - export PATH="$(pwd):$PATH" # Add VAL to path. cd misc tox -e driver,translator,search + - name: Run Gurobi tests + if: env.GUROBI_URL != '' && env.GUROBI_LICENSE_URL != '' + run: | + curl \ + --fail \ + --location \ + --silent \ + --show-error \ + "$GUROBI_LICENSE_URL" \ + --output "$GRB_LICENSE_FILE" + + chmod 600 "$GRB_LICENSE_FILE" + + cd misc + tox -e gurobi + + - name: Run SoPlex tests + run: | + cd misc + tox -e soplex + ... From 6c0595a98f8fd0b36123746a5a32a100b22c51f8 Mon Sep 17 00:00:00 2001 From: Travis Rivera Petit Date: Fri, 31 Jul 2026 18:37:07 +0200 Subject: [PATCH 12/35] mac --- .github/workflows/mac.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 2911759003..1950e4d336 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -90,7 +90,11 @@ jobs: - name: Compile planner run: | - export CXXFLAGS="-Werror" + GMP_PREFIX="$(brew --prefix gmp)" + + export CXXFLAGS="-Werror -I$GMP_PREFIX/include" + export LDFLAGS="-L$GMP_PREFIX/lib" + ./build.py --debug ./build.py From 6a1b8d467b96221aa403c10318bc299f4564e019 Mon Sep 17 00:00:00 2001 From: Travis Rivera Petit Date: Fri, 31 Jul 2026 18:42:40 +0200 Subject: [PATCH 13/35] no soplex bye --- .github/workflows/mac.yml | 197 ++++++++++---------------------------- 1 file changed, 50 insertions(+), 147 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 1950e4d336..0431990835 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -1,147 +1,50 @@ ---- -name: macOS - -on: - push: - branches: [main, release-*, issue1199-part2-algs-rebased] - pull_request: - branches: [main, release-*] - -jobs: - test: - name: Compile and test planner - timeout-minutes: 60 - runs-on: ${{ matrix.version.macos }} - - strategy: - matrix: - version: - - {macos: macos-14, python: '3.10'} - - {macos: macos-15, python: '3.10'} - - env: - GUROBI_URL: ${{ secrets.GUROBI1302_OSX_URL }} - GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} - - GUROBI_HOME: /Library/gurobi1302/macos_universal2 - GUROBI_LIB: /Library/gurobi1302/macos_universal2/lib - DYLD_LIBRARY_PATH: /Library/gurobi1302/macos_universal2/lib - GRB_LICENSE_FILE: /Users/runner/gurobi.lic - - soplex_DIR: /Users/runner/lib/soplex-7.1.0 - SOPLEX_LIB: /Users/runner/lib/soplex-7.1.0/lib - SOPLEX_INCLUDE: /Users/runner/lib/soplex-7.1.0/include - - HOMEBREW_NO_AUTO_UPDATE: 1 - - steps: - - name: Clone repository - uses: actions/checkout@v4 - - - name: Install Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.version.python }} - - - name: Install dependencies - run: | - brew install gmp gnu-sed - mkdir -p /Users/runner/lib - - # The secret must contain a direct URL to the macOS .pkg file. - - name: Install Gurobi - if: env.GUROBI_URL != '' - run: | - curl \ - --fail \ - --location \ - --silent \ - --show-error \ - "$GUROBI_URL" \ - --output "$RUNNER_TEMP/gurobi.pkg" - - sudo installer \ - -pkg "$RUNNER_TEMP/gurobi.pkg" \ - -target / - - rm "$RUNNER_TEMP/gurobi.pkg" - - # Fail immediately if Gurobi was not installed where expected. - test -f "$GUROBI_HOME/include/gurobi_c.h" - test -d "$GUROBI_HOME/lib" - - - name: Install SoPlex - run: | - git clone https://github.com/scipopt/soplex.git - cd soplex - git checkout release-710 - cd .. - - cmake \ - -S soplex \ - -B soplex-build \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="$soplex_DIR" - - cmake --build soplex-build --parallel 2 - cmake --install soplex-build - - rm -rf soplex soplex-build - - - name: Compile planner - run: | - GMP_PREFIX="$(brew --prefix gmp)" - - export CXXFLAGS="-Werror -I$GMP_PREFIX/include" - export LDFLAGS="-L$GMP_PREFIX/lib" - - ./build.py --debug - ./build.py - - - name: Install tox - run: | - python3 -m pip install tox - - - name: Install VAL - run: | - git clone https://github.com/KCL-Planning/VAL.git - cd VAL - git checkout a5565396007eee73ac36527fbf904142b3077c74 - - make clean - gsed -i 's/-Werror //g' Makefile - make -j2 - - mv validate "$GITHUB_WORKSPACE/" - cd .. - rm -rf VAL - - echo "$GITHUB_WORKSPACE" >> "$GITHUB_PATH" - - - name: Run driver, translator and search tests - run: | - cd misc - tox -e driver,translator,search - - - name: Run Gurobi tests - if: env.GUROBI_URL != '' && env.GUROBI_LICENSE_URL != '' - run: | - curl \ - --fail \ - --location \ - --silent \ - --show-error \ - "$GUROBI_LICENSE_URL" \ - --output "$GRB_LICENSE_FILE" - - chmod 600 "$GRB_LICENSE_FILE" - - cd misc - tox -e gurobi - - - name: Run SoPlex tests - run: | - cd misc - tox -e soplex - -... +env: + GUROBI_URL: ${{ secrets.GUROBI1302_MACOS_URL }} + GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} + + GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 + GUROBI_LIB: /Users/runner/lib/gurobi1302/macos_universal2/lib + DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib + + GRB_LICENSE_FILE: /Users/runner/gurobi.lic + +steps: + - name: Clone repository + uses: actions/checkout@v3 + + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.version.python }} + + - name: Install Gurobi + if: env.GUROBI_URL != '' + run: | + curl \ + --fail \ + --location \ + --silent \ + --show-error \ + "$GUROBI_URL" \ + --output "$RUNNER_TEMP/gurobi.pkg" + + sudo installer \ + -pkg "$RUNNER_TEMP/gurobi.pkg" \ + -target / + + mkdir -p "$HOME/lib/gurobi1302" + + cp -R \ + /Library/gurobi1302/macos_universal2 \ + "$HOME/lib/gurobi1302/" + + rm "$RUNNER_TEMP/gurobi.pkg" + + test -f "$GUROBI_HOME/include/gurobi_c.h" + test -d "$GUROBI_HOME/lib" + + - name: Compile planner + run: | + export CXXFLAGS="-Werror" + ./build.py --debug + ./build.py From 97bb525e462e9ff0c31dc71c91e8d3711396bf98 Mon Sep 17 00:00:00 2001 From: Travis Rivera Petit Date: Fri, 31 Jul 2026 18:45:32 +0200 Subject: [PATCH 14/35] Fix --- .github/workflows/mac.yml | 110 +++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 0431990835..5954314c82 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -1,50 +1,60 @@ -env: - GUROBI_URL: ${{ secrets.GUROBI1302_MACOS_URL }} - GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} - - GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 - GUROBI_LIB: /Users/runner/lib/gurobi1302/macos_universal2/lib - DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib - - GRB_LICENSE_FILE: /Users/runner/gurobi.lic - -steps: - - name: Clone repository - uses: actions/checkout@v3 - - - name: Install Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.version.python }} - - - name: Install Gurobi - if: env.GUROBI_URL != '' - run: | - curl \ - --fail \ - --location \ - --silent \ - --show-error \ - "$GUROBI_URL" \ - --output "$RUNNER_TEMP/gurobi.pkg" - - sudo installer \ - -pkg "$RUNNER_TEMP/gurobi.pkg" \ - -target / - - mkdir -p "$HOME/lib/gurobi1302" - - cp -R \ - /Library/gurobi1302/macos_universal2 \ - "$HOME/lib/gurobi1302/" - - rm "$RUNNER_TEMP/gurobi.pkg" - - test -f "$GUROBI_HOME/include/gurobi_c.h" - test -d "$GUROBI_HOME/lib" - - - name: Compile planner - run: | - export CXXFLAGS="-Werror" - ./build.py --debug - ./build.py +--- +name: macOS + +on: + push: + branches: [main, release-*, issue1199-part2-algs-rebased] + pull_request: + branches: [main, release-*] + +jobs: + compile: + name: Compile planner + timeout-minutes: 60 + runs-on: macos-14 + + env: + GUROBI_URL: ${{ secrets.GUROBI1302_MACOS_URL }} + GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 + DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib + + steps: + - name: Clone repository + uses: actions/checkout@v3 + + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Gurobi + if: env.GUROBI_URL != '' + run: | + curl \ + --fail \ + --location \ + --silent \ + --show-error \ + "$GUROBI_URL" \ + --output "$RUNNER_TEMP/gurobi.pkg" + + sudo installer \ + -pkg "$RUNNER_TEMP/gurobi.pkg" \ + -target / + + mkdir -p "$HOME/lib/gurobi1302" + + cp -R \ + /Library/gurobi1302/macos_universal2 \ + "$HOME/lib/gurobi1302/" + + rm "$RUNNER_TEMP/gurobi.pkg" + + test -f "$GUROBI_HOME/include/gurobi_c.h" + test -d "$GUROBI_HOME/lib" + + - name: Compile planner + run: | + export CXXFLAGS="-Werror" + ./build.py --debug + ./build.py From d9113c94dd1531da0fa1d53c8b93580d3ce38aeb Mon Sep 17 00:00:00 2001 From: Travis Rivera Petit Date: Fri, 31 Jul 2026 18:48:46 +0200 Subject: [PATCH 15/35] asjdkfl --- .github/workflows/mac.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 5954314c82..8ea8f47251 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -14,7 +14,7 @@ jobs: runs-on: macos-14 env: - GUROBI_URL: ${{ secrets.GUROBI1302_MACOS_URL }} + GUROBI_URL: ${{ secrets.GUROBI1302_OSX_URL }} GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib From a351fe6d4a63cbb2a0b97bb55829749537ed9719 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 10:56:40 +0200 Subject: [PATCH 16/35] update macos CI workflow --- .github/workflows/mac.yml | 144 ++++++++++++++++++++++++++++++++------ 1 file changed, 121 insertions(+), 23 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 8ea8f47251..f6c6456944 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -11,13 +11,19 @@ jobs: compile: name: Compile planner timeout-minutes: 60 - runs-on: macos-14 - + strategy: + matrix: + version: + - {macos: macos-14, python: '3.10', run_tox_tests: true} + - {macos: macos-15, python: '3.10', run_tox_tests: true} + runs-on: ${{ matrix.version.macos }} env: GUROBI_URL: ${{ secrets.GUROBI1302_OSX_URL }} GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib - + soplex_DIR: /Users/runner/lib/soplex-7.1.0 + SOPLEX_LIB: /Users/runner/lib/soplex-7.1.0/lib/ + SOPLEX_INCLUDE: /Users/runner/lib/soplex-7.1.0/include/ steps: - name: Clone repository uses: actions/checkout@v3 @@ -25,36 +31,128 @@ jobs: - name: Install Python uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: ${{ matrix.version.python }} - - name: Install Gurobi - if: env.GUROBI_URL != '' + - name: Install dependencies run: | - curl \ - --fail \ - --location \ - --silent \ - --show-error \ - "$GUROBI_URL" \ - --output "$RUNNER_TEMP/gurobi.pkg" - - sudo installer \ - -pkg "$RUNNER_TEMP/gurobi.pkg" \ - -target / + brew install coreutils gmp + - name: Install Gurobi + if: ${{ env.GUROBI_URL != 0 }} + run: | + wget -O "$RUNNER_TEMP/gurobi.pkg" "$GUROBI_URL" &> /dev/null + sudo installer -pkg "$RUNNER_TEMP/gurobi.pkg" -target / mkdir -p "$HOME/lib/gurobi1302" - - cp -R \ - /Library/gurobi1302/macos_universal2 \ - "$HOME/lib/gurobi1302/" - + cp -R /Library/gurobi1302/macos_universal2 "$HOME/lib/gurobi1302/" rm "$RUNNER_TEMP/gurobi.pkg" - test -f "$GUROBI_HOME/include/gurobi_c.h" test -d "$GUROBI_HOME/lib" + - name: Install SoPlex + run: | + git clone https://github.com/scipopt/soplex.git + cd soplex + git checkout release-710 + cd .. + cmake -S soplex -B build + cmake --build build + cmake --install build --prefix "${soplex_DIR}" + rm -rf soplex build + - name: Compile planner run: | export CXXFLAGS="-Werror" ./build.py --debug ./build.py + + - name: Archive required files + if: ${{ matrix.version.run_tox_tests }} + run: | + files_to_archive="fast-downward.py driver misc src builds/debug/bin/ \ + builds/release/bin/ ${SOPLEX_LIB} ${SOPLEX_INCLUDE}" + if [[ -n "${GUROBI_URL}" ]]; then + files_to_archive="${files_to_archive} ${GUROBI_HOME}/lib" + fi + tar czf archive.tar.gz -C "$HOME" $(grealpath --relative-to="$HOME" $files_to_archive) + + - name: Upload archive + if: ${{ matrix.version.run_tox_tests }} + uses: actions/upload-artifact@v4.4.0 + with: + name: compiled-planner-${{ matrix.version.macos }} + path: archive.tar.gz + retention-days: 1 + + run_tox_tests: + name: Test planner + needs: compile # TODO: this only depends on the compile step with the gcc version we test + strategy: + matrix: + version: + - {macos: macos-14, python: '3.10'} + - {macos: macos-15, python: '3.10'} + runs-on: ${{ matrix.version.macos }} + env: + GUROBI_URL: ${{ secrets.GUROBI1302_OSX_URL }} + GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} + GRB_LICENSE_FILE: /Users/runner/gurobi.lic + DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib + steps: + - name: Download archive + uses: actions/download-artifact@v4.1.8 + with: + name: compiled-planner-${{ matrix.version.macos }} + + - name: Delete artifact (ignore if not found) + uses: geekyeggo/delete-artifact@v2 + continue-on-error: true + with: + name: compiled-planner-${{ matrix.version.macos }} + + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.version.python }} + + - name: Install dependencies + run: | + pip3 install tox + brew install gmp + + - name: Install VAL + run: | + git clone https://github.com/KCL-Planning/VAL.git + cd VAL + git checkout a5565396007eee73ac36527fbf904142b3077c74 + make clean # Remove old build artifacts and binaries. + sed -i '' 's/-Werror //g' Makefile # Ignore warnings. + make -j2 + mv validate ../ + cd ../ + rm -rf VAL + echo "$PWD" >> "$GITHUB_PATH" # Add VAL to path of subsequent steps. + + - name: Extract archive + # We need to make sure that library paths are the same as + # during compilation. + run: | + tar xfz archive.tar.gz -C "$HOME" + + - name: Run driver, translator and search tests + run: | + cd misc/ + tox -e driver,translator,search,parameters,generate-docs + + - name: Run Gurobi tests + if: ${{ env.GUROBI_URL != 0 }} + run: | + # We redirect output of wget to hide the secret URL. + wget -O "$GRB_LICENSE_FILE" "$GUROBI_LICENSE_URL" &> /dev/null + chmod 600 "$GRB_LICENSE_FILE" + cd misc/ + tox -e gurobi + + - name: Run SoPlex tests + run: | + cd misc/ + tox -e soplex \ No newline at end of file From 4940fd671021022cbc3b723bfe5daf0bc76fd68a Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 11:23:36 +0200 Subject: [PATCH 17/35] update macos CI workflow (add gmp paths to env variables) --- .github/workflows/mac.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index f6c6456944..1958b34a60 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -61,7 +61,9 @@ jobs: - name: Compile planner run: | - export CXXFLAGS="-Werror" + gmp_prefix="$(brew --prefix gmp)" + export CXXFLAGS="-Werror -I${gmp_prefix}/include" + export LDFLAGS="-L${gmp_prefix}/lib" ./build.py --debug ./build.py From 75205060350f05d21cf6bfc7d7f0a19024e291bb Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 11:30:58 +0200 Subject: [PATCH 18/35] update macos CI workflow (update soplex patch version) --- .github/workflows/mac.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 1958b34a60..1aeb62df26 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -21,9 +21,9 @@ jobs: GUROBI_URL: ${{ secrets.GUROBI1302_OSX_URL }} GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib - soplex_DIR: /Users/runner/lib/soplex-7.1.0 - SOPLEX_LIB: /Users/runner/lib/soplex-7.1.0/lib/ - SOPLEX_INCLUDE: /Users/runner/lib/soplex-7.1.0/include/ + soplex_DIR: /Users/runner/lib/soplex-7.1.6 + SOPLEX_LIB: /Users/runner/lib/soplex-7.1.6/lib/ + SOPLEX_INCLUDE: /Users/runner/lib/soplex-7.1.6/include/ steps: - name: Clone repository uses: actions/checkout@v3 @@ -52,7 +52,7 @@ jobs: run: | git clone https://github.com/scipopt/soplex.git cd soplex - git checkout release-710 + git checkout release-716 cd .. cmake -S soplex -B build cmake --build build From ace4ef4f56e53215134acc5edae67f79f45b29e3 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 11:55:16 +0200 Subject: [PATCH 19/35] update macos CI workflow (fix gurobi installation, update macos test version) --- .github/workflows/mac.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 1aeb62df26..cfa780b81e 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -14,8 +14,8 @@ jobs: strategy: matrix: version: - - {macos: macos-14, python: '3.10', run_tox_tests: true} - {macos: macos-15, python: '3.10', run_tox_tests: true} + - {macos: macos-26, python: '3.10', run_tox_tests: true} runs-on: ${{ matrix.version.macos }} env: GUROBI_URL: ${{ secrets.GUROBI1302_OSX_URL }} @@ -91,13 +91,14 @@ jobs: strategy: matrix: version: - - {macos: macos-14, python: '3.10'} - {macos: macos-15, python: '3.10'} + - {macos: macos-26, python: '3.10'} runs-on: ${{ matrix.version.macos }} env: GUROBI_URL: ${{ secrets.GUROBI1302_OSX_URL }} GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} GRB_LICENSE_FILE: /Users/runner/gurobi.lic + GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib steps: - name: Download archive @@ -140,6 +141,15 @@ jobs: run: | tar xfz archive.tar.gz -C "$HOME" + # Fast Downward's binary records Gurobi's installation path under /Library/gurobi1302/macos_universal2 + # This step copies the archived Gurobi to the right place + - name: Restore Gurobi library path + if: ${{ env.GUROBI_URL != 0 }} + run: | + sudo mkdir -p /Library/gurobi1302 + sudo cp -R "$GUROBI_HOME" /Library/gurobi1302/ + test -f /Library/gurobi1302/macos_universal2/lib/libgurobi130.dylib + - name: Run driver, translator and search tests run: | cd misc/ From e23f6f4319e24cc2f6c0dd05b6743831ba49f11d Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 12:21:26 +0200 Subject: [PATCH 20/35] update CI workflows (add to gurobi license check --- .github/workflows/mac.yml | 14 +++++++++++++- .github/workflows/ubuntu.yml | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index cfa780b81e..ce7bf07953 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -19,6 +19,8 @@ jobs: runs-on: ${{ matrix.version.macos }} env: GUROBI_URL: ${{ secrets.GUROBI1302_OSX_URL }} + GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} + GRB_LICENSE_FILE: /Users/runner/gurobi.lic GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib soplex_DIR: /Users/runner/lib/soplex-7.1.6 @@ -48,6 +50,16 @@ jobs: test -f "$GUROBI_HOME/include/gurobi_c.h" test -d "$GUROBI_HOME/lib" + - name: Check Gurobi license + if: ${{ env.GUROBI_URL != 0 }} + run: | + wget -O "$GRB_LICENSE_FILE" "$GUROBI_LICENSE_URL" &> /dev/null + chmod 600 "$GRB_LICENSE_FILE" + if ! "$GUROBI_HOME/bin/gurobi_cl" --license; then + echo "Gurobi license is invalid or expired. Please contact gustavo.delazeri@unibas.ch" + exit 1 + fi + - name: Install SoPlex run: | git clone https://github.com/scipopt/soplex.git @@ -167,4 +179,4 @@ jobs: - name: Run SoPlex tests run: | cd misc/ - tox -e soplex \ No newline at end of file + tox -e soplex diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index babc6fd6f7..ef9c5a1116 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -37,6 +37,8 @@ jobs: CPLEX_LIB: /home/runner/lib/ibm/ILOG/CPLEX_Studio2211/cplex/bin/x86-64_linux/libcplex2211.so GUROBI_URL: ${{ secrets.GUROBI1302_LINUX_URL }} + GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} + GRB_LICENSE_FILE: /home/runner/gurobi.lic GUROBI_HOME: /home/runner/lib/gurobi1302/linux64 GUROBI_LIB: /home/runner/lib/gurobi1302/linux64/lib LD_LIBRARY_PATH: /home/runner/lib/gurobi1302/linux64/lib @@ -86,6 +88,17 @@ jobs: tar xfz gurobi.tar.gz -C /home/runner/lib rm gurobi.tar.gz + - name: Check Gurobi license + if: ${{ env.GUROBI_URL != 0 }} + run: | + # We redirect output of wget to hide the secret URL. + wget -O "$GRB_LICENSE_FILE" "$GUROBI_LICENSE_URL" &> /dev/null + chmod 600 "$GRB_LICENSE_FILE" + if ! "$GUROBI_HOME/bin/gurobi_cl" --license; then + echo "Gurobi license is invalid or expired. Please contact gustavo.delazeri@unibas.ch" + exit 1 + fi + # Always install SoPlex - name: Install SoPlex run: | @@ -209,4 +222,4 @@ jobs: cd misc/ tox -e soplex -... \ No newline at end of file +... From 6085129efc4bedc5f4a770d50d50347fe38e2295 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 13:48:07 +0200 Subject: [PATCH 21/35] update ubuntu and macos CI workflows (hide gurobi_cl output) --- .github/workflows/mac.yml | 3 ++- .github/workflows/ubuntu.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index ce7bf07953..be4e87ad57 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -55,10 +55,11 @@ jobs: run: | wget -O "$GRB_LICENSE_FILE" "$GUROBI_LICENSE_URL" &> /dev/null chmod 600 "$GRB_LICENSE_FILE" - if ! "$GUROBI_HOME/bin/gurobi_cl" --license; then + if ! "$GUROBI_HOME/bin/gurobi_cl" --license &> /dev/null; then echo "Gurobi license is invalid or expired. Please contact gustavo.delazeri@unibas.ch" exit 1 fi + echo "Gurobi license check passed." - name: Install SoPlex run: | diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index ef9c5a1116..6688ff6d37 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -94,10 +94,11 @@ jobs: # We redirect output of wget to hide the secret URL. wget -O "$GRB_LICENSE_FILE" "$GUROBI_LICENSE_URL" &> /dev/null chmod 600 "$GRB_LICENSE_FILE" - if ! "$GUROBI_HOME/bin/gurobi_cl" --license; then + if ! "$GUROBI_HOME/bin/gurobi_cl" --license &> /dev/null; then echo "Gurobi license is invalid or expired. Please contact gustavo.delazeri@unibas.ch" exit 1 fi + echo "Gurobi license check passed." # Always install SoPlex - name: Install SoPlex From 777c003fa2f3ed190d6251f6ee4b60319523b07e Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 13:48:36 +0200 Subject: [PATCH 22/35] add vibe-coded windows CI workflow for gurobi --- .github/workflows/windows.yml | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index acf91841b3..483926b3a2 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -15,6 +15,9 @@ env: cplex_DIR: D:\a\cplex CPLEX_URL: "${{ secrets.CPLEX2211_WINDOWS_URL }}" + GUROBI_URL: "${{ secrets.GUROBI1302_WINDOWS_URL }}" + GUROBI_LICENSE_URL: "${{ secrets.GUROBI_LICENSE_URL }}" + GUROBI_HOME: C:\gurobi1302\win64 ZLIB_URL: "https://www.zlib.net/zlib132.zip" @@ -45,6 +48,53 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Install Gurobi + if: ${{ env.GUROBI_URL != 0 }} + run: | + $installer = Join-Path $ENV:RUNNER_TEMP "gurobi.msi" + curl.exe --fail --location --silent --show-error --output "$installer" "$ENV:GUROBI_URL" + if ($LASTEXITCODE -ne 0) { + throw "Failed to download Gurobi." + } + + $arguments = "/i `"$installer`" /quiet /norestart" + $process = Start-Process -FilePath msiexec.exe -ArgumentList $arguments -Wait -PassThru + if ($process.ExitCode -notin @(0, 3010)) { + throw "Gurobi installation failed with exit code $($process.ExitCode)." + } + Remove-Item "$installer" + + $required_files = @( + "$ENV:GUROBI_HOME\include\gurobi_c.h", + "$ENV:GUROBI_HOME\lib\gurobi130.lib", + "$ENV:GUROBI_HOME\bin\gurobi130.dll", + "$ENV:GUROBI_HOME\bin\gurobi_cl.exe" + ) + foreach ($file in $required_files) { + if (-not (Test-Path "$file")) { + throw "Required Gurobi file not found: $file" + } + } + + Add-Content -Path $ENV:GITHUB_PATH -Value "$ENV:GUROBI_HOME\bin" + + - name: Check Gurobi license + if: ${{ env.GUROBI_URL != 0 }} + run: | + $license_file = Join-Path $ENV:USERPROFILE "gurobi.lic" + curl.exe --fail --location --silent --show-error --output "$license_file" "$ENV:GUROBI_LICENSE_URL" + if ($LASTEXITCODE -ne 0) { + throw "Failed to download the Gurobi license." + } + Add-Content -Path $ENV:GITHUB_ENV -Value "GRB_LICENSE_FILE=$license_file" + $ENV:GRB_LICENSE_FILE = $license_file + + & "$ENV:GUROBI_HOME\bin\gurobi_cl.exe" --license *> $null + if ($LASTEXITCODE -ne 0) { + Write-Host "Gurobi license is invalid or expired. Please contact gustavo.delazeri@unibas.ch" + exit 1 + } + Write-Host "Gurobi license check passed." - name: Install zlib if: ${{ env.CPLEX_URL != 0 }} @@ -122,4 +172,12 @@ jobs: cd misc/ tox -e cplex + - name: Run Gurobi tests + shell: cmd + if: ${{ env.GUROBI_URL != 0 }} + run: | + call "${{ matrix.platform.vc }}" %ARCH% + cd misc/ + tox -e gurobi + ... From 221579f38dcb4fcc3e76a73c034c13ad9920f8c5 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 13:50:25 +0200 Subject: [PATCH 23/35] make windows CI workflow run in this branch --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 483926b3a2..0a7a6699a3 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -3,7 +3,7 @@ name: Windows on: push: - branches: [main, release-*] + branches: [main, release-*, issue1199-part2-algs-rebased] pull_request: branches: [main, release-*] From c9c5c58d00d03182902f5efd002c2a0484b2df6e Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 14:24:17 +0200 Subject: [PATCH 24/35] add vibe-coded soplex workflow on windows --- .github/workflows/windows.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0a7a6699a3..60bce8ffbc 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -18,6 +18,7 @@ env: GUROBI_URL: "${{ secrets.GUROBI1302_WINDOWS_URL }}" GUROBI_LICENSE_URL: "${{ secrets.GUROBI_LICENSE_URL }}" GUROBI_HOME: C:\gurobi1302\win64 + soplex_DIR: D:\a\soplex-7.1.6 ZLIB_URL: "https://www.zlib.net/zlib132.zip" @@ -139,6 +140,20 @@ jobs: echo "Copy the relevant directory to a location which is not magically protected against CMake" Xcopy /E /I D:\a\cplex_temp\cplex $ENV:cplex_DIR + - name: Install SoPlex + shell: cmd + run: | + call "${{ matrix.platform.vc }}" %ARCH% + git clone --branch release-716 --depth 1 https://github.com/scipopt/soplex.git || exit /b 1 + cmake -S soplex -B soplex-build -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DBOOST=off -DGMP=off -DZLIB=off || exit /b 1 + cmake --build soplex-build || exit /b 1 + cmake --install soplex-build --prefix "%soplex_DIR%" || exit /b 1 + + if not exist "%soplex_DIR%\include\soplex.h" ( + echo ERROR: SoPlex headers were not installed. + exit /b 1 + ) + echo %soplex_DIR%\bin>>"%GITHUB_PATH%" - name: Compile planner shell: cmd @@ -180,4 +195,11 @@ jobs: cd misc/ tox -e gurobi + - name: Run SoPlex tests + shell: cmd + run: | + call "${{ matrix.platform.vc }}" %ARCH% + cd misc/ + tox -e soplex + ... From 63000c067b5c84e7b2b80483a72da39182e9a75f Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 14:55:49 +0200 Subject: [PATCH 25/35] fix soplex workflow on windows --- .github/workflows/windows.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 60bce8ffbc..31241fe696 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -157,9 +157,10 @@ jobs: - name: Compile planner shell: cmd + # We set SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING because SoPlex 7.1.6 uses some deprecated Microsoft C++ extensions. run: | call "${{ matrix.platform.vc }}" %ARCH% - set CXXFLAGS=/WX + set CXXFLAGS=/WX /D_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING python build.py release python build.py debug From dfeb2682b5647708ee83f71903024f436e3e2f50 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 15:28:33 +0200 Subject: [PATCH 26/35] update soplex interface to disable windows macro ERROR --- src/search/lp/soplex_solver_interface.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/search/lp/soplex_solver_interface.cc b/src/search/lp/soplex_solver_interface.cc index e420372666..e28643c5f6 100644 --- a/src/search/lp/soplex_solver_interface.cc +++ b/src/search/lp/soplex_solver_interface.cc @@ -6,6 +6,11 @@ #include +// Windows headers define ERROR as a macro, which conflicts with SoPlex's enum SPxSolverBase::Status defined in spxsolver.h +#ifdef ERROR +#undef ERROR +#endif + using namespace std; using namespace soplex; From e48b35c4481dd7045da9ee7b739e77ab512308df Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 15:53:31 +0200 Subject: [PATCH 27/35] add commands to build a debug version of soplex when fast downward is built in debug mode --- .github/workflows/windows.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 31241fe696..d4bd8e853e 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -19,6 +19,7 @@ env: GUROBI_LICENSE_URL: "${{ secrets.GUROBI_LICENSE_URL }}" GUROBI_HOME: C:\gurobi1302\win64 soplex_DIR: D:\a\soplex-7.1.6 + soplex_DEBUG_DIR: D:\a\soplex-7.1.6-debug ZLIB_URL: "https://www.zlib.net/zlib132.zip" @@ -145,12 +146,20 @@ jobs: run: | call "${{ matrix.platform.vc }}" %ARCH% git clone --branch release-716 --depth 1 https://github.com/scipopt/soplex.git || exit /b 1 - cmake -S soplex -B soplex-build -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DBOOST=off -DGMP=off -DZLIB=off || exit /b 1 - cmake --build soplex-build || exit /b 1 - cmake --install soplex-build --prefix "%soplex_DIR%" || exit /b 1 + cmake -S soplex -B soplex-build-release -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DBOOST=off -DGMP=off -DZLIB=off || exit /b 1 + cmake --build soplex-build-release || exit /b 1 + cmake --install soplex-build-release --prefix "%soplex_DIR%" || exit /b 1 + + cmake -S soplex -B soplex-build-debug -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Debug -DBOOST=off -DGMP=off -DZLIB=off || exit /b 1 + cmake --build soplex-build-debug || exit /b 1 + cmake --install soplex-build-debug --prefix "%soplex_DEBUG_DIR%" || exit /b 1 if not exist "%soplex_DIR%\include\soplex.h" ( - echo ERROR: SoPlex headers were not installed. + echo ERROR: SoPlex release headers were not installed. + exit /b 1 + ) + if not exist "%soplex_DEBUG_DIR%\include\soplex.h" ( + echo ERROR: SoPlex debug headers were not installed. exit /b 1 ) echo %soplex_DIR%\bin>>"%GITHUB_PATH%" @@ -162,6 +171,7 @@ jobs: call "${{ matrix.platform.vc }}" %ARCH% set CXXFLAGS=/WX /D_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING python build.py release + set soplex_DIR=%soplex_DEBUG_DIR% python build.py debug - name: Install tox From 168e19a02734a42463b859685776fbde7cc8222b Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 15:54:24 +0200 Subject: [PATCH 28/35] disable CI for ubuntu and macos in this branch --- .github/workflows/mac.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index be4e87ad57..56e8fb22d6 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -3,7 +3,7 @@ name: macOS on: push: - branches: [main, release-*, issue1199-part2-algs-rebased] + branches: [main, release-*] pull_request: branches: [main, release-*] diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 6688ff6d37..7998206a9d 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -3,7 +3,7 @@ name: Ubuntu on: push: - branches: [main, release-*, issue1199-part2-algs-rebased] + branches: [main, release-*] pull_request: branches: [main, release-*] From 93b0704f5603892dfcc22f8777fbdeb4602a2c03 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 17:17:05 +0200 Subject: [PATCH 29/35] add macos CI workflow for cplex --- .github/workflows/mac.yml | 42 ++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 56e8fb22d6..954cbcd5f3 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -3,7 +3,7 @@ name: macOS on: push: - branches: [main, release-*] + branches: [main, release-*, issue1199-part2-algs-rebased] pull_request: branches: [main, release-*] @@ -22,10 +22,13 @@ jobs: GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} GRB_LICENSE_FILE: /Users/runner/gurobi.lic GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 - DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib + DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib:/Applications/CPLEX_Studio2220/cplex/bin/arm64_osx soplex_DIR: /Users/runner/lib/soplex-7.1.6 SOPLEX_LIB: /Users/runner/lib/soplex-7.1.6/lib/ SOPLEX_INCLUDE: /Users/runner/lib/soplex-7.1.6/include/ + CPLEX_URL: ${{ secrets.CPLEX2220_OSX_URL }} + cplex_DIR: /Applications/CPLEX_Studio2220/cplex + CPLEX_LIB: /Applications/CPLEX_Studio2220/cplex/bin/arm64_osx/libcplex2220.dylib steps: - name: Clone repository uses: actions/checkout@v3 @@ -72,6 +75,15 @@ jobs: cmake --install build --prefix "${soplex_DIR}" rm -rf soplex build + - name: Install CPLEX + if: ${{ env.CPLEX_URL != 0 }} + run: | + wget -O "$RUNNER_TEMP/cplex.pkg" "$CPLEX_URL" &> /dev/null + sudo installer -pkg "$RUNNER_TEMP/cplex.pkg" -target / + rm "$RUNNER_TEMP/cplex.pkg" + test -f "${cplex_DIR}/include/ilcplex/cplex.h" + test -f "$CPLEX_LIB" + - name: Compile planner run: | gmp_prefix="$(brew --prefix gmp)" @@ -83,11 +95,15 @@ jobs: - name: Archive required files if: ${{ matrix.version.run_tox_tests }} run: | - files_to_archive="fast-downward.py driver misc src builds/debug/bin/ \ - builds/release/bin/ ${SOPLEX_LIB} ${SOPLEX_INCLUDE}" + files_to_archive="fast-downward.py driver misc src builds/debug/bin/ builds/release/bin/ ${SOPLEX_LIB} ${SOPLEX_INCLUDE}" if [[ -n "${GUROBI_URL}" ]]; then files_to_archive="${files_to_archive} ${GUROBI_HOME}/lib" fi + if [[ -n "${CPLEX_URL}" ]]; then + mkdir -p "$HOME/lib/cplex2220" + cp "$CPLEX_LIB" "$HOME/lib/cplex2220/" + files_to_archive="${files_to_archive} $HOME/lib/cplex2220/libcplex2220.dylib" + fi tar czf archive.tar.gz -C "$HOME" $(grealpath --relative-to="$HOME" $files_to_archive) - name: Upload archive @@ -112,7 +128,10 @@ jobs: GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} GRB_LICENSE_FILE: /Users/runner/gurobi.lic GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 - DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib + DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib:/Applications/CPLEX_Studio2220/cplex/bin/arm64_osx + CPLEX_URL: ${{ secrets.CPLEX2220_OSX_URL }} + CPLEX_LIB: /Applications/CPLEX_Studio2220/cplex/bin/arm64_osx/libcplex2220.dylib + CPLEX_ARCHIVE_LIB: /Users/runner/lib/cplex2220/libcplex2220.dylib steps: - name: Download archive uses: actions/download-artifact@v4.1.8 @@ -154,6 +173,13 @@ jobs: run: | tar xfz archive.tar.gz -C "$HOME" + - name: Restore CPLEX library path + if: ${{ env.CPLEX_URL != 0 }} + run: | + sudo mkdir -p "$(dirname "$CPLEX_LIB")" + sudo cp "$CPLEX_ARCHIVE_LIB" "$CPLEX_LIB" + test -f "$CPLEX_LIB" + # Fast Downward's binary records Gurobi's installation path under /Library/gurobi1302/macos_universal2 # This step copies the archived Gurobi to the right place - name: Restore Gurobi library path @@ -168,6 +194,12 @@ jobs: cd misc/ tox -e driver,translator,search,parameters,generate-docs + - name: Run CPLEX tests + if: ${{ env.CPLEX_URL != 0 }} + run: | + cd misc/ + tox -e cplex + - name: Run Gurobi tests if: ${{ env.GUROBI_URL != 0 }} run: | From b16e7a7bae66bf6de4c3168c8cb7044384bc35ce Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Fri, 7 Aug 2026 17:55:54 +0200 Subject: [PATCH 30/35] fix cplex paths on macos ci workflow --- .github/workflows/mac.yml | 10 +++++----- .github/workflows/windows.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 954cbcd5f3..0daefb2bc3 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -22,13 +22,13 @@ jobs: GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} GRB_LICENSE_FILE: /Users/runner/gurobi.lic GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 - DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib:/Applications/CPLEX_Studio2220/cplex/bin/arm64_osx + DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib:/Applications/CPLEX_Studio222/cplex/bin/arm64_osx soplex_DIR: /Users/runner/lib/soplex-7.1.6 SOPLEX_LIB: /Users/runner/lib/soplex-7.1.6/lib/ SOPLEX_INCLUDE: /Users/runner/lib/soplex-7.1.6/include/ CPLEX_URL: ${{ secrets.CPLEX2220_OSX_URL }} - cplex_DIR: /Applications/CPLEX_Studio2220/cplex - CPLEX_LIB: /Applications/CPLEX_Studio2220/cplex/bin/arm64_osx/libcplex2220.dylib + cplex_DIR: /Applications/CPLEX_Studio222/cplex + CPLEX_LIB: /Applications/CPLEX_Studio222/cplex/bin/arm64_osx/libcplex2220.dylib steps: - name: Clone repository uses: actions/checkout@v3 @@ -128,9 +128,9 @@ jobs: GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} GRB_LICENSE_FILE: /Users/runner/gurobi.lic GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 - DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib:/Applications/CPLEX_Studio2220/cplex/bin/arm64_osx + DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib:/Applications/CPLEX_Studio222/cplex/bin/arm64_osx CPLEX_URL: ${{ secrets.CPLEX2220_OSX_URL }} - CPLEX_LIB: /Applications/CPLEX_Studio2220/cplex/bin/arm64_osx/libcplex2220.dylib + CPLEX_LIB: /Applications/CPLEX_Studio222/cplex/bin/arm64_osx/libcplex2220.dylib CPLEX_ARCHIVE_LIB: /Users/runner/lib/cplex2220/libcplex2220.dylib steps: - name: Download archive diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d4bd8e853e..c66ecf05eb 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -3,7 +3,7 @@ name: Windows on: push: - branches: [main, release-*, issue1199-part2-algs-rebased] + branches: [main, release-*] pull_request: branches: [main, release-*] From e054495a6209539325fc4356ac8b33343d900d45 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Sun, 9 Aug 2026 21:26:08 +0200 Subject: [PATCH 31/35] fix cplex path on macos --- .github/workflows/mac.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 0daefb2bc3..55475d64ac 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -128,9 +128,8 @@ jobs: GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} GRB_LICENSE_FILE: /Users/runner/gurobi.lic GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 - DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib:/Applications/CPLEX_Studio222/cplex/bin/arm64_osx + DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib:/Users/runner/lib/cplex2220 CPLEX_URL: ${{ secrets.CPLEX2220_OSX_URL }} - CPLEX_LIB: /Applications/CPLEX_Studio222/cplex/bin/arm64_osx/libcplex2220.dylib CPLEX_ARCHIVE_LIB: /Users/runner/lib/cplex2220/libcplex2220.dylib steps: - name: Download archive @@ -173,12 +172,10 @@ jobs: run: | tar xfz archive.tar.gz -C "$HOME" - - name: Restore CPLEX library path + - name: Check CPLEX library if: ${{ env.CPLEX_URL != 0 }} run: | - sudo mkdir -p "$(dirname "$CPLEX_LIB")" - sudo cp "$CPLEX_ARCHIVE_LIB" "$CPLEX_LIB" - test -f "$CPLEX_LIB" + test -r "$CPLEX_ARCHIVE_LIB" # Fast Downward's binary records Gurobi's installation path under /Library/gurobi1302/macos_universal2 # This step copies the archived Gurobi to the right place From 2ce9857dbb766fb84b19d6619f6f7f9e7fc39cc1 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Sun, 9 Aug 2026 21:51:35 +0200 Subject: [PATCH 32/35] add restore step to cplex workflow --- .github/workflows/mac.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 55475d64ac..68d3b6a920 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -128,8 +128,9 @@ jobs: GUROBI_LICENSE_URL: ${{ secrets.GUROBI_LICENSE_URL }} GRB_LICENSE_FILE: /Users/runner/gurobi.lic GUROBI_HOME: /Users/runner/lib/gurobi1302/macos_universal2 - DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib:/Users/runner/lib/cplex2220 + DYLD_LIBRARY_PATH: /Users/runner/lib/gurobi1302/macos_universal2/lib:/Applications/CPLEX_Studio222/cplex/bin/arm64_osx CPLEX_URL: ${{ secrets.CPLEX2220_OSX_URL }} + CPLEX_LIB: /Applications/CPLEX_Studio222/cplex/bin/arm64_osx/libcplex2220.dylib CPLEX_ARCHIVE_LIB: /Users/runner/lib/cplex2220/libcplex2220.dylib steps: - name: Download archive @@ -172,13 +173,9 @@ jobs: run: | tar xfz archive.tar.gz -C "$HOME" - - name: Check CPLEX library - if: ${{ env.CPLEX_URL != 0 }} - run: | - test -r "$CPLEX_ARCHIVE_LIB" - # Fast Downward's binary records Gurobi's installation path under /Library/gurobi1302/macos_universal2 # This step copies the archived Gurobi to the right place + - name: Restore Gurobi library path if: ${{ env.GUROBI_URL != 0 }} run: | @@ -186,6 +183,15 @@ jobs: sudo cp -R "$GUROBI_HOME" /Library/gurobi1302/ test -f /Library/gurobi1302/macos_universal2/lib/libgurobi130.dylib + # Ditto for cplex + - name: Restore CPLEX library path + if: ${{ env.CPLEX_URL != 0 }} + run: | + sudo mkdir -p "$(dirname "$CPLEX_LIB")" + sudo cp "$CPLEX_ARCHIVE_LIB" "$CPLEX_LIB" + sudo chmod 755 "$CPLEX_LIB" + test -r "$CPLEX_LIB" + - name: Run driver, translator and search tests run: | cd misc/ From 479edd3721c473d842500958b2b47fa5f54cd752 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Sun, 9 Aug 2026 22:11:48 +0200 Subject: [PATCH 33/35] update style --- src/search/lp/gurobi_solver_interface.cc | 3 ++- src/search/lp/gurobi_solver_interface.h | 3 ++- src/search/lp/soplex_solver_interface.cc | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/search/lp/gurobi_solver_interface.cc b/src/search/lp/gurobi_solver_interface.cc index 842939079c..bae4592e6d 100644 --- a/src/search/lp/gurobi_solver_interface.cc +++ b/src/search/lp/gurobi_solver_interface.cc @@ -253,7 +253,8 @@ void GurobiSolverInterface::set_constraint_rhs( right_hand_side); } -void GurobiSolverInterface::set_constraint_sense(int index, LPConstraintSense sense) { +void GurobiSolverInterface::set_constraint_sense( + int index, LPConstraintSense sense) { assert(model); assert(index >= 0 && index < get_num_constraints()); diff --git a/src/search/lp/gurobi_solver_interface.h b/src/search/lp/gurobi_solver_interface.h index 7641c728a2..fc03fa9548 100644 --- a/src/search/lp/gurobi_solver_interface.h +++ b/src/search/lp/gurobi_solver_interface.h @@ -28,7 +28,8 @@ class GurobiSolverInterface : public SolverInterface { virtual void set_objective_coefficient( int index, double coefficient) override; virtual void set_constraint_rhs(int index, double right_hand_side) override; - virtual void set_constraint_sense(int index, LPConstraintSense sense) override; + virtual void set_constraint_sense( + int index, LPConstraintSense sense) override; virtual void set_variable_lower_bound(int index, double bound) override; virtual void set_variable_upper_bound(int index, double bound) override; diff --git a/src/search/lp/soplex_solver_interface.cc b/src/search/lp/soplex_solver_interface.cc index e28643c5f6..5d4024743f 100644 --- a/src/search/lp/soplex_solver_interface.cc +++ b/src/search/lp/soplex_solver_interface.cc @@ -6,7 +6,8 @@ #include -// Windows headers define ERROR as a macro, which conflicts with SoPlex's enum SPxSolverBase::Status defined in spxsolver.h +// Windows headers define ERROR as a macro, which conflicts with SoPlex's enum +// SPxSolverBase::Status defined in spxsolver.h #ifdef ERROR #undef ERROR #endif From f0b2ea21331355b01783ae506c537d55bb6b3633 Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Sun, 9 Aug 2026 22:57:44 +0200 Subject: [PATCH 34/35] update documentation; add gurobi header to ignore list; fix cmake --- BUILD.md | 23 ++++++++++++----------- misc/style/run-clang-tidy.py | 1 + src/search/CMakeLists.txt | 4 ++-- src/search/cmake/FindGurobi.cmake | 2 +- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/BUILD.md b/BUILD.md index 2fa77aaf1b..f5c2af1f0e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -16,7 +16,7 @@ During the installation of Visual Studio, the C++ compiler is not installed by d ### Optional: Linear-Programming Solvers -Some planner configurations depend on an LP or MIP solver. We support CPLEX (commercial, [free academic license](http://ibm.com/academic)), Gurobi (commercial, [free academic license](https://www.gurobi.com/academics#licenses)), and SoPlex (Apache License, no MIP support). You can install one or both solvers without causing conflicts. +Some planner configurations depend on an LP or MIP solver. We support CPLEX (commercial, [free academic license](http://ibm.com/academic)), Gurobi (commercial, [free academic license](https://www.gurobi.com/academics#licenses)), and SoPlex (Apache License, no MIP support). You can install one or more solvers without causing conflicts. Once LP solvers are installed and the appropriate environment variables are set up correctly, Fast Downward automatically includes each solver detected on the system in the build. @@ -32,19 +32,20 @@ export cplex_DIR=/opt/ibm/ILOG/CPLEX_Studio2211/cplex ``` Note that on Windows, setting up the environment variable might require using `/` instead of the more Windows-common `\`. -#### Installing Gurobi on Ubuntu +#### Installing Gurobi -We currently only support Gurobi 13.0.x. After downloading from the website and obtaining a license, you should have a directory called `gurobi130x`, where `x` depends on your patch. Set the following environment variables for Ubuntu and OSX. Instructions for Windows will follow soon. -- `GUROBI_HOME` to the folder where the binaries and headers are located. -- `GRB_LICENSE_FILE` to the path where the Gurobi license file is located. -- `LD_LIBRARY_PATH` to where the Gurobi precompiled libraries are located. +We currently support Gurobi 13.0.x. Download and install the appropriate Gurobi package for your operating system and obtain a license. -For example, on Ubuntu, assuming that Gurobi 13.0.2 was installed under `/opt` and the license is at `/home/myuser`: +Set `GUROBI_HOME` to the directory containing Gurobi's `include`, `lib`, and `bin` directories. Set `GRB_LICENSE_FILE` to the location of your Gurobi license file. For example, `GUROBI_HOME` could be `/opt/gurobi130x/linux64` on Ubuntu, `/Library/gurobi130x/macos_universal2` on macOS, or `C:\gurobi130x\win64` on Windows. -```bash -export GUROBI_HOME="/opt/gurobi1302/linux64" -export LD_LIBRARY_PATH="${GUROBI_HOME}/lib:$LD_LIBRARY_PATH" -export GRB_LICENSE_FILE="/home/myuser/gurobi.lic" +On Ubuntu, add Gurobi's `lib` directory to `LD_LIBRARY_PATH`. On macOS, add it to `DYLD_LIBRARY_PATH`. On Windows, add Gurobi's `bin` directory to `PATH` so that `gurobi130.dll` can be found when running Fast Downward. + +For example, assuming that Gurobi 13.0.2 was installed in its default location and the license is stored in the user's home directory, run the following commands in PowerShell: + +```powershell +$env:GUROBI_HOME = "C:\gurobi1302\win64" +$env:GRB_LICENSE_FILE = "$env:USERPROFILE\gurobi.lic" +$env:PATH = "$env:GUROBI_HOME\bin;$env:PATH" ``` #### Installing SoPlex on Linux/macOS diff --git a/misc/style/run-clang-tidy.py b/misc/style/run-clang-tidy.py index 6a0fc95f28..abfefe70cf 100755 --- a/misc/style/run-clang-tidy.py +++ b/misc/style/run-clang-tidy.py @@ -19,6 +19,7 @@ IGNORES = [ "'cplex.h' file not found [clang-diagnostic-error]", "'soplex.h' file not found [clang-diagnostic-error]", + "'gurobi_c.h' file not found [clang-diagnostic-error]", "'git_revision.h' file not found [clang-diagnostic-error]", "local copy 'copied_key' of the variable 'key' is never modified; consider avoiding the copy [performance-unnecessary-copy-initialization]", ] diff --git a/src/search/CMakeLists.txt b/src/search/CMakeLists.txt index f11a68a43a..bc4961d776 100644 --- a/src/search/CMakeLists.txt +++ b/src/search/CMakeLists.txt @@ -601,7 +601,7 @@ create_fast_downward_library( if(USE_LP) find_package(Cplex 12) if(CPLEX_FOUND) - message(STATUS "Found CPLEX: ${CPLEX_DIR}") + message(STATUS "Found CPLEX: ${cplex_DIR}") target_compile_definitions(lp_solver INTERFACE HAS_CPLEX) target_link_libraries(lp_solver INTERFACE cplex::cplex) target_sources(lp_solver INTERFACE lp/cplex_solver_interface.h lp/cplex_solver_interface.cc) @@ -610,7 +610,7 @@ if(USE_LP) find_package(soplex 7.1.0 QUIET) if (SOPLEX_FOUND) - message(STATUS "Found SoPlex: ${SOPLEX_DIR}") + message(STATUS "Found SoPlex: ${soplex_DIR}") target_link_libraries(lp_solver INTERFACE libsoplex) target_compile_definitions(lp_solver INTERFACE HAS_SOPLEX) target_sources(lp_solver INTERFACE lp/soplex_solver_interface.h lp/soplex_solver_interface.cc) diff --git a/src/search/cmake/FindGurobi.cmake b/src/search/cmake/FindGurobi.cmake index b1cad55de5..e0ed0343e0 100644 --- a/src/search/cmake/FindGurobi.cmake +++ b/src/search/cmake/FindGurobi.cmake @@ -22,7 +22,7 @@ find_library(GUROBI_LIBRARY NAMES gurobi130 # Gurobi 13.0 HINTS ${HINT_PATHS} - PATH_SUFFIXES lib + PATH_SUFFIXES lib ) # Check if everything was found and set Gurobi_FOUND. From c36912a7a6085381aee9b25178a9bfc6808bf5bd Mon Sep 17 00:00:00 2001 From: Gustavo Delazeri Date: Mon, 10 Aug 2026 09:23:05 +0200 Subject: [PATCH 35/35] remove working branch from macOS CI tests --- .github/workflows/mac.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 68d3b6a920..0521a488bf 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -3,7 +3,7 @@ name: macOS on: push: - branches: [main, release-*, issue1199-part2-algs-rebased] + branches: [main, release-*] pull_request: branches: [main, release-*]