24#include "Eigen/SparseCore"
25#include "absl/status/status.h"
26#include "absl/status/statusor.h"
27#include "absl/types/optional.h"
28#include "gmock/gmock.h"
29#include "gtest/gtest.h"
32#include "ortools/linear_solver/linear_solver.pb.h"
40using ::testing::ElementsAre;
42using ::testing::HasSubstr;
43using ::testing::IsEmpty;
44using ::testing::Optional;
45using ::testing::PrintToString;
46const double kInfinity = std::numeric_limits<double>::infinity();
48TEST(QuadraticProgram, DefaultConstructorWorks) { QuadraticProgram qp; }
50TEST(QuadraticProgram, MoveConstructor) {
52 QuadraticProgram qp2(std::move(qp1));
56TEST(QuadraticProgram, MoveAssignment) {
64 const absl::Status
status =
71 qp.ResizeAndInitialize(2, 3);
72 qp.constraint_lower_bounds.resize(10);
74 absl::StatusCode::kInvalidArgument);
79 qp.ResizeAndInitialize(2, 3);
80 qp.constraint_upper_bounds.resize(10);
82 absl::StatusCode::kInvalidArgument);
87 qp.ResizeAndInitialize(2, 3);
88 qp.objective_vector.resize(10);
90 absl::StatusCode::kInvalidArgument);
95 qp.ResizeAndInitialize(2, 3);
96 qp.variable_lower_bounds.resize(10);
98 absl::StatusCode::kInvalidArgument);
103 qp.ResizeAndInitialize(2, 3);
104 qp.variable_upper_bounds.resize(10);
106 absl::StatusCode::kInvalidArgument);
111 qp.ResizeAndInitialize(2, 3);
112 qp.constraint_matrix.resize(10, 2);
114 absl::StatusCode::kInvalidArgument);
119 qp.ResizeAndInitialize(2, 3);
120 qp.constraint_matrix.resize(2, 10);
122 absl::StatusCode::kInvalidArgument);
127 qp.ResizeAndInitialize(2, 3);
128 qp.objective_matrix.emplace();
129 qp.objective_matrix->resize(10);
131 absl::StatusCode::kInvalidArgument);
134TEST(HasValidBoundsTest, InconsistentConstraintBounds) {
139TEST(HasValidBoundsTest, InconsistentVariableBounds) {
144TEST(HasValidBoundsTest, SmallValidLp) {
149class ConvertQpMpModelProtoTest :
public testing::TestWithParam<bool> {};
161MPModelProto TestLpProto(
bool maximize) {
162 auto proto = ParseTextOrDie<MPModelProto>(R
"pb(variable {
165 objective_coefficient: 5.5
170 objective_coefficient: -2
175 objective_coefficient: -1
180 objective_coefficient: 1
185 var_index: [ 0, 1, 2, 3 ]
186 coefficient: [ 2, 1, 1, 2 ]
192 coefficient: [ 1, 1 ]
204 coefficient: [ 1.5, -1 ]
206 objective_offset: -14)pb");
207 proto.set_maximize(maximize);
212TEST_P(ConvertQpMpModelProtoTest, LpFromMpModelProto) {
213 const bool maximize = GetParam();
214 MPModelProto
proto = TestLpProto(maximize);
216 ASSERT_TRUE(lp.ok()) << lp.status();
226MPModelProto TestQpProto(
bool maximize) {
227 auto proto = ParseTextOrDie<MPModelProto>(
228 R
"pb(variable { lower_bound: -1 upper_bound: 2 objective_coefficient: 3 }
229 variable { lower_bound: -2 upper_bound: 3 objective_coefficient: 0 }
234 coefficient: [ 1, 1 ]
237 quadratic_objective {
238 qvar1_index: [ 0, 1 ]
239 qvar2_index: [ 0, 1 ]
240 coefficient: [ 1, 1 ]
243 proto.set_maximize(maximize);
249 const bool maximize = GetParam();
250 MPModelProto
proto = TestQpProto(maximize);
252 ASSERT_TRUE(qp.ok()) << qp.status();
254 EXPECT_THAT(qp->constraint_lower_bounds, ElementsAre(-
kInfinity));
255 EXPECT_THAT(qp->constraint_upper_bounds, ElementsAre(42));
256 EXPECT_THAT(qp->variable_lower_bounds, ElementsAre(-1, -2));
257 EXPECT_THAT(qp->variable_upper_bounds, ElementsAre(2, 3));
258 EXPECT_THAT(
ToDense(qp->constraint_matrix), EigenArrayEq<double>({{1, 1}}));
259 EXPECT_TRUE(qp->constraint_matrix.isCompressed());
261 double sign = maximize ? -1 : 1;
262 EXPECT_EQ(sign * qp->objective_offset, -4);
263 EXPECT_EQ(qp->objective_scaling_factor, sign);
264 EXPECT_THAT(sign * qp->objective_vector, ElementsAre(3, 0));
265 EXPECT_THAT(sign * (qp->objective_matrix->diagonal()),
266 EigenArrayEq<double>({2, 2}));
270 auto proto = ParseTextOrDie<MPModelProto>(
271 R
"pb(variable { lower_bound: -1 upper_bound: 2 objective_coefficient: 3 }
272 variable { lower_bound: -2 upper_bound: 3 objective_coefficient: 0 }
277 coefficient: [ 1, 1 ]
280 quadratic_objective {
289 absl::StatusCode::kInvalidArgument);
296 ASSERT_TRUE(qp.ok()) << qp.status();
307TEST_P(ConvertQpMpModelProtoTest, IntegerVariablesFromMpModelProto) {
308 const bool maximize = GetParam();
309 auto proto = ParseTextOrDie<MPModelProto>(
310 R
"pb(variable { lower_bound: -1 upper_bound: 2 objective_coefficient: 1 }
314 objective_coefficient: 2
321 coefficient: [ 1, 1 ]
324 proto.set_maximize(maximize);
328 absl::StatusCode::kInvalidArgument);
330 ASSERT_TRUE(lp.ok()) << lp.status();
332 EXPECT_THAT(lp->constraint_lower_bounds, ElementsAre(-
kInfinity));
333 EXPECT_THAT(lp->constraint_upper_bounds, ElementsAre(1));
334 EXPECT_THAT(lp->variable_lower_bounds, ElementsAre(-1, -2));
335 EXPECT_THAT(lp->variable_upper_bounds, ElementsAre(2, 3));
336 EXPECT_THAT(
ToDense(lp->constraint_matrix), EigenArrayEq<double>({{1, 1}}));
337 EXPECT_TRUE(lp->constraint_matrix.isCompressed());
339 double sign = maximize ? -1 : 1;
340 EXPECT_EQ(lp->objective_offset, 0);
341 EXPECT_THAT(sign * lp->objective_vector, ElementsAre(1, 2));
342 EXPECT_FALSE(lp->objective_matrix.has_value());
345MPModelProto TinyModelWithNames() {
346 return ParseTextOrDie<MPModelProto>(
352 objective_coefficient: 1
358 objective_coefficient: 2
365 coefficient: [ 1, 1 ]
370TEST(QpFromMpModelProtoTest, EmptyQp) {
373 ASSERT_TRUE(qp.ok()) << qp.status();
375 EXPECT_THAT(qp->constraint_lower_bounds, ElementsAre());
376 EXPECT_THAT(qp->constraint_upper_bounds, ElementsAre());
377 EXPECT_THAT(qp->variable_lower_bounds, ElementsAre());
378 EXPECT_THAT(qp->variable_upper_bounds, ElementsAre());
379 EXPECT_EQ(qp->constraint_matrix.cols(), 0);
380 EXPECT_EQ(qp->constraint_matrix.rows(), 0);
381 EXPECT_EQ(qp->objective_offset, 0);
382 EXPECT_EQ(qp->objective_scaling_factor, 1);
383 EXPECT_FALSE(qp->objective_matrix.has_value());
384 EXPECT_THAT(qp->objective_vector, ElementsAre());
387TEST(QpFromMpModelProtoTest, DoesNotIncludeNames) {
391 ASSERT_TRUE(lp.ok()) << lp.status();
392 EXPECT_EQ(lp->problem_name, absl::nullopt);
393 EXPECT_EQ(lp->variable_names, absl::nullopt);
394 EXPECT_EQ(lp->constraint_names, absl::nullopt);
397TEST(QpFromMpModelProtoTest, IncludesNames) {
401 ASSERT_TRUE(lp.ok()) << lp.status();
402 EXPECT_THAT(lp->problem_name, Optional(Eq(
"problem")));
403 EXPECT_THAT(lp->variable_names, Optional(ElementsAre(
"x_0",
"x_1")));
404 EXPECT_THAT(lp->constraint_names, Optional(ElementsAre(
"c_0")));
407INSTANTIATE_TEST_SUITE_P(
408 ConvertQpMpModelProtoTests, ConvertQpMpModelProtoTest, testing::Bool(),
409 [](
const testing::TestParamInfo<ConvertQpMpModelProtoTest::ParamType>&
420 std::string(negation ?
"isn't" :
"is") +
" the triplet " +
421 PrintToString(
row) +
"," + PrintToString(
col) +
"=" +
422 PrintToString(
value)) {
423 return arg.row() ==
row && arg.col() ==
col && arg.value() ==
value;
427 std::vector<Eigen::Triplet<double, int64_t>> triplets;
429 EXPECT_THAT(triplets, IsEmpty());
433 std::vector<Eigen::Triplet<double, int64_t>> triplets = {{1, 2, 3.0}};
435 EXPECT_THAT(triplets, ElementsAre(IsEigenTriplet(1, 2, 3.0)));
439 std::vector<Eigen::Triplet<double, int64_t>> triplets = {
440 {1, 2, 3.0}, {2, 1, 1.0}, {1, 1, 0.0}};
442 EXPECT_THAT(triplets,
443 ElementsAre(IsEigenTriplet(1, 2, 3.0), IsEigenTriplet(2, 1, 1.0),
444 IsEigenTriplet(1, 1, 0.0)));
448 std::vector<Eigen::Triplet<double, int64_t>> triplets = {
449 {1, 2, 3.0}, {1, 2, -1.0}, {1, 1, 0.0}};
451 EXPECT_THAT(triplets, ElementsAre(IsEigenTriplet(1, 2, 2.0),
452 IsEigenTriplet(1, 1, 0.0)));
456 std::vector<Eigen::Triplet<double, int64_t>> triplets = {
457 {1, 2, 3.0}, {2, 1, 1.0}, {2, 1, 1.0}};
459 EXPECT_THAT(triplets, ElementsAre(IsEigenTriplet(1, 2, 3.0),
460 IsEigenTriplet(2, 1, 2.0)));
464 std::vector<Eigen::Triplet<double, int64_t>> triplets = {
465 {1, 2, 3.0}, {1, 2, 1.0}, {1, 2, 2.0}};
467 EXPECT_THAT(triplets, ElementsAre(IsEigenTriplet(1, 2, 6.0)));
471 std::vector<Eigen::Triplet<double, int64_t>> triplets;
472 Eigen::SparseMatrix<double, Eigen::ColMajor, int64_t> matrix(2, 2);
474 EXPECT_THAT(
ToDense(matrix), EigenArrayEq<double>({{0, 0},
479 std::vector<Eigen::Triplet<double, int64_t>> triplets = {
480 {0, 0, 1.0}, {1, 0, -1.0}, {0, 0, 0.0}, {1, 1, 1.0}, {0, 0, 1.0}};
481 Eigen::SparseMatrix<double, Eigen::ColMajor, int64_t> matrix(2, 2);
483 EXPECT_THAT(
ToDense(matrix), EigenArrayEq<double>({{2, 0},
T ParseTextOrDie(const std::string &input)
void CombineRepeatedTripletsInPlace(std::vector< Eigen::Triplet< double, int64_t > > &triplets)
absl::StatusOr< QuadraticProgram > QpFromMpModelProto(const MPModelProto &proto, bool relax_integer_variables, bool include_names)
absl::Status ValidateQuadraticProgramDimensions(const QuadraticProgram &qp)
::Eigen::ArrayXXd ToDense(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &sparse_mat)
void VerifyTestQp(const QuadraticProgram &qp, bool maximize)
absl::Status CanFitInMpModelProto(const QuadraticProgram &qp)
constexpr double kInfinity
void SetEigenMatrixFromTriplets(std::vector< Eigen::Triplet< double, int64_t > > triplets, Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix)
bool HasValidBounds(const QuadraticProgram &qp)
void VerifyTestLp(const QuadraticProgram &qp, bool maximize)
QuadraticProgram SmallInvalidProblemLp()
QuadraticProgram SmallPrimalInfeasibleLp()
QuadraticProgram SmallInconsistentVariableBoundsLp()
QuadraticProgram TestDiagonalQp1()
TEST(LinearAssignmentTest, NullMatrix)