From 7734184bc6eb418756059e4e3c91342e4817279c Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Thu, 14 Nov 2024 16:24:49 +0100 Subject: [PATCH 01/14] Draft idea for InitialSolution --- include/scippp/initial_solution.hpp | 21 +++++++++++++++++++++ include/scippp/model.hpp | 19 +++++++++++++++++++ source/initial_solution.cpp | 20 ++++++++++++++++++++ source/model.cpp | 25 +++++++++++++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 include/scippp/initial_solution.hpp create mode 100644 source/initial_solution.cpp diff --git a/include/scippp/initial_solution.hpp b/include/scippp/initial_solution.hpp new file mode 100644 index 00000000..57ebfcbb --- /dev/null +++ b/include/scippp/initial_solution.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +// forward declare +struct Scip; +struct SCIP_Var; + +namespace scippp { + +// forward declare +class Var; + +class InitialSolution { + //! Variable assignment in the initial solution. + std::map m_values {}; +public: + void setValue(const Var& var, double value); +}; + +} diff --git a/include/scippp/model.hpp b/include/scippp/model.hpp index eba6b179..eb571b2c 100644 --- a/include/scippp/model.hpp +++ b/include/scippp/model.hpp @@ -11,6 +11,7 @@ #include #include "scippp/constant_coefficient.hpp" +#include "scippp/initial_solution.hpp" #include "scippp/lin_expr.hpp" #include "scippp/lin_ineq.hpp" #include "scippp/param.hpp" @@ -326,5 +327,23 @@ class Model { That is only required for use-cases not supported by SCIP++. Consider adding the feature you are using to SCIP++!)")]] [[nodiscard]] Scip* scip() const; + + /** + * todo + * @since 1.3.0 + * @param printReason Should all reasons of violations be printed? + * @param completely Should all violations be checked if \p printReason is true? + * @param checkBounds Should the bounds of the variables be checked? + * @param checkIntegrality Should integrality be checked? + * @param checkLpRows Do constraints represented by rows in the current LP have to be checked? + * @return \c true iff the solution was feasible and stored in the solution storage (i.e, good enough to keep). + */ + bool addSolution( + const InitialSolution& initialSolution, + bool printReason = true, + bool completely = true, + bool checkBounds = true, + bool checkIntegrality = true, + bool checkLpRows = true); }; } diff --git a/source/initial_solution.cpp b/source/initial_solution.cpp new file mode 100644 index 00000000..5c38abe4 --- /dev/null +++ b/source/initial_solution.cpp @@ -0,0 +1,20 @@ +#include "scippp/initial_solution.hpp" + +#include + +namespace scippp { + +InitialSolution::InitialSolution(Scip* scip) + : m_scip { scip } +{ +} + +void InitialSolution::setValue(const Var& var, double value) +{ + // maybe add scip pointer to var and check this here + m_values[var.var] = value; +} + + + +} \ No newline at end of file diff --git a/source/model.cpp b/source/model.cpp index 4553f92f..fab3b2f7 100644 --- a/source/model.cpp +++ b/source/model.cpp @@ -150,4 +150,29 @@ double Model::getPrimalbound() const return SCIPgetPrimalbound(m_scip); } +bool Model::addSolution( + const InitialSolution& initialSolution, + bool printReason, + bool completely, + bool checkBounds, + bool checkIntegrality, + bool checkLpRows) +{ + SCIP_Sol* sol { nullptr }; + m_scipCallWrapper(SCIPcreateSol(m_scip, &sol, nullptr)); + for (const auto& [var, value] : initialSolution.m_values) { + m_scipCallWrapper(SCIPsetSolVal(m_scip, sol, var, value)); + } + SCIP_Bool isFeasible { false }; + m_scipCallWrapper(SCIPcheckSol( + m_scip, sol, printReason, completely, checkBounds, checkIntegrality, checkLpRows, &isFeasible)); + SCIP_Bool isStored { false }; + if (isFeasible) { + m_scipCallWrapper(SCIPaddSolFree(m_scip, &sol, &isStored)); + } else { + m_scipCallWrapper(SCIPfreeSol(m_scip, &sol)); + } + return isStored; +} + } From 5af4a5db9dcbbaef5659871681eecc18d7c8612e Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Tue, 19 Nov 2024 12:30:36 +0100 Subject: [PATCH 02/14] Finish draft of implementation --- include/scippp/initial_solution.hpp | 1 + source/initial_solution.cpp | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/include/scippp/initial_solution.hpp b/include/scippp/initial_solution.hpp index 57ebfcbb..dbf4f21d 100644 --- a/include/scippp/initial_solution.hpp +++ b/include/scippp/initial_solution.hpp @@ -12,6 +12,7 @@ namespace scippp { class Var; class InitialSolution { + friend class Model; //! Variable assignment in the initial solution. std::map m_values {}; public: diff --git a/source/initial_solution.cpp b/source/initial_solution.cpp index 5c38abe4..116f2f4c 100644 --- a/source/initial_solution.cpp +++ b/source/initial_solution.cpp @@ -4,17 +4,10 @@ namespace scippp { -InitialSolution::InitialSolution(Scip* scip) - : m_scip { scip } -{ -} - void InitialSolution::setValue(const Var& var, double value) { // maybe add scip pointer to var and check this here m_values[var.var] = value; } - - -} \ No newline at end of file +} From ab605246aceec357de6294ff0222a7d0ea6cb6cd Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Tue, 19 Nov 2024 12:30:49 +0100 Subject: [PATCH 03/14] Test implementation --- test/test_initial_solution.cpp | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/test_initial_solution.cpp diff --git a/test/test_initial_solution.cpp b/test/test_initial_solution.cpp new file mode 100644 index 00000000..cbce9977 --- /dev/null +++ b/test/test_initial_solution.cpp @@ -0,0 +1,35 @@ +#include + +#include + +#include "scippp/model.hpp" + +using namespace scippp; +using namespace std; +namespace bdata = boost::unit_test::data; + +BOOST_AUTO_TEST_SUITE(InitSolution) + +BOOST_DATA_TEST_CASE(InitialSolutionStored, bdata::make({ 1, 2, 3 }), indexSetToOne) +{ + Model model("Simple"); + auto x1 = model.addVar("x_1", 1, VarType::BINARY); + auto x2 = model.addVar("x_2", 1, VarType::BINARY); + auto x3 = model.addVar("x_3", 1, VarType::BINARY); + model.addConstr(x1 + x2 + x3 <= 1, "upperBound"); + model.setObjsense(Sense::MAXIMIZE); + + InitialSolution is; + is.setValue(x1, indexSetToOne == 1 ? 1 : 0); + is.setValue(x2, indexSetToOne == 2 ? 1 : 0); + is.setValue(x3, indexSetToOne == 3 ? 1 : 0); + BOOST_TEST(model.addSolution(is)); + + BOOST_REQUIRE(model.getNSols() > 0); + auto sol = model.getBestSol(); + BOOST_TEST(x1.getSolValAsInt(sol) == (indexSetToOne == 1 ? 1 : 0)); + BOOST_TEST(x2.getSolValAsInt(sol) == (indexSetToOne == 2 ? 1 : 0)); + BOOST_TEST(x3.getSolValAsInt(sol) == (indexSetToOne == 3 ? 1 : 0)); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 6ff708cbfe2f26b7893fe89c6bb914c562bf6a28 Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Tue, 19 Nov 2024 12:35:35 +0100 Subject: [PATCH 04/14] Fix formatting --- include/scippp/initial_solution.hpp | 1 + include/scippp/model.hpp | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/scippp/initial_solution.hpp b/include/scippp/initial_solution.hpp index dbf4f21d..4fb3e30a 100644 --- a/include/scippp/initial_solution.hpp +++ b/include/scippp/initial_solution.hpp @@ -15,6 +15,7 @@ class InitialSolution { friend class Model; //! Variable assignment in the initial solution. std::map m_values {}; + public: void setValue(const Var& var, double value); }; diff --git a/include/scippp/model.hpp b/include/scippp/model.hpp index eb571b2c..7ba91e7b 100644 --- a/include/scippp/model.hpp +++ b/include/scippp/model.hpp @@ -339,11 +339,11 @@ class Model { * @return \c true iff the solution was feasible and stored in the solution storage (i.e, good enough to keep). */ bool addSolution( - const InitialSolution& initialSolution, - bool printReason = true, - bool completely = true, - bool checkBounds = true, - bool checkIntegrality = true, - bool checkLpRows = true); + const InitialSolution& initialSolution, + bool printReason = true, + bool completely = true, + bool checkBounds = true, + bool checkIntegrality = true, + bool checkLpRows = true); }; } From 83984d186d411c0503da25d74768b796f6a278d0 Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Tue, 19 Nov 2024 12:42:11 +0100 Subject: [PATCH 05/14] Add documentation --- include/scippp/initial_solution.hpp | 12 +++++++++++- include/scippp/model.hpp | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/scippp/initial_solution.hpp b/include/scippp/initial_solution.hpp index 4fb3e30a..ffd3e066 100644 --- a/include/scippp/initial_solution.hpp +++ b/include/scippp/initial_solution.hpp @@ -3,7 +3,6 @@ #include // forward declare -struct Scip; struct SCIP_Var; namespace scippp { @@ -11,12 +10,23 @@ namespace scippp { // forward declare class Var; +/** + * A primal solution to be added to %SCIP's solution pool. + * + * @since 1.3.0 + */ class InitialSolution { friend class Model; //! Variable assignment in the initial solution. std::map m_values {}; public: + /** + * Sets the value for a variable in the solution. + * + * @param var Variable to assign a value to. + * @param value to assign to the variable. + */ void setValue(const Var& var, double value); }; diff --git a/include/scippp/model.hpp b/include/scippp/model.hpp index 7ba91e7b..37decfe7 100644 --- a/include/scippp/model.hpp +++ b/include/scippp/model.hpp @@ -329,7 +329,8 @@ class Model { scip() const; /** - * todo + * Adds a solution to %SCIP's solution pool. + * * @since 1.3.0 * @param printReason Should all reasons of violations be printed? * @param completely Should all violations be checked if \p printReason is true? From 3d5fae20b070616740f8a19a57e875f2cdd71a3a Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Tue, 19 Nov 2024 12:47:55 +0100 Subject: [PATCH 06/14] Add test case for infeasible solution --- test/test_initial_solution.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/test_initial_solution.cpp b/test/test_initial_solution.cpp index cbce9977..47daeb20 100644 --- a/test/test_initial_solution.cpp +++ b/test/test_initial_solution.cpp @@ -32,4 +32,14 @@ BOOST_DATA_TEST_CASE(InitialSolutionStored, bdata::make({ 1, 2, 3 }), indexSetTo BOOST_TEST(x3.getSolValAsInt(sol) == (indexSetToOne == 3 ? 1 : 0)); } +BOOST_AUTO_TEST_CASE(Infeasible) +{ + Model model("Simple"); + auto x1 = model.addVar("x_1", 1, VarType::BINARY); + model.setObjsense(Sense::MAXIMIZE); + InitialSolution is; + is.setValue(x1, 2); + BOOST_TEST(!model.addSolution(is)); +} + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 53ca5a93fef0850670fb21c0d263837f97eefaa2 Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Tue, 19 Nov 2024 12:49:24 +0100 Subject: [PATCH 07/14] Add missing docu --- include/scippp/model.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/scippp/model.hpp b/include/scippp/model.hpp index 37decfe7..cac360ea 100644 --- a/include/scippp/model.hpp +++ b/include/scippp/model.hpp @@ -332,6 +332,7 @@ class Model { * Adds a solution to %SCIP's solution pool. * * @since 1.3.0 + * @param initialSolution to add to the solution pool. * @param printReason Should all reasons of violations be printed? * @param completely Should all violations be checked if \p printReason is true? * @param checkBounds Should the bounds of the variables be checked? From b7cc7ecde4e00ea525068dae05a59ec27fb42b10 Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Tue, 19 Nov 2024 13:24:15 +0100 Subject: [PATCH 08/14] Fix eof --- test/test_initial_solution.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_initial_solution.cpp b/test/test_initial_solution.cpp index 47daeb20..3e82ff2c 100644 --- a/test/test_initial_solution.cpp +++ b/test/test_initial_solution.cpp @@ -42,4 +42,4 @@ BOOST_AUTO_TEST_CASE(Infeasible) BOOST_TEST(!model.addSolution(is)); } -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file +BOOST_AUTO_TEST_SUITE_END() From b153a9206561a3ea660b42f1d80d5e0307fdb385 Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Tue, 19 Nov 2024 13:27:01 +0100 Subject: [PATCH 09/14] Update changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 38ac2aca..15ee84f4 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Added +- [PR31](https://github.com/scipopt/SCIPpp/pull/31) Add `InitialSolution` and `Model::addSolution`. - [PR28](https://github.com/scipopt/SCIPpp/pull/28) Add `Var::getVar`. ## [1.2.0] - 2024-05-21 From ec0fbdf24691f3f0d41d1870055fa12a075d25e2 Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Wed, 20 Nov 2024 09:38:45 +0100 Subject: [PATCH 10/14] Add InitialSolution::operator() --- include/scippp/initial_solution.hpp | 12 ++++++++++++ source/initial_solution.cpp | 5 +++++ test/test_initial_solution.cpp | 12 ++++++++++++ 3 files changed, 29 insertions(+) diff --git a/include/scippp/initial_solution.hpp b/include/scippp/initial_solution.hpp index ffd3e066..61242bc0 100644 --- a/include/scippp/initial_solution.hpp +++ b/include/scippp/initial_solution.hpp @@ -24,10 +24,22 @@ class InitialSolution { /** * Sets the value for a variable in the solution. * + * @since 1.3.0 * @param var Variable to assign a value to. * @param value to assign to the variable. */ void setValue(const Var& var, double value); + + /** + * Access the mutable value assigned to the variable. + * + * Initializes the assigned value to 0 if no value was assigned to the variable so far. + * + * @since 1.3.0 + * @param var Variable to manipulate the value in the solution for. + * @return Mutable value assigned to the variable. + */ + double& operator()(const Var& var); }; } diff --git a/source/initial_solution.cpp b/source/initial_solution.cpp index 116f2f4c..7b1f71ed 100644 --- a/source/initial_solution.cpp +++ b/source/initial_solution.cpp @@ -10,4 +10,9 @@ void InitialSolution::setValue(const Var& var, double value) m_values[var.var] = value; } +double& InitialSolution::operator()(const Var& var) +{ + return m_values[var.var]; +} + } diff --git a/test/test_initial_solution.cpp b/test/test_initial_solution.cpp index 3e82ff2c..1342492f 100644 --- a/test/test_initial_solution.cpp +++ b/test/test_initial_solution.cpp @@ -42,4 +42,16 @@ BOOST_AUTO_TEST_CASE(Infeasible) BOOST_TEST(!model.addSolution(is)); } +BOOST_AUTO_TEST_CASE(UpdateSolution) +{ + Model model("Simple"); + const auto& [x1, x2] = model.addVars<2>("x_"); + InitialSolution is; + is(x1) = 4; + is(x1) += 38; + is(x2) += 42; + BOOST_TEST(is(x1) == 42); + BOOST_TEST(is(x2) == 42); +} + BOOST_AUTO_TEST_SUITE_END() From 3a4ef7b2c3af9e79f82a9d4577acb2bbd188f5d9 Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Thu, 9 Jan 2025 13:48:43 +0100 Subject: [PATCH 11/14] Don't use SCIP_Var in InitialSolution --- include/scippp/initial_solution.hpp | 5 +---- source/initial_solution.cpp | 6 ++---- source/model.cpp | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/include/scippp/initial_solution.hpp b/include/scippp/initial_solution.hpp index 61242bc0..451acf0c 100644 --- a/include/scippp/initial_solution.hpp +++ b/include/scippp/initial_solution.hpp @@ -2,9 +2,6 @@ #include -// forward declare -struct SCIP_Var; - namespace scippp { // forward declare @@ -18,7 +15,7 @@ class Var; class InitialSolution { friend class Model; //! Variable assignment in the initial solution. - std::map m_values {}; + std::map m_values {}; public: /** diff --git a/source/initial_solution.cpp b/source/initial_solution.cpp index 7b1f71ed..cc4551f7 100644 --- a/source/initial_solution.cpp +++ b/source/initial_solution.cpp @@ -1,18 +1,16 @@ #include "scippp/initial_solution.hpp" -#include - namespace scippp { void InitialSolution::setValue(const Var& var, double value) { // maybe add scip pointer to var and check this here - m_values[var.var] = value; + m_values[&var] = value; } double& InitialSolution::operator()(const Var& var) { - return m_values[var.var]; + return m_values[&var]; } } diff --git a/source/model.cpp b/source/model.cpp index fab3b2f7..84117cbd 100644 --- a/source/model.cpp +++ b/source/model.cpp @@ -161,7 +161,7 @@ bool Model::addSolution( SCIP_Sol* sol { nullptr }; m_scipCallWrapper(SCIPcreateSol(m_scip, &sol, nullptr)); for (const auto& [var, value] : initialSolution.m_values) { - m_scipCallWrapper(SCIPsetSolVal(m_scip, sol, var, value)); + m_scipCallWrapper(SCIPsetSolVal(m_scip, sol, var->getVar(), value)); } SCIP_Bool isFeasible { false }; m_scipCallWrapper(SCIPcheckSol( From c2d2acc2b91c1d1cf187103e43c269e5012db77a Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Thu, 9 Jan 2025 13:49:27 +0100 Subject: [PATCH 12/14] Remove comment that was only relevant when plain SCIP data structures were used --- source/initial_solution.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/source/initial_solution.cpp b/source/initial_solution.cpp index cc4551f7..1896a961 100644 --- a/source/initial_solution.cpp +++ b/source/initial_solution.cpp @@ -4,7 +4,6 @@ namespace scippp { void InitialSolution::setValue(const Var& var, double value) { - // maybe add scip pointer to var and check this here m_values[&var] = value; } From 1cc1cd1cad16764c09771f1d2e99c1fbc7776d96 Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Thu, 9 Jan 2025 13:57:33 +0100 Subject: [PATCH 13/14] Enforce gcc11 to use pre-built binaries --- .github/workflows/main.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6a2c02b1..fe7650c3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,6 +9,9 @@ on: jobs: coverage: runs-on: ubuntu-latest + env: + CC: gcc-11 + CXX: g++-11 steps: - uses: actions/checkout@v3 - uses: turtlebrowser/get-conan@main @@ -56,6 +59,9 @@ jobs: run: ./build/test/Release/tests.exe test_without_conan: runs-on: ubuntu-latest + env: + CC: gcc-11 + CXX: g++-11 steps: - uses: actions/checkout@v3 - name: Install parallelism library for C++ @@ -97,6 +103,9 @@ jobs: run: ./build/Release/test/tests clang_tidy: runs-on: ubuntu-latest + env: + CC: gcc-11 + CXX: g++-11 steps: - uses: actions/checkout@v3 - uses: turtlebrowser/get-conan@main From a156c4fce95f037f04698b82fe0d2d97d374d04c Mon Sep 17 00:00:00 2001 From: Ivo Hedtke Date: Thu, 9 Jan 2025 14:03:48 +0100 Subject: [PATCH 14/14] Switch from ubuntu-latest to ubuntu-22 to ensure GCC11 is present --- .github/workflows/main.yml | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fe7650c3..4dd26138 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: jobs: coverage: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: CC: gcc-11 CXX: g++-11 @@ -58,7 +58,7 @@ jobs: - name: Run Tests run: ./build/test/Release/tests.exe test_without_conan: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: CC: gcc-11 CXX: g++-11 @@ -80,11 +80,29 @@ jobs: make -j tests - name: Run tests run: ./test/tests - test_release_inx: - strategy: - matrix: - os: [ ubuntu-latest, macos-13 ] - runs-on: ${{ matrix.os }} + test_release_mac: + runs-on: macos-13 + steps: + - uses: actions/checkout@v3 + - uses: turtlebrowser/get-conan@main + - name: Run Conan Install + run: | + conan profile detect + pushd ~/.conan2/profiles + sed -i'' -e 's/gnu17/17/g' * + popd + conan install -of . -o with_tests=True --build=missing . + - name: Run CMake + run: cmake --preset conan-release . + - name: Compile + run: cmake --build build/Release --target tests + - name: Run Tests + run: ./build/Release/test/tests + test_release_linux: + runs-on: ubuntu-22.04 + env: + CC: gcc-11 + CXX: g++-11 steps: - uses: actions/checkout@v3 - uses: turtlebrowser/get-conan@main @@ -102,7 +120,7 @@ jobs: - name: Run Tests run: ./build/Release/test/tests clang_tidy: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: CC: gcc-11 CXX: g++-11 @@ -121,7 +139,7 @@ jobs: - name: Run Clang-Tidy run: clang-tidy-14 -p build/Release source/*.cpp include/**/*.hpp clang_format: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 - uses: DoozyX/clang-format-lint-action@v0.16.2 @@ -130,7 +148,7 @@ jobs: extensions: 'hpp,cpp' clangFormatVersion: 17 doxygen: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 - uses: ssciwr/doxygen-install@v1