Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
65 changes: 63 additions & 2 deletions include/scippp/lin_expr.hpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#pragma once

#include <array>
#include <vector>

#include "scippp/var.hpp"

// just for tests, see test_linexpr.cpp
namespace LinExpression { // NOLINT(readability-identifier-naming)
struct CheckInternalsUsingFriendStruct;
}
namespace scippp {

/**
Expand All @@ -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.
Expand Down Expand Up @@ -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 <code>x <= 1</code>.
* @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 <code>... + {x1, x2}</code>.
* @param vars Variables to store with coefficient one in the expression.
*/
LinExpr(std::initializer_list<Var> 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<Var> vars, std::initializer_list<double> coeffs);
/**
* @copybrief LinExpr(std::initializer_list<Var>)
* @since 1.3.0
* @remark This is on purpose not an explicit c'tor to allow expressions like <code>... + vars</code>.
* @tparam N Size of the array \p vars.
* @param vars Variables to store with coefficient one in the expression.
*/
template <std::size_t N>
LinExpr(const std::array<Var, N>& vars)
: m_vars { vars.begin(), vars.end() }
, m_coeffs(vars.size(), 1)
{
}
/**
* @copybrief LinExpr(std::initializer_list<Var>, std::initializer_list<double>)
* @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 <std::size_t N>
LinExpr(const std::array<Var, N>& vars, std::array<double, N>& coeffs)
: m_vars { vars.begin(), vars.end() }
, m_coeffs { coeffs.begin(), coeffs.end() }
{
}
/**
* @copybrief LinExpr(std::initializer_list<Var>)
* @since 1.3.0
* @remark This is on purpose not an explicit c'tor to allow expressions like <code>... + vars</code>.
* @param vars Variables to store with coefficient one in the expression.
*/
LinExpr(const std::vector<Var>& vars);
/**
* @copybrief LinExpr(std::initializer_list<Var>, std::initializer_list<double>)
* @since 1.3.0
* @param vars Variables to store in the expression.
* @param coeffs Coefficients to store in the expression.
*/
LinExpr(const std::vector<Var>& vars, const std::vector<double>& coeffs);

/**
* Returns the constant term of the expression.
Expand Down
28 changes: 28 additions & 0 deletions source/lin_expr.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "scippp/lin_expr.hpp"

#include <cassert>

namespace scippp {

LinExpr::LinExpr(const Var& var)
Expand All @@ -8,6 +10,32 @@ LinExpr::LinExpr(const Var& var)
{
}

LinExpr::LinExpr(std::initializer_list<Var> vars)
: m_vars { vars }
, m_coeffs(vars.size(), 1)
{
}

LinExpr::LinExpr(std::initializer_list<Var> vars, std::initializer_list<double> coeffs)
: m_vars { vars }
, m_coeffs { coeffs }
{
assert(vars.size() == coeffs.size()); // GCOVR_EXCL_LINE
}

LinExpr::LinExpr(const std::vector<Var>& vars)
: m_vars { vars }
, m_coeffs(vars.size(), 1)
{
}

LinExpr::LinExpr(const std::vector<Var>& vars, const std::vector<double>& coeffs)
: m_vars { vars }
, m_coeffs { coeffs }
{
assert(vars.size() == coeffs.size()); // GCOVR_EXCL_LINE
}

double LinExpr::getConstant() const
{
return m_constant;
Expand Down
127 changes: 127 additions & 0 deletions test/test_linexpr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include <boost/test/unit_test.hpp>

#include <algorithm>

#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<Var> 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()