Skip to content

Commit

Permalink
fixes farkas certificate and extreme ray for HiGHS
Browse files Browse the repository at this point in the history
  • Loading branch information
hlefebvr committed Oct 22, 2023
1 parent 05e3318 commit 611ea60
Show file tree
Hide file tree
Showing 13 changed files with 495 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [ "*", "!gh-pages" ]
pull_request:
branches: [ "main", "dev" ]
branches: [ "main" ]

env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ endif()

if (${BUILD_TESTS})
add_subdirectory(tests)
add_subdirectory(unit-tests)
elseif(${TEST_COVERAGE})
message(FATAL "Must have BUILD_TESTS=YES in order to build code coverage")
endif()
Expand Down
4 changes: 3 additions & 1 deletion dev/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
add_executable(dev main.cpp)
add_executable(dev main.cpp
../unit-tests/external-solvers/all/toy_milp.cpp
)
target_link_libraries(dev PRIVATE idol)
target_include_directories(dev PRIVATE ../lib/include)
6 changes: 6 additions & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ if (${USE_GUROBI})
find_package(GUROBI REQUIRED)
target_link_libraries(idol PUBLIC gurobi)
target_compile_definitions(idol PUBLIC IDOL_USE_GUROBI)
list(APPEND AVAILABLE_MILP_SOLVERS "Gurobi")

endif()

Expand All @@ -65,6 +66,7 @@ if (${USE_MOSEK})
find_package(MOSEK REQUIRED)
target_link_libraries(idol PUBLIC mosek)
target_compile_definitions(idol PUBLIC IDOL_USE_MOSEK)
list(APPEND AVAILABLE_MILP_SOLVERS "Mosek")

endif()

Expand All @@ -73,6 +75,7 @@ if (${USE_GLPK})
find_package(GLPK REQUIRED)
target_link_libraries(idol PUBLIC glpk)
target_compile_definitions(idol PUBLIC IDOL_USE_GLPK)
list(APPEND AVAILABLE_MILP_SOLVERS "GLPK")

endif()

Expand All @@ -82,9 +85,12 @@ if (${USE_HIGHS})
find_package(HIGHS REQUIRED)
target_link_libraries(idol PUBLIC highs::highs)
target_compile_definitions(idol PUBLIC IDOL_USE_HIGHS)
list(APPEND AVAILABLE_MILP_SOLVERS "HiGHS")

endif()

set(AVAILABLE_MILP_SOLVERS "${AVAILABLE_MILP_SOLVERS}" PARENT_SCOPE)

