diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4dd26138..9757fc50 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,7 +41,7 @@ jobs: verbose: true # optional (default = false) fail_ci_if_error: true test_release_win: - runs-on: windows-2019 + runs-on: windows-2022 steps: - uses: actions/checkout@v3 - uses: turtlebrowser/get-conan@main diff --git a/changelog.md b/changelog.md index 15ee84f4..ce6c5a10 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Added +- [PR36](https://github.com/scipopt/SCIPpp/pull/36) + New c'tors for `LinExpr` to allow use-cases like + ```cpp + const Var& [x1, x2] = ... + const array A_VARS = ... + const vector V_VARS = ... + + LinExpr le(A_VARS); // new c'tor for std::array + le += V_VARS; // new c'tor for std::vector + le += {x1, x2}; // new c'tor for std::initializer_list + // all of them also available with a second argument for coefficients + ``` - [PR31](https://github.com/scipopt/SCIPpp/pull/31) Add `InitialSolution` and `Model::addSolution`. - [PR28](https://github.com/scipopt/SCIPpp/pull/28) Add `Var::getVar`. diff --git a/include/scippp/lin_expr.hpp b/include/scippp/lin_expr.hpp index e0c7f6f7..0abc1dcc 100644 --- a/include/scippp/lin_expr.hpp +++ b/include/scippp/lin_expr.hpp @@ -1,9 +1,14 @@ #pragma once +#include #include #include "scippp/var.hpp" +// just for tests, see test_linexpr.cpp +namespace LinExpression { // NOLINT(readability-identifier-naming) +struct CheckInternalsUsingFriendStruct; +} namespace scippp { /** @@ -12,6 +17,7 @@ namespace scippp { */ class LinExpr { friend class Model; + friend struct LinExpression::CheckInternalsUsingFriendStruct; // just for tests, see test_linexpr.cpp //! Constant term. double m_constant { 0.0 }; //! Variables in the linear combination. @@ -39,12 +45,67 @@ class LinExpr { { } /** - * Creates a linear expression with zero as constant the given variable with coefficient one. + * Creates a linear expression with zero as constant and the given variable with coefficient one. * @since 1.0.0 - * @remark This is on purpose not an explicit c'tor to allow expressions like x <= 1. + * @remark This is on purpose not an explicit c'tor to allow expressions like x <= 1. * @param var Variable to store with coefficient one in the expression. */ LinExpr(const Var& var); + /** + * Creates a linear expression with zero as constant and the given variables with coefficient one. + * @since 1.3.0 + * @remark This is on purpose not an explicit c'tor to allow expressions like ... + {x1, x2}. + * @param vars Variables to store with coefficient one in the expression. + */ + LinExpr(std::initializer_list vars); + /** + * Creates a linear expression with zero as constant and the given variables with the provided coefficients. + * @since 1.3.0 + * @param vars Variables to store in the expression. + * @param coeffs Coefficients to store in the expression. + * @pre \p vars and \p coeffs have the same length. + */ + LinExpr(std::initializer_list vars, std::initializer_list coeffs); + /** + * @copybrief LinExpr(std::initializer_list) + * @since 1.3.0 + * @remark This is on purpose not an explicit c'tor to allow expressions like ... + vars. + * @tparam N Size of the array \p vars. + * @param vars Variables to store with coefficient one in the expression. + */ + template + LinExpr(const std::array& vars) + : m_vars { vars.begin(), vars.end() } + , m_coeffs(vars.size(), 1) + { + } + /** + * @copybrief LinExpr(std::initializer_list, std::initializer_list) + * @since 1.3.0 + * @tparam N Size of the arrays \p vars and \p coeffs. + * @param vars Variables to store in the expression. + * @param coeffs Coefficients to store in the expression. + */ + template + LinExpr(const std::array& vars, std::array& coeffs) + : m_vars { vars.begin(), vars.end() } + , m_coeffs { coeffs.begin(), coeffs.end() } + { + } + /** + * @copybrief LinExpr(std::initializer_list) + * @since 1.3.0 + * @remark This is on purpose not an explicit c'tor to allow expressions like ... + vars. + * @param vars Variables to store with coefficient one in the expression. + */ + LinExpr(const std::vector& vars); + /** + * @copybrief LinExpr(std::initializer_list, std::initializer_list) + * @since 1.3.0 + * @param vars Variables to store in the expression. + * @param coeffs Coefficients to store in the expression. + */ + LinExpr(const std::vector& vars, const std::vector& coeffs); /** * Returns the constant term of the expression. diff --git a/source/lin_expr.cpp b/source/lin_expr.cpp index 858aa442..4d839cdb 100644 --- a/source/lin_expr.cpp +++ b/source/lin_expr.cpp @@ -1,5 +1,7 @@ #include "scippp/lin_expr.hpp" +#include + namespace scippp { LinExpr::LinExpr(const Var& var) @@ -8,6 +10,32 @@ LinExpr::LinExpr(const Var& var) { } +LinExpr::LinExpr(std::initializer_list vars) + : m_vars { vars } + , m_coeffs(vars.size(), 1) +{ +} + +LinExpr::LinExpr(std::initializer_list vars, std::initializer_list coeffs) + : m_vars { vars } + , m_coeffs { coeffs } +{ + assert(vars.size() == coeffs.size()); // GCOVR_EXCL_LINE +} + +LinExpr::LinExpr(const std::vector& vars) + : m_vars { vars } + , m_coeffs(vars.size(), 1) +{ +} + +LinExpr::LinExpr(const std::vector& vars, const std::vector& coeffs) + : m_vars { vars } + , m_coeffs { coeffs } +{ + assert(vars.size() == coeffs.size()); // GCOVR_EXCL_LINE +} + double LinExpr::getConstant() const { return m_constant; diff --git a/test/test_linexpr.cpp b/test/test_linexpr.cpp new file mode 100644 index 00000000..bad5033d --- /dev/null +++ b/test/test_linexpr.cpp @@ -0,0 +1,127 @@ +#include + +#include + +#include "scippp/model.hpp" +#include "scippp/solving_statistics.hpp" + +using namespace scippp; +using namespace std; + +BOOST_AUTO_TEST_SUITE(LinExpression) + +BOOST_AUTO_TEST_CASE(AddOneVar) +{ + Model model("Simple"); + array coeff { 1, 1 }; + const auto& [x1, x2] = model.addVars<2>("x_", coeff); + LinExpr l; + l += x1; // allocates memory, thus slow + l += x2; // allocates memory again, thus slow + model.addConstr(l <= 1, "capacity"); + model.setObjsense(Sense::MAXIMIZE); + model.solve(); + BOOST_TEST(model.getSolvingStatistic(statistics::PRIMALBOUND) == 1); +} + +BOOST_AUTO_TEST_CASE(CtorArray) +{ + array objCoeff { 1.0, 1.0 }; + + Model m1("m1"); + const auto VARS1 = m1.addVars<2>("x_", objCoeff); + LinExpr l1(VARS1); + m1.addConstr(l1 <= 1, "capacity"); + m1.setObjsense(Sense::MAXIMIZE); + m1.solve(); + BOOST_TEST(m1.getSolvingStatistic(statistics::PRIMALBOUND) == 1.0); + + Model m2("m2"); + const auto VARS2 = m2.addVars<2>("x_", objCoeff); + LinExpr l2(VARS2, objCoeff); // same as before, but different c'tor + m2.addConstr(l2 <= 1, "capacity"); + m2.setObjsense(Sense::MAXIMIZE); + m2.solve(); + BOOST_TEST(m2.getSolvingStatistic(statistics::PRIMALBOUND) == 1.0); + + const double FAC { 2.0 }; + array constrCoeff { FAC, FAC }; + + Model m3("m3"); + const auto VARS3 = m3.addVars<2>("x_", objCoeff); + LinExpr l3(VARS3, constrCoeff); + m3.addConstr(l3 <= 1, "capacity"); + m3.setObjsense(Sense::MAXIMIZE); + m3.solve(); + BOOST_TEST(m3.getSolvingStatistic(statistics::PRIMALBOUND) == 1.0 / FAC); +} + +BOOST_AUTO_TEST_CASE(AddArray) +{ + Model model("Simple"); + array coeff { 1, 1 }; + const auto VARS = model.addVars<2>("x_", coeff); + LinExpr l; + l += VARS; + model.addConstr(l <= 1, "capacity"); + model.setObjsense(Sense::MAXIMIZE); + model.solve(); + BOOST_TEST(model.getSolvingStatistic(statistics::PRIMALBOUND) == 1); +} + +BOOST_AUTO_TEST_CASE(AddVector) +{ + Model model("Simple"); + array coeff { 1, 1 }; + const auto A_VARS { model.addVars<2>("x_", coeff) }; + const vector VARS { A_VARS.begin(), A_VARS.end() }; + LinExpr l1; + l1 += VARS; + model.addConstr(l1 <= 1, "capacity1"); + LinExpr l2(VARS, vector { 2.0, 2.0 }); + model.addConstr(l2 <= 2, "capacity2"); // duplicate, but different c'tor + model.setObjsense(Sense::MAXIMIZE); + model.solve(); + BOOST_TEST(model.getSolvingStatistic(statistics::PRIMALBOUND) == 1); +} + +BOOST_AUTO_TEST_CASE(AddInitializerList) +{ + Model model("Simple"); + array coeff { 1, 1 }; + const auto& [x1, x2] = model.addVars<2>("x_", coeff); + LinExpr l; + l += { x1, x2 }; + model.addConstr(l <= 1, "capacity"); + // duplicate, but different c'tor: + model.addConstr(LinExpr({ x1, x2 }, { 2.0, 2.0 }) <= 2, "capacity2"); + model.setObjsense(Sense::MAXIMIZE); + model.solve(); + BOOST_TEST(model.getSolvingStatistic(statistics::PRIMALBOUND) == 1); +} + +BOOST_AUTO_TEST_CASE(CheckInternalsUsingFriendStruct) +{ + const double VAL { 2.0 }; + for (size_t num { 1 }; num < 4; ++num) { + Model m("m"); + const auto VARS = m.addVars("x", num); + + LinExpr lDef(VARS); + BOOST_TEST(lDef.getConstant() == 0); + BOOST_TEST(lDef.m_constant == 0); + BOOST_TEST(lDef.m_vars.size() == num); + BOOST_TEST(lDef.m_coeffs.size() == num); + BOOST_TEST(all_of(lDef.m_coeffs.begin(), lDef.m_coeffs.end(), [](double d) { return d == 1; })); + + const vector COEFF(num, VAL); + LinExpr lCoeff(VARS, COEFF); + BOOST_TEST(lCoeff.getConstant() == 0); + BOOST_TEST(lCoeff.m_constant == 0); + BOOST_TEST(lCoeff.m_vars.size() == num); + BOOST_TEST(lCoeff.m_coeffs.size() == num); + BOOST_TEST(all_of(lCoeff.m_coeffs.begin(), lCoeff.m_coeffs.end(), [&VAL](double d) { return d == VAL; })); + } +} + +BOOST_AUTO_TEST_SUITE_END()