target_include_directories(
idol
PUBLIC
Expand Down
4 changes: 4 additions & 0 deletions lib/include/idol/optimizers/solvers/Optimizers_HiGHS.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class idol::Optimizers::HiGHS : public OptimizerWithLazyUpdates<int, int> {

SolutionStatus m_solution_status = Loaded;
SolutionReason m_solution_reason = NotSpecified;
double* m_extreme_ray = nullptr;
double* m_farkas_certificate = nullptr;

std::stack<int> m_deleted_variables;
std::stack<int> m_deleted_constraints;
Expand All @@ -31,6 +33,8 @@ class idol::Optimizers::HiGHS : public OptimizerWithLazyUpdates<int, int> {

void hook_optimize() override;

void run_without_presolve();

void hook_write(const std::string &t_name) override;

int hook_add(const Var &t_var, bool t_add_column) override;
Expand Down
62 changes: 59 additions & 3 deletions lib/src/optimizers/solvers/Optimizers_HiGHS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,13 @@ void idol::Optimizers::HiGHS::hook_remove(const Ctr &t_ctr) {
}

void idol::Optimizers::HiGHS::hook_optimize() {

delete m_extreme_ray;
m_extreme_ray = nullptr;

delete m_farkas_certificate;
m_farkas_certificate = nullptr;

m_model.run();

const auto status = m_model.getModelStatus();
Expand Down Expand Up @@ -254,9 +261,48 @@ void idol::Optimizers::HiGHS::hook_optimize() {
case HighsModelStatus::kSolutionLimit:
case HighsModelStatus::kInterrupt:
throw Exception("Unhandled status: " + std::to_string((int) status));
break;
}

if (get_param_infeasible_or_unbounded_info() && m_solution_status == Unbounded) {

bool has_primal_ray;
m_extreme_ray = new double[m_model.getNumCol()];
m_model.getPrimalRay(has_primal_ray, m_extreme_ray);

if (!has_primal_ray) {
run_without_presolve();
m_model.getPrimalRay(has_primal_ray, m_extreme_ray);
if (!has_primal_ray) {
throw Exception("Cannot save primal ray.");
}
}

}

if (get_param_infeasible_or_unbounded_info() && m_solution_status == Infeasible) {

bool has_dual_ray;
m_farkas_certificate = new double[m_model.getNumRow()];
m_model.getDualRay(has_dual_ray, m_farkas_certificate);

if (!has_dual_ray) {
run_without_presolve();
m_model.getDualRay(has_dual_ray, m_farkas_certificate);
if (!has_dual_ray) {
throw Exception("Cannot save dual ray.");
}
}

}

}

void idol::Optimizers::HiGHS::run_without_presolve() {
const std::string old_presolve_setting;
m_model.getStringOptionValues("presolve");
m_model.setOptionValue("presolve", "off");
m_model.run();
m_model.setOptionValue("presolve", old_presolve_setting);
}

void idol::Optimizers::HiGHS::set_param_time_limit(double t_time_limit) {
Expand Down Expand Up @@ -309,15 +355,25 @@ double idol::Optimizers::HiGHS::get_var_primal(const Var &t_var) const {
}

double idol::Optimizers::HiGHS::get_var_ray(const Var &t_var) const {
return get_var_primal(t_var);

if (!m_extreme_ray) {
throw Exception("Extreme ray is not available.");
}

return m_extreme_ray[lazy(t_var).impl()];
}

double idol::Optimizers::HiGHS::get_ctr_dual(const Ctr &t_ctr) const {
return m_model.getSolution().row_dual[lazy(t_ctr).impl()];
}

double idol::Optimizers::HiGHS::get_ctr_farkas(const Ctr &t_ctr) const {
return get_ctr_farkas(t_ctr);

if (!m_farkas_certificate) {
throw Exception("Farkas certificate not available.");
}

return m_farkas_certificate[lazy(t_ctr).impl()];
}

double idol::Optimizers::HiGHS::get_relative_gap() const {
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/LP_Toy.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include "../test_utils.h"
#include "idol/optimizers/solvers/DefaultOptimizer.h"

TEMPLATE_LIST_TEST_CASE("LP solvers: solve toy example",
TEMPLATE_LIST_TEST_CASE("LP external-solvers: solve toy example",
"[integration][backend][solver]",
lp_solvers) {

Expand Down
2 changes: 1 addition & 1 deletion tests/integration/MILP_Toy.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#include "idol/optimizers/branch-and-bound/node-selection-rules/factories/BestBound.h"
#include "idol/optimizers/branch-and-bound/branching-rules/factories/MostInfeasible.h"

TEMPLATE_LIST_TEST_CASE("MILP solvers: solve toy example",
TEMPLATE_LIST_TEST_CASE("MILP external-solvers: solve toy example",
"[integration][backend][solver]",
milp_solvers) {

Expand Down
32 changes: 32 additions & 0 deletions unit-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Configure test environment

enable_testing()

if (${WITH_TEST_COVERAGE})
target_compile_options(idol PRIVATE -fprofile-arcs -ftest-coverage -g -O0 --coverage)
set(CMAKE_C_COMPILER gcc)
set(CMAKE_CPP_COMPILER g++)
target_link_libraries(idol PUBLIC gcov)
endif()

find_package(Catch2 QUIET)

if (NOT ${Catch2_FOUND})

Include(FetchContent)

set(FETCHCONTENT_QUIET FALSE)

FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.3.2 # or a later release
GIT_PROGRESS TRUE
)

FetchContent_MakeAvailable(Catch2)

endif()

# Define tests
add_subdirectory(external-solvers)
1 change: 1 addition & 0 deletions unit-tests/external-solvers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_subdirectory(all)
15 changes: 15 additions & 0 deletions unit-tests/external-solvers/all/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
message("${AVAILABLE_MILP_SOLVERS}")

foreach (OPTIMIZER ${AVAILABLE_MILP_SOLVERS})

set(target_name "test_external_solver_${OPTIMIZER}")

add_executable(${target_name}
toy_lp.cpp
toy_milp.cpp
)

target_compile_definitions(${target_name} PRIVATE OPTIMIZER=${OPTIMIZER})
target_link_libraries(${target_name} PRIVATE idol Catch2::Catch2WithMain)

endforeach ()
Loading

0 comments on commit 611ea60

Please sign in to comment.