Files
ortools-clone/ortools/math_opt/cpp/model_test.cc
2025-08-29 15:31:26 +02:00

2588 lines
93 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Unit tests for math_opt.h on model reading and construction. The underlying
// solver is not invoked. For tests that run Solve(), see
// ortools/math_opt/solver_tests/*.
#include "ortools/math_opt/cpp/model.h"
#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/base/parse_text_proto.h"
#include "ortools/base/string_view_migration.h"
#include "ortools/math_opt/constraints/indicator/indicator_constraint.h"
#include "ortools/math_opt/constraints/quadratic/quadratic_constraint.h"
#include "ortools/math_opt/constraints/second_order_cone/second_order_cone_constraint.h"
#include "ortools/math_opt/constraints/sos/sos1_constraint.h"
#include "ortools/math_opt/constraints/sos/sos2_constraint.h"
#include "ortools/math_opt/cpp/key_types.h"
#include "ortools/math_opt/cpp/linear_constraint.h"
#include "ortools/math_opt/cpp/math_opt.h"
#include "ortools/math_opt/cpp/update_tracker.h"
#include "ortools/math_opt/cpp/variable_and_expressions.h"
#include "ortools/math_opt/sparse_containers.pb.h"
#include "ortools/math_opt/storage/model_storage.h"
#include "ortools/math_opt/storage/model_storage_types.h"
#include "ortools/math_opt/testing/stream.h"
#include "ortools/util/fp_roundtrip_conv_testing.h"
namespace operations_research {
namespace math_opt {
namespace {
using ::google::protobuf::contrib::parse_proto::ParseTextProto;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::EquivToProto;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
using ::testing::status::IsOkAndHolds;
using ::testing::status::StatusIs;
constexpr double kInf = std::numeric_limits<double>::infinity();
// max 2.0 * y + 3.5
// s.t. x + y - 1 <= 0.5 (c)
// 2.0 * y >= 0.5 (d)
// x unbounded
// y in {0, 1}
class ModelingTest : public testing::Test {
protected:
ModelingTest()
: model_("math_opt_model"),
x_(model_.AddVariable("x")),
y_(model_.AddBinaryVariable("y")),
c_(model_.AddLinearConstraint(x_ + y_ - 1.0 <= 0.5, "c")),
d_(model_.AddLinearConstraint(2.0 * y_ >= 0.5, "d")) {
model_.Maximize(2.0 * y_ + 3.5);
}
Model model_;
const Variable x_;
const Variable y_;
const LinearConstraint c_;
const LinearConstraint d_;
};
TEST(ModelTest, FromValidModelProto) {
// Here we assume Model::FromModelProto() uses ModelStorage::FromModelProto()
// and thus we don't test everything.
ModelProto model_proto;
model_proto.set_name("model");
const VariableId x_id(1);
model_proto.mutable_variables()->add_ids(x_id.value());
model_proto.mutable_variables()->add_lower_bounds(0.0);
model_proto.mutable_variables()->add_upper_bounds(1.0);
model_proto.mutable_variables()->add_integers(false);
model_proto.mutable_variables()->add_names("x");
ASSERT_OK_AND_ASSIGN(const std::unique_ptr<Model> model,
Model::FromModelProto(model_proto));
EXPECT_THAT(model->ExportModel(), EquivToProto(model_proto));
ASSERT_EQ(model->num_variables(), 1);
EXPECT_EQ(model->Variables().front().typed_id(), x_id);
}
TEST(ModelTest, FromInvalidModelProto) {
// Here we assume Model::FromModelProto() uses ValidateModel() via
// ModelStorage::FromModelProto() and thus we don't test all possible errors.
ModelProto model_proto;
model_proto.set_name("model");
model_proto.mutable_variables()->add_ids(1);
// Missing lower_bounds entry.
model_proto.mutable_variables()->add_upper_bounds(1.0);
model_proto.mutable_variables()->add_integers(false);
model_proto.mutable_variables()->add_names("x");
EXPECT_THAT(
Model::FromModelProto(model_proto),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("lower_bounds")));
}
TEST(ModelTest, FromStorage) {
// In this test, we only test adding one variable. We assume here that the
// constructor will move the provided storage in-place. Thus it is not
// necessary to over-test this feature.
auto storage = std::make_unique<ModelStorage>("test");
// Here we directly delete variables since the ModelStorage won't reuse an id
// already returned. We don't bother giving names or bounds to these
// variables.
storage->DeleteVariable(storage->AddVariable());
const VariableId x_id = storage->AddVariable(
/*lower_bound=*/0.0, /*upper_bound=*/1.0, /*is_integer=*/true, "x");
const Model model(std::move(storage));
const std::vector<Variable> variables = model.Variables();
ASSERT_EQ(variables.size(), 1);
const Variable x = variables[0];
EXPECT_EQ(x.typed_id(), x_id);
EXPECT_EQ(x.name(), "x");
EXPECT_EQ(x.lower_bound(), 0.0);
EXPECT_EQ(x.upper_bound(), 1.0);
}
// We can't use Pointwise(Property(...)) matchers, hence here we have this
// function to extract the typed ids to use UnorderedElementsAre().
template <typename T>
std::vector<typename T::IdType> TypedIds(const std::vector<T>& v) {
std::vector<typename T::IdType> ids;
for (const T& e : v) {
ids.push_back(e.typed_id());
}
return ids;
}
TEST_F(ModelingTest, Clone) {
const Model& const_ref = model_;
{
const std::unique_ptr<Model> clone = const_ref.Clone();
EXPECT_THAT(clone->ExportModel(), EquivToProto(model_.ExportModel()));
EXPECT_THAT(TypedIds(clone->SortedVariables()),
ElementsAreArray(TypedIds(model_.SortedVariables())));
EXPECT_THAT(TypedIds(clone->SortedLinearConstraints()),
ElementsAreArray(TypedIds(model_.SortedLinearConstraints())));
}
// Redo the test after removing the first variable and a new variable that we
// just added. This should shift the new variables's IDs by one.
{
model_.DeleteVariable(x_);
model_.DeleteVariable(model_.AddVariable());
// Same with constraints.
model_.DeleteLinearConstraint(c_);
model_.DeleteLinearConstraint(model_.AddLinearConstraint());
const std::unique_ptr<Model> clone = const_ref.Clone();
EXPECT_THAT(clone->ExportModel(), EquivToProto(model_.ExportModel()));
EXPECT_THAT(TypedIds(clone->SortedVariables()),
ElementsAreArray(TypedIds(model_.SortedVariables())));
EXPECT_THAT(TypedIds(clone->SortedLinearConstraints()),
ElementsAreArray(TypedIds(model_.SortedLinearConstraints())));
// New variables and constraints should start with the same id.
EXPECT_EQ(clone->AddVariable().typed_id(), model_.AddVariable().typed_id());
EXPECT_EQ(clone->AddLinearConstraint().typed_id(),
model_.AddLinearConstraint().typed_id());
}
// Test renaming.
{
const std::unique_ptr<Model> clone = const_ref.Clone("new_name");
ModelProto expected_proto = model_.ExportModel();
expected_proto.set_name("new_name");
EXPECT_THAT(clone->ExportModel(), EquivToProto(expected_proto));
EXPECT_THAT(TypedIds(clone->SortedVariables()),
ElementsAreArray(TypedIds(model_.SortedVariables())));
EXPECT_THAT(TypedIds(clone->SortedLinearConstraints()),
ElementsAreArray(TypedIds(model_.SortedLinearConstraints())));
}
}
TEST(ModelTest, ApplyValidUpdateProto) {
// Here we assume Model::ApplyUpdateProto() uses
// ModelStorage::ApplyUpdateProto() and thus we don't test everything.
ModelProto model_proto;
model_proto.set_name("model");
const VariableId x_id(1);
model_proto.mutable_variables()->add_ids(x_id.value());
model_proto.mutable_variables()->add_lower_bounds(0.0);
model_proto.mutable_variables()->add_upper_bounds(1.0);
model_proto.mutable_variables()->add_integers(false);
model_proto.mutable_variables()->add_names("x");
ASSERT_OK_AND_ASSIGN(const std::unique_ptr<Model> model,
Model::FromModelProto(model_proto));
EXPECT_THAT(model->ExportModel(), EquivToProto(model_proto));
ModelUpdateProto update_proto;
update_proto.mutable_variable_updates()->mutable_lower_bounds()->add_ids(
x_id.value());
update_proto.mutable_variable_updates()->mutable_lower_bounds()->add_values(
-3.0);
ASSERT_OK(model->ApplyUpdateProto(update_proto));
model_proto.mutable_variables()->mutable_lower_bounds()->Set(0, -3.0);
EXPECT_THAT(model->ExportModel(), EquivToProto(model_proto));
}
TEST(ModelTest, ApplyInvalidUpdateProto) {
// Here we assume Model::ApplyUpdateProto() uses
// ModelStorage::ApplyUpdateProto() and thus we don't test everything.
ModelProto model_proto;
model_proto.set_name("model");
const VariableId x_id(1);
model_proto.mutable_variables()->add_ids(x_id.value());
model_proto.mutable_variables()->add_lower_bounds(0.0);
model_proto.mutable_variables()->add_upper_bounds(1.0);
model_proto.mutable_variables()->add_integers(false);
model_proto.mutable_variables()->add_names("x");
ASSERT_OK_AND_ASSIGN(const std::unique_ptr<Model> model,
Model::FromModelProto(model_proto));
EXPECT_THAT(model->ExportModel(), EquivToProto(model_proto));
ModelUpdateProto update_proto;
// Id 0 does not exist.
update_proto.mutable_variable_updates()->mutable_lower_bounds()->add_ids(0);
update_proto.mutable_variable_updates()->mutable_lower_bounds()->add_values(
-3.0);
EXPECT_THAT(model->ApplyUpdateProto(update_proto),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("invalid variable id")));
}
TEST(ModelTest, VariableGetters) {
Model model;
const Model& const_model = model;
{
const Variable v =
model.AddVariable(/*lower_bound=*/-kInf, /*upper_bound=*/kInf,
/*is_integer=*/false, "continuous");
EXPECT_EQ(const_model.name(v), "continuous");
EXPECT_EQ(const_model.lower_bound(v), -kInf);
EXPECT_EQ(const_model.upper_bound(v), kInf);
EXPECT_FALSE(const_model.is_integer(v));
}
{
const Variable v =
model.AddVariable(/*lower_bound=*/3.0, /*upper_bound=*/5.0,
/*is_integer=*/true, "integer");
EXPECT_EQ(const_model.name(v), "integer");
EXPECT_EQ(const_model.lower_bound(v), 3.0);
EXPECT_EQ(const_model.upper_bound(v), 5.0);
EXPECT_TRUE(const_model.is_integer(v));
}
}
TEST(ModelTest, VariableSetters) {
Model model;
const Model& const_model = model;
const Variable v =
model.AddVariable(/*lower_bound=*/-kInf, /*upper_bound=*/kInf,
/*is_integer=*/false, "v");
model.set_lower_bound(v, 3.0);
model.set_upper_bound(v, 5.0);
model.set_is_integer(v, true);
EXPECT_EQ(const_model.lower_bound(v), 3.0);
EXPECT_EQ(const_model.upper_bound(v), 5.0);
EXPECT_TRUE(const_model.is_integer(v));
model.set_continuous(v);
EXPECT_FALSE(const_model.is_integer(v));
model.set_integer(v);
EXPECT_TRUE(const_model.is_integer(v));
}
TEST(ModelTest, VariableById) {
Model model;
const Variable x0 = model.AddBinaryVariable("x0");
const Variable x1 = model.AddBinaryVariable("x1");
const Variable x2 = model.AddContinuousVariable(-1.0, 2.0, "x2");
model.DeleteVariable(x1);
EXPECT_TRUE(model.has_variable(x0.id()));
EXPECT_FALSE(model.has_variable(x1.id()));
EXPECT_TRUE(model.has_variable(x2.id()));
EXPECT_EQ(model.variable(x0.id()).name(), "x0");
EXPECT_EQ(model.variable(x0.id()).lower_bound(), 0.0);
EXPECT_EQ(model.variable(x0.id()).upper_bound(), 1.0);
EXPECT_EQ(model.variable(x2.id()).name(), "x2");
EXPECT_EQ(model.variable(x2.id()).lower_bound(), -1.0);
EXPECT_EQ(model.variable(x2.id()).upper_bound(), 2.0);
EXPECT_TRUE(model.has_variable(x0.typed_id()));
EXPECT_FALSE(model.has_variable(x1.typed_id()));
EXPECT_TRUE(model.has_variable(x2.typed_id()));
EXPECT_EQ(model.variable(x0.typed_id()).name(), "x0");
EXPECT_EQ(model.variable(x2.typed_id()).name(), "x2");
}
TEST(ModelTest, ValidateExistingVariableOfThisModel) {
Model model_a;
const Variable x0 = model_a.AddBinaryVariable("x0");
const Variable x1 = model_a.AddBinaryVariable("x1");
model_a.DeleteVariable(x0);
Model model_b("b");
EXPECT_OK(model_a.ValidateExistingVariableOfThisModel(x1));
EXPECT_THAT(
model_a.ValidateExistingVariableOfThisModel(x0),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("not found")));
EXPECT_THAT(model_b.ValidateExistingVariableOfThisModel(x1),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("different model")));
}
TEST(ModelDeathTest, VariableByIdOutOfBounds) {
Model model;
model.AddBinaryVariable("x0");
EXPECT_DEATH(model.variable(-1),
AllOf(HasSubstr("variable"), HasSubstr("-1")));
EXPECT_DEATH(model.variable(2), AllOf(HasSubstr("variable"), HasSubstr("2")));
}
TEST(ModelDeathTest, VariableByIdDeleted) {
Model model;
const Variable x = model.AddBinaryVariable("x");
EXPECT_EQ(model.variable(x.id()).name(), "x");
model.DeleteVariable(x);
EXPECT_DEATH(model.variable(x.id()),
AllOf(HasSubstr("variable"), HasSubstr("0")));
}
TEST(ModelDeathTest, VariableAccessorsInvalidModel) {
Model model_a("a");
const Variable a_a = model_a.AddVariable("a_a");
Model model_b("b");
EXPECT_DEATH(model_b.name(a_a), internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.lower_bound(a_a),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.upper_bound(a_a),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.is_integer(a_a),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.set_lower_bound(a_a, 0.0),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.set_upper_bound(a_a, 0.0),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.set_is_integer(a_a, true),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.set_continuous(a_a),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.set_integer(a_a),
internal::kObjectsFromOtherModelStorage);
}
TEST(ModelTest, LinearConstraintGetters) {
Model model;
const Model& const_model = model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const Variable z = model.AddVariable("z");
{
const LinearConstraint c = model.AddLinearConstraint(
/*lower_bound=*/-kInf, /*upper_bound=*/1.5, "upper_bounded");
model.set_coefficient(c, x, 1.0);
model.set_coefficient(c, y, 2.0);
EXPECT_EQ(const_model.name(c), "upper_bounded");
EXPECT_EQ(const_model.lower_bound(c), -kInf);
EXPECT_EQ(const_model.upper_bound(c), 1.5);
EXPECT_EQ(const_model.coefficient(c, x), 1.0);
EXPECT_EQ(const_model.coefficient(c, y), 2.0);
EXPECT_TRUE(const_model.is_coefficient_nonzero(c, x));
EXPECT_TRUE(const_model.is_coefficient_nonzero(c, y));
EXPECT_FALSE(const_model.is_coefficient_nonzero(c, z));
EXPECT_THAT(model.RowNonzeros(c), UnorderedElementsAre(x, y));
const BoundedLinearExpression c_bounded_expr =
c.AsBoundedLinearExpression();
// TODO(b/171883688): we should use expression matchers.
EXPECT_EQ(c_bounded_expr.lower_bound, -kInf);
EXPECT_EQ(c_bounded_expr.upper_bound, 1.5);
EXPECT_THAT(c_bounded_expr.expression.terms(),
UnorderedElementsAre(Pair(x, 1.0), Pair(y, 2.0)));
}
{
const LinearConstraint c = model.AddLinearConstraint(
/*lower_bound=*/0.5, /*upper_bound=*/kInf, "lower_bounded");
model.set_coefficient(c, y, 2.0);
EXPECT_EQ(const_model.name(c), "lower_bounded");
EXPECT_EQ(const_model.lower_bound(c), 0.5);
EXPECT_EQ(const_model.upper_bound(c), kInf);
EXPECT_EQ(const_model.coefficient(c, x), 0.0);
EXPECT_EQ(const_model.coefficient(c, y), 2.0);
EXPECT_FALSE(const_model.is_coefficient_nonzero(c, x));
EXPECT_TRUE(const_model.is_coefficient_nonzero(c, y));
EXPECT_THAT(model.RowNonzeros(c), UnorderedElementsAre(y));
const BoundedLinearExpression c_bounded_expr =
c.AsBoundedLinearExpression();
// TODO(b/171883688): we should use expression matchers.
EXPECT_EQ(c_bounded_expr.lower_bound, 0.5);
EXPECT_EQ(c_bounded_expr.upper_bound, kInf);
EXPECT_THAT(c_bounded_expr.expression.terms(),
UnorderedElementsAre(Pair(y, 2.0)));
}
}
TEST(ModelTest, LinearConstraintSetters) {
Model model;
const Variable x = model.AddVariable("x");
const Model& const_model = model;
const LinearConstraint c = model.AddLinearConstraint("c");
model.set_coefficient(c, x, 1.0);
model.set_coefficient(c, x, 2.0);
model.set_lower_bound(c, 3.0);
model.set_upper_bound(c, 5.0);
EXPECT_EQ(model.coefficient(c, x), 2.0);
EXPECT_EQ(const_model.lower_bound(c), 3.0);
EXPECT_EQ(const_model.upper_bound(c), 5.0);
}
TEST(ModelTest, LinearConstraintById) {
Model model;
const LinearConstraint c0 = model.AddLinearConstraint("c0");
const LinearConstraint c1 = model.AddLinearConstraint("c1");
const LinearConstraint c2 = model.AddLinearConstraint("c2");
model.DeleteLinearConstraint(c1);
EXPECT_TRUE(model.has_linear_constraint(c0.id()));
EXPECT_FALSE(model.has_linear_constraint(c1.id()));
EXPECT_TRUE(model.has_linear_constraint(c2.id()));
EXPECT_EQ(model.linear_constraint(c0.id()).name(), "c0");
EXPECT_EQ(model.linear_constraint(c2.id()).name(), "c2");
EXPECT_TRUE(model.has_linear_constraint(c0.typed_id()));
EXPECT_FALSE(model.has_linear_constraint(c1.typed_id()));
EXPECT_TRUE(model.has_linear_constraint(c2.typed_id()));
EXPECT_EQ(model.linear_constraint(c0.typed_id()).name(), "c0");
EXPECT_EQ(model.linear_constraint(c2.typed_id()).name(), "c2");
}
TEST(ModelTest, ValidateExistingLinearConstraintOfThisModel) {
Model model_a;
const LinearConstraint c0 = model_a.AddLinearConstraint("c0");
const LinearConstraint c1 = model_a.AddLinearConstraint("c1");
model_a.DeleteLinearConstraint(c0);
Model model_b("b");
EXPECT_OK(model_a.ValidateExistingLinearConstraintOfThisModel(c1));
EXPECT_THAT(
model_a.ValidateExistingLinearConstraintOfThisModel(c0),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("not found")));
EXPECT_THAT(model_b.ValidateExistingLinearConstraintOfThisModel(c1),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("different model")));
}
TEST(ModelDeathTest, LinearConstraintByIdOutOfBounds) {
Model model;
model.AddLinearConstraint("c");
EXPECT_DEATH(model.linear_constraint(-1),
AllOf(HasSubstr("linear constraint"), HasSubstr("-1")));
EXPECT_DEATH(model.linear_constraint(2),
AllOf(HasSubstr("linear constraint"), HasSubstr("2")));
}
TEST(ModelDeathTest, LinearConstraintByIdDeleted) {
Model model;
const LinearConstraint c = model.AddLinearConstraint("c");
EXPECT_EQ(model.linear_constraint(c.id()).name(), "c");
model.DeleteLinearConstraint(c);
EXPECT_DEATH(model.linear_constraint(c.id()),
AllOf(HasSubstr("linear constraint"), HasSubstr("0")));
}
TEST(ModelDeathTest, LinearConstraintAccessorsInvalidModel) {
Model model_a("a");
const Variable x_a = model_a.AddVariable("x_a");
const LinearConstraint c_a = model_a.AddLinearConstraint("c_a");
Model model_b("b");
const Variable x_b = model_b.AddVariable("x_b");
const LinearConstraint c_b = model_b.AddLinearConstraint("c_b");
EXPECT_DEATH(model_b.name(c_a), internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.lower_bound(c_a),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.upper_bound(c_a),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.set_lower_bound(c_a, 0.0),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.set_upper_bound(c_a, 0.0),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.set_coefficient(c_a, x_b, 0.0),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.set_coefficient(c_b, x_a, 0.0),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.coefficient(c_a, x_b),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.coefficient(c_b, x_a),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.is_coefficient_nonzero(c_a, x_b),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.is_coefficient_nonzero(c_b, x_a),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.RowNonzeros(c_a),
internal::kObjectsFromOtherModelStorage);
}
TEST_F(ModelingTest, ModelProperties) {
EXPECT_EQ(model_.name(), "math_opt_model");
EXPECT_EQ(model_.num_variables(), 2);
EXPECT_EQ(model_.next_variable_id(), 2);
EXPECT_TRUE(model_.has_variable(0));
EXPECT_TRUE(model_.has_variable(1));
EXPECT_FALSE(model_.has_variable(2));
EXPECT_FALSE(model_.has_variable(3));
EXPECT_FALSE(model_.has_variable(-1));
EXPECT_THAT(model_.Variables(), UnorderedElementsAre(x_, y_));
EXPECT_THAT(model_.SortedVariables(), ElementsAre(x_, y_));
EXPECT_EQ(model_.num_linear_constraints(), 2);
EXPECT_EQ(model_.next_linear_constraint_id(), 2);
EXPECT_TRUE(model_.has_linear_constraint(0));
EXPECT_TRUE(model_.has_linear_constraint(1));
EXPECT_FALSE(model_.has_linear_constraint(2));
EXPECT_FALSE(model_.has_linear_constraint(3));
EXPECT_FALSE(model_.has_linear_constraint(-1));
EXPECT_THAT(model_.LinearConstraints(), UnorderedElementsAre(c_, d_));
EXPECT_THAT(model_.SortedLinearConstraints(), ElementsAre(c_, d_));
}
TEST(ModelTest, ColumnNonzeros) {
Model model("math_opt_model");
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const Variable z = model.AddVariable("z");
const LinearConstraint c1 = model.AddLinearConstraint(x + y <= 2);
const LinearConstraint c2 = model.AddLinearConstraint(x <= 1);
const LinearConstraint c3 = model.AddLinearConstraint(x + y <= 2);
model.DeleteLinearConstraint(c3);
EXPECT_THAT(model.ColumnNonzeros(x), UnorderedElementsAre(c1, c2));
EXPECT_THAT(model.ColumnNonzeros(y), UnorderedElementsAre(c1));
EXPECT_THAT(model.ColumnNonzeros(z), IsEmpty());
}
template <typename T>
std::vector<std::string> SortedValueNames(
const google::protobuf::Map<int64_t, T>& messages) {
std::vector<std::pair<int64_t, std::string>> sorted_entries;
for (const auto& [id, message] : messages) {
sorted_entries.push_back(
{id, google::protobuf::StringCopy(message.name())});
}
absl::c_sort(sorted_entries);
std::vector<std::string> result;
for (const auto& [id, name] : sorted_entries) {
result.push_back(name);
}
return result;
}
TEST(ModelTest, ExportModel_RemoveNames) {
Model model("my_model");
const Variable x = model.AddVariable("x");
const Variable y = model.AddBinaryVariable("y");
model.Maximize(x);
const Objective b = model.AddAuxiliaryObjective(1, "objB");
model.set_objective_offset(b, 2.0);
model.AddLinearConstraint(x <= 1.0, "lin_con");
model.AddQuadraticConstraint(x * x <= 1.0, "quad_con");
model.AddIndicatorConstraint(y, x >= 3.0, /*activate_on_zero=*/false,
"ind_con");
model.AddSos1Constraint({y, 1.0 - y}, {1.0, 1.0}, "sos1");
model.AddSos2Constraint({y, 1.0 - y}, {1.0, 1.0}, "sos2");
model.AddSecondOrderConeConstraint({x + y}, 1.0, "soc");
{
ModelProto named_proto = model.ExportModel(/*remove_names=*/false);
EXPECT_EQ(named_proto.name(), "my_model");
EXPECT_THAT(named_proto.variables().names(), ElementsAre("x", "y"));
EXPECT_THAT(SortedValueNames(named_proto.auxiliary_objectives()),
ElementsAre("objB"));
EXPECT_THAT(named_proto.linear_constraints().names(),
ElementsAre("lin_con"));
EXPECT_THAT(SortedValueNames(named_proto.quadratic_constraints()),
ElementsAre("quad_con"));
EXPECT_THAT(SortedValueNames(named_proto.indicator_constraints()),
ElementsAre("ind_con"));
EXPECT_THAT(SortedValueNames(named_proto.sos1_constraints()),
ElementsAre("sos1"));
EXPECT_THAT(SortedValueNames(named_proto.sos2_constraints()),
ElementsAre("sos2"));
EXPECT_THAT(SortedValueNames(named_proto.second_order_cone_constraints()),
ElementsAre("soc"));
}
{
ModelProto unnamed_proto = model.ExportModel(/*remove_names=*/true);
EXPECT_EQ(unnamed_proto.name(), "");
EXPECT_THAT(unnamed_proto.variables().names(), IsEmpty());
EXPECT_THAT(SortedValueNames(unnamed_proto.auxiliary_objectives()),
ElementsAre(""));
EXPECT_THAT(unnamed_proto.linear_constraints().names(), IsEmpty());
EXPECT_THAT(SortedValueNames(unnamed_proto.quadratic_constraints()),
ElementsAre(""));
EXPECT_THAT(SortedValueNames(unnamed_proto.indicator_constraints()),
ElementsAre(""));
EXPECT_THAT(SortedValueNames(unnamed_proto.sos1_constraints()),
ElementsAre(""));
EXPECT_THAT(SortedValueNames(unnamed_proto.sos2_constraints()),
ElementsAre(""));
EXPECT_THAT(SortedValueNames(unnamed_proto.second_order_cone_constraints()),
ElementsAre(""));
}
}
TEST(ModelDeathTest, ColumnNonzerosOtherModel) {
Model model_a("a");
Model model_b("b");
const Variable b_x = model_b.AddVariable("x");
EXPECT_DEATH(model_a.ColumnNonzeros(b_x),
internal::kObjectsFromOtherModelStorage);
}
TEST(ModelDeathTest, RowNonzerosOtherModel) {
Model model_a("a");
Model model_b("b");
const LinearConstraint b_c = model_b.AddLinearConstraint("c");
EXPECT_DEATH(model_a.RowNonzeros(b_c),
internal::kObjectsFromOtherModelStorage);
}
TEST_F(ModelingTest, DeleteVariable) {
model_.DeleteVariable(x_);
EXPECT_EQ(model_.num_variables(), 1);
EXPECT_EQ(model_.next_variable_id(), 2);
EXPECT_FALSE(model_.has_variable(0));
EXPECT_TRUE(model_.has_variable(1));
EXPECT_THAT(model_.Variables(), UnorderedElementsAre(y_));
EXPECT_THAT(model_.RowNonzeros(c_), UnorderedElementsAre(y_));
const BoundedLinearExpression c_bounded_expr = c_.AsBoundedLinearExpression();
// TODO(b/171883688): we should use expression matchers.
EXPECT_DOUBLE_EQ(c_bounded_expr.lower_bound, -kInf);
EXPECT_DOUBLE_EQ(c_bounded_expr.upper_bound, 1.5);
EXPECT_THAT(c_bounded_expr.expression.terms(),
UnorderedElementsAre(Pair(y_, 1.0)));
}
TEST_F(ModelingTest, DeleteLinearConstraint) {
model_.DeleteLinearConstraint(c_);
EXPECT_EQ(model_.num_linear_constraints(), 1);
EXPECT_EQ(model_.next_linear_constraint_id(), 2);
EXPECT_FALSE(model_.has_linear_constraint(0));
EXPECT_TRUE(model_.has_linear_constraint(1));
EXPECT_THAT(model_.LinearConstraints(), UnorderedElementsAre(d_));
}
TEST_F(ModelingTest, ExportModel) {
ASSERT_OK_AND_ASSIGN(const ModelProto expected,
ParseTextProto<ModelProto>(R"pb(
name: "math_opt_model"
variables {
ids: [ 0, 1 ]
lower_bounds: [ -inf, 0.0 ]
upper_bounds: [ inf, 1.0 ]
integers: [ false, true ]
names: [ "x", "y" ]
}
objective {
offset: 3.5
maximize: true
linear_coefficients: {
ids: [ 1 ]
values: [ 2.0 ]
}
}
linear_constraints {
ids: [ 0, 1 ]
lower_bounds: [ -inf, 0.5 ]
upper_bounds: [ 1.5, inf ]
names: [ "c", "d" ]
}
linear_constraint_matrix {
row_ids: [ 0, 0, 1 ]
column_ids: [ 0, 1, 1 ]
coefficients: [ 1.0, 1.0, 2.0 ]
}
)pb"));
EXPECT_THAT(model_.ExportModel(), EquivToProto(expected));
}
TEST(ModelTest, AddBoundedLinearConstraint) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const LinearConstraint c =
model.AddLinearConstraint(3 <= 2 * x - y + 2 <= 5, "c");
EXPECT_EQ(c.coefficient(x), 2.0);
EXPECT_EQ(c.coefficient(y), -1.0);
EXPECT_EQ(c.lower_bound(), 3.0 - 2.0);
EXPECT_EQ(c.upper_bound(), 5.0 - 2.0);
}
TEST(ModelTest, AddEqualityLinearConstraint) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const LinearConstraint c = model.AddLinearConstraint(2 * x - 5 == x + y, "c");
EXPECT_EQ(c.coefficient(x), 1.0);
EXPECT_EQ(c.coefficient(y), -1.0);
EXPECT_EQ(c.lower_bound(), 5.0);
EXPECT_EQ(c.upper_bound(), 5.0);
}
TEST(ModelTest, AddVariablesEqualityLinearConstraint) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const LinearConstraint c = model.AddLinearConstraint(x == y, "c");
EXPECT_EQ(c.coefficient(x), 1.0);
EXPECT_EQ(c.coefficient(y), -1.0);
EXPECT_EQ(c.lower_bound(), 0.0);
EXPECT_EQ(c.upper_bound(), 0.0);
}
TEST(ModelTest, AddLowerBoundedLinearConstraint) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const LinearConstraint c = model.AddLinearConstraint(3 <= x - 1, "c");
EXPECT_EQ(c.coefficient(x), 1.0);
EXPECT_EQ(c.coefficient(y), 0.0);
EXPECT_EQ(c.lower_bound(), 3.0 - -1.0);
EXPECT_EQ(c.upper_bound(), kInf);
}
TEST(ModelTest, AddUpperBoundedLinearConstraint) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const LinearConstraint c = model.AddLinearConstraint(y <= 5, "c");
EXPECT_EQ(c.coefficient(x), 0.0);
EXPECT_EQ(c.coefficient(y), 1.0);
EXPECT_EQ(c.lower_bound(), -kInf);
EXPECT_EQ(c.upper_bound(), 5.0);
}
TEST(ModelDeathTest, AddLinearConstraintOtherModel) {
Model model_a("a");
Model model_b("b");
const Variable b_x = model_b.AddVariable("x");
const Variable b_y = model_b.AddVariable("y");
EXPECT_DEATH(model_a.AddLinearConstraint(2 <= 2 * b_x - b_y + 2, "c"),
internal::kObjectsFromOtherModelStorage);
}
TEST(ModelTest, AddLinearConstraintWithoutVariables) {
Model model;
// Here we test a corner case that may not be very useful in practice: the
// case of a bounded LinearExpression that have no terms but its offset.
//
// We want to make sure the code don't assume all LinearExpression have a
// non-null storage().
const LinearConstraint c =
model.AddLinearConstraint(LinearExpression(3) <= 5, "c");
EXPECT_EQ(c.lower_bound(), -kInf);
EXPECT_EQ(c.upper_bound(), 2.0);
}
TEST(ModelTest, ObjectiveAccessors) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
model.set_maximize();
model.set_objective_offset(3.5);
model.set_objective_coefficient(y, 2.0);
const Model& const_model = model;
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x), 0.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y), 2.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x, x), 0.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x, y), 0.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y, x), 0.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y, y), 0.0);
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, x));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(y, x));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(y, y));
EXPECT_DOUBLE_EQ(const_model.objective_offset(), 3.5);
EXPECT_TRUE(const_model.is_maximize());
// TODO(b/171883688): we should use expression matchers.
EXPECT_THAT(const_model.ObjectiveAsLinearExpression().terms(),
UnorderedElementsAre(Pair(y, 2.0)));
EXPECT_DOUBLE_EQ(const_model.ObjectiveAsLinearExpression().offset(), 3.5);
EXPECT_THAT(const_model.ObjectiveAsQuadraticExpression().quadratic_terms(),
IsEmpty());
EXPECT_THAT(const_model.ObjectiveAsQuadraticExpression().linear_terms(),
UnorderedElementsAre(Pair(y, 2.0)));
EXPECT_DOUBLE_EQ(const_model.ObjectiveAsQuadraticExpression().offset(), 3.5);
// Now we add a quadratic term
model.set_objective_coefficient(x, y, 3.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x), 0.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y), 2.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x, x), 0.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x, y), 3.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y, x), 3.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y, y), 0.0);
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, x));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(x, y));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y, x));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(y, y));
EXPECT_DOUBLE_EQ(const_model.objective_offset(), 3.5);
EXPECT_TRUE(const_model.is_maximize());
// TODO(b/171883688): we should use expression matchers.
EXPECT_THAT(const_model.ObjectiveAsQuadraticExpression().quadratic_terms(),
UnorderedElementsAre(Pair(QuadraticTermKey(x, y), 3.0)));
EXPECT_THAT(const_model.ObjectiveAsQuadraticExpression().linear_terms(),
UnorderedElementsAre(Pair(y, 2.0)));
EXPECT_DOUBLE_EQ(const_model.ObjectiveAsQuadraticExpression().offset(), 3.5);
}
TEST(ModelDeathTest, ObjectiveAsLinearExpressionWhenObjectiveIsQuadratic) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
model.set_objective_coefficient(x, y, 3.0);
EXPECT_DEATH(model.ObjectiveAsLinearExpression(), "quadratic terms");
}
TEST(ModelTest, AddToObjective) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
model.set_maximize();
model.set_objective_offset(3.5);
model.set_objective_coefficient(y, 2.0);
model.AddToObjective(5.0 * x - y + 7.0);
const Model& const_model = model;
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x), 5.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y), 1.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x, x), 0.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x, y), 0.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y, x), 0.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y, y), 0.0);
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(x));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, x));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(y, x));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(y, y));
EXPECT_DOUBLE_EQ(const_model.objective_offset(), 10.5);
EXPECT_TRUE(const_model.is_maximize());
model.AddToObjective(6.0 * x * y + 7.0 * y * y + 8.0 * x);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x), 13.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y), 1.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x, x), 0.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x, y), 6.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y, x), 6.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y, y), 7.0);
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(x));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, x));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(x, y));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y, x));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y, y));
EXPECT_DOUBLE_EQ(const_model.objective_offset(), 10.5);
EXPECT_TRUE(const_model.is_maximize());
}
TEST(ObjectiveDeathTest, AddToObjectiveOtherModel) {
Model model_a;
Model model_b;
const Variable x_b = model_b.AddVariable("x");
const Variable y_b = model_b.AddVariable("y");
EXPECT_DEATH(model_a.AddToObjective(5.0 * x_b - y_b + 7.0),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_a.AddToObjective(5.0 * x_b * x_b - y_b + 7.0),
internal::kObjectsFromOtherModelStorage);
}
TEST(ModelTest, AddToObjectiveConstant) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
model.set_maximize();
model.set_objective_offset(3.5);
model.set_objective_coefficient(y, 2.0);
model.AddToObjective(7.0);
const Model& const_model = model;
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(x), 0.0);
EXPECT_DOUBLE_EQ(const_model.objective_coefficient(y), 2.0);
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y));
EXPECT_DOUBLE_EQ(const_model.objective_offset(), 10.5);
EXPECT_TRUE(const_model.is_maximize());
}
TEST(ModelTest, MinimizeLinear) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const Variable z = model.AddVariable("z");
// Set a non trivial initial quadratic objective to test that SetObjective
// updates the offset and linear and quadratic coefficients, and resets to
// zero those coefficients not in the new objective.
model.set_maximize();
model.set_objective_offset(3.5);
model.set_objective_coefficient(y, 2.0);
model.set_objective_coefficient(z, 3.0);
model.set_objective_coefficient(x, z, 4.0);
model.Minimize(5.0 * x - y + 7.0);
const Model& const_model = model;
EXPECT_EQ(const_model.objective_coefficient(x), 5.0);
EXPECT_EQ(const_model.objective_coefficient(y), -1.0);
EXPECT_EQ(const_model.objective_coefficient(z), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, z), 0.0);
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(x));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(z));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, z));
EXPECT_DOUBLE_EQ(const_model.objective_offset(), 7.0);
EXPECT_FALSE(const_model.is_maximize());
}
TEST(ModelTest, MinimizeQuadratic) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const Variable z = model.AddVariable("z");
// Set a non trivial initial quadratic objective to test that SetObjective
// updates the offset and linear and quadratic coefficients, and resets to
// zero those coefficients not in the new objective.
model.set_maximize();
model.set_objective_offset(3.5);
model.set_objective_coefficient(y, 2.0);
model.set_objective_coefficient(z, 3.0);
model.set_objective_coefficient(x, z, 4.0);
model.Minimize(5.0 * x * y - y + 7.0);
const Model& const_model = model;
EXPECT_EQ(const_model.objective_coefficient(y), -1.0);
EXPECT_EQ(const_model.objective_coefficient(z), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, y), 5.0);
EXPECT_EQ(const_model.objective_coefficient(x, z), 0.0);
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(z));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(x, y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, z));
EXPECT_DOUBLE_EQ(const_model.objective_offset(), 7.0);
EXPECT_FALSE(const_model.is_maximize());
}
TEST(ModelDeathTest, MinimizeOtherModel) {
Model model_a;
Model model_b;
const Variable x_b = model_b.AddVariable("x");
const Variable y_b = model_b.AddVariable("y");
EXPECT_DEATH(model_a.Minimize(5.0 * x_b - y_b + 7.0),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_a.Minimize(5.0 * x_b * y_b - y_b + 7.0),
internal::kObjectsFromOtherModelStorage);
}
TEST(ModelTest, MaximizeLinear) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const Variable z = model.AddVariable("z");
// Set a non trivial initial quadratic objective to test that SetObjective
// updates the offset and linear and quadratic coefficients, and resets to
// zero those coefficients not in the new objective.
model.set_minimize();
model.set_objective_offset(3.5);
model.set_objective_coefficient(y, 2.0);
model.set_objective_coefficient(z, 3.0);
model.set_objective_coefficient(x, z, 4.0);
model.Maximize(5.0 * x - y + 7.0);
const Model& const_model = model;
EXPECT_EQ(const_model.objective_coefficient(x), 5.0);
EXPECT_EQ(const_model.objective_coefficient(y), -1.0);
EXPECT_EQ(const_model.objective_coefficient(z), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, z), 0.0);
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(x));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(z));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, z));
EXPECT_DOUBLE_EQ(const_model.objective_offset(), 7.0);
EXPECT_TRUE(const_model.is_maximize());
}
TEST(ModelTest, MaximizeQuadratic) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const Variable z = model.AddVariable("z");
// Set a non trivial initial quadratic objective to test that SetObjective
// updates the offset and linear and quadratic coefficients, and resets to
// zero those coefficients not in the new objective.
model.set_minimize();
model.set_objective_offset(3.5);
model.set_objective_coefficient(y, 2.0);
model.set_objective_coefficient(z, 3.0);
model.set_objective_coefficient(x, z, 4.0);
model.Maximize(5.0 * x * y - y + 7.0);
const Model& const_model = model;
EXPECT_EQ(const_model.objective_coefficient(y), -1.0);
EXPECT_EQ(const_model.objective_coefficient(z), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, y), 5.0);
EXPECT_EQ(const_model.objective_coefficient(x, z), 0.0);
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(z));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(x, y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, z));
EXPECT_DOUBLE_EQ(const_model.objective_offset(), 7.0);
EXPECT_TRUE(const_model.is_maximize());
}
TEST(ModelDeathTest, MaximizeOtherModel) {
Model model_a;
Model model_b;
const Variable x_b = model_b.AddVariable("x");
const Variable y_b = model_b.AddVariable("y");
EXPECT_DEATH(model_a.Maximize(5.0 * x_b - y_b + 7.0),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_a.Maximize(5.0 * x_b * y_b - y_b + 7.0),
internal::kObjectsFromOtherModelStorage);
}
TEST(ModelTest, SetObjectiveLinear) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const Variable z = model.AddVariable("z");
// Set a non trivial initial quadratic objective to test that SetObjective
// updates the offset and linear and quadratic coefficients, and resets to
// zero those coefficients not in the new objective.
model.set_maximize();
model.set_objective_offset(3.5);
model.set_objective_coefficient(y, 2.0);
model.set_objective_coefficient(z, 3.0);
model.set_objective_coefficient(x, z, 4.0);
model.SetObjective(5.0 * x - y + 7.0, false);
const Model& const_model = model;
EXPECT_EQ(const_model.objective_coefficient(x), 5.0);
EXPECT_EQ(const_model.objective_coefficient(y), -1.0);
EXPECT_EQ(const_model.objective_coefficient(z), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, z), 0.0);
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(x));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(z));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, z));
EXPECT_EQ(const_model.objective_offset(), 7.0);
EXPECT_FALSE(const_model.is_maximize());
}
TEST(ModelTest, SetObjectiveQuadratic) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddVariable("y");
const Variable z = model.AddVariable("z");
// Set a non trivial initial quadratic objective to test that SetObjective
// updates the offset and linear and quadratic coefficients, and resets to
// zero those coefficients not in the new objective.
model.set_maximize();
model.set_objective_offset(3.5);
model.set_objective_coefficient(y, 2.0);
model.set_objective_coefficient(z, 3.0);
model.set_objective_coefficient(x, z, 4.0);
model.SetObjective(5.0 * x * y - y + 7.0, false);
const Model& const_model = model;
EXPECT_EQ(const_model.objective_coefficient(y), -1.0);
EXPECT_EQ(const_model.objective_coefficient(z), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, y), 5.0);
EXPECT_EQ(const_model.objective_coefficient(x, z), 0.0);
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(z));
EXPECT_TRUE(const_model.is_objective_coefficient_nonzero(x, y));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, z));
EXPECT_EQ(const_model.objective_offset(), 7.0);
EXPECT_FALSE(const_model.is_maximize());
}
TEST(ModelDeathTest, SetObjectiveOtherModel) {
Model model_a;
Model model_b;
const Variable x_b = model_b.AddVariable("x");
const Variable y_b = model_b.AddVariable("y");
EXPECT_DEATH(model_a.SetObjective(5.0 * x_b + 7.0, true),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_a.SetObjective(5.0 * x_b * y_b + 7.0, true),
internal::kObjectsFromOtherModelStorage);
}
TEST(ModelTest, SetObjectiveAsConstant) {
Model model;
const Variable x = model.AddVariable("x");
// Set a non trivial initial quadratic objective to test that SetObjective
// updates the offset and linear and quadratic coefficients, and resets to
// zero those coefficients not in the new objective.
model.set_maximize();
model.set_objective_offset(3.5);
model.set_objective_coefficient(x, 2.0);
model.set_objective_coefficient(x, x, 3.0);
model.SetObjective(7.0, false);
const Model& const_model = model;
EXPECT_EQ(const_model.objective_coefficient(x), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x));
EXPECT_FALSE(const_model.is_objective_coefficient_nonzero(x, x));
EXPECT_EQ(const_model.objective_offset(), 7.0);
EXPECT_FALSE(const_model.is_maximize());
}
// TODO(b/207482515): Add tests against expression constructor counters
TEST(ModelTest, ObjectiveAsDouble) {
Model model;
const Variable x = model.AddContinuousVariable(0, 1, "x");
model.Maximize(3.0);
const Model& const_model = model;
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 3.0);
EXPECT_EQ(const_model.objective_coefficient(x), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
model.Minimize(4.0);
EXPECT_FALSE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 4.0);
EXPECT_EQ(const_model.objective_coefficient(x), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
model.SetObjective(5.0, true);
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 5.0);
EXPECT_EQ(const_model.objective_coefficient(x), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
}
// TODO(b/207482515): Add tests against expression constructor counters
TEST(ModelTest, ObjectiveAsVariable) {
Model model;
const Variable x = model.AddContinuousVariable(0, 1, "x");
model.Maximize(x);
const Model& const_model = model;
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x), 1.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
model.Minimize(x);
EXPECT_FALSE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x), 1.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
model.SetObjective(x, true);
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x), 1.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
}
// TODO(b/207482515): Add tests against expression constructor counters
TEST(ModelTest, ObjectiveAsLinearTerm) {
Model model;
const Variable x = model.AddContinuousVariable(0, 1, "x");
model.Maximize(3.0 * x);
const Model& const_model = model;
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x), 3.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
model.Minimize(4.0 * x);
EXPECT_FALSE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x), 4.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
model.SetObjective(5.0 * x, true);
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x), 5.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
}
// TODO(b/207482515): Add tests against expression constructor counters
TEST(ModelTest, ObjectiveAsLinearExpression) {
Model model;
const Variable x = model.AddContinuousVariable(0, 1, "x");
model.Maximize(3.0 * x + 4);
const Model& const_model = model;
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 4.0);
EXPECT_EQ(const_model.objective_coefficient(x), 3.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
model.Minimize(5.0 * x + 6);
EXPECT_FALSE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 6.0);
EXPECT_EQ(const_model.objective_coefficient(x), 5.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
model.SetObjective(7.0 * x + 8, true);
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 8.0);
EXPECT_EQ(const_model.objective_coefficient(x), 7.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 0.0);
}
// TODO(b/207482515): Add tests against expression constructor counters
TEST(ModelTest, ObjectiveAsQuadraticTerm) {
Model model;
const Variable x = model.AddContinuousVariable(0, 1, "x");
model.Maximize(3.0 * x * x);
const Model& const_model = model;
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 3.0);
model.Minimize(4.0 * x * x);
EXPECT_FALSE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 4.0);
model.SetObjective(5.0 * x * x, true);
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x), 0.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 5.0);
}
// TODO(b/207482515): Add tests against expression constructor counters
TEST(ModelTest, ObjectiveAsQuadraticExpression) {
Model model;
const Variable x = model.AddContinuousVariable(0, 1, "x");
model.Maximize(3.0 * x * x + 4 * x + 5);
const Model& const_model = model;
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 5.0);
EXPECT_EQ(const_model.objective_coefficient(x), 4.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 3.0);
model.Minimize(6.0 * x * x + 7 * x + 8);
EXPECT_FALSE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), 8.0);
EXPECT_EQ(const_model.objective_coefficient(x), 7.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 6.0);
model.SetObjective(9.0 * x * x - x - 2, true);
EXPECT_TRUE(const_model.is_maximize());
EXPECT_EQ(const_model.objective_offset(), -2.0);
EXPECT_EQ(const_model.objective_coefficient(x), -1.0);
EXPECT_EQ(const_model.objective_coefficient(x, x), 9.0);
}
TEST(ModelTest, NonzeroVariablesInLinearObjective) {
Model model;
model.AddVariable();
const Variable y = model.AddVariable();
const Variable z = model.AddVariable();
model.Minimize(3.0 * y + 0.0 * z + 1.0 * z * z);
EXPECT_THAT(model.NonzeroVariablesInLinearObjective(),
UnorderedElementsAre(y));
}
TEST(ModelTest, NonzeroVariablesInQuadraticObjective) {
Model model;
model.AddVariable();
const Variable y = model.AddVariable();
const Variable z = model.AddVariable();
const Variable u = model.AddVariable();
const Variable v = model.AddVariable();
model.Minimize(3.0 * y + 0.0 * z + 1.0 * u * v);
EXPECT_THAT(model.NonzeroVariablesInQuadraticObjective(),
UnorderedElementsAre(u, v));
}
TEST(UpdateTrackerTest, ExportModel) {
Model model;
model.AddVariable("x");
std::unique_ptr<UpdateTracker> update_tracker = model.NewUpdateTracker();
EXPECT_THAT(update_tracker->ExportModel(),
IsOkAndHolds(EquivToProto(R"pb(variables {
ids: [ 0 ]
lower_bounds: [ -inf ]
upper_bounds: [ inf ]
integers: [ false ]
names: [ "x" ]
})pb")));
}
TEST(UpdateTrackerTest, ExportModelUpdate) {
Model model;
const Variable x = model.AddVariable("x");
std::unique_ptr<UpdateTracker> update_tracker = model.NewUpdateTracker();
model.set_integer(x);
EXPECT_THAT(update_tracker->ExportModelUpdate(),
IsOkAndHolds(Optional(EquivToProto(R"pb(variable_updates {
integers {
ids: [ 0 ]
values: [ true ]
}
})pb"))));
}
TEST(ModelTest, ExportModelUpdate_RemoveNames) {
Model model("my_model");
std::unique_ptr<UpdateTracker> tracker = model.NewUpdateTracker();
const Variable x = model.AddVariable("x");
const Variable y = model.AddBinaryVariable("y");
model.Maximize(x);
Objective b = model.AddAuxiliaryObjective(1, "objB");
model.set_objective_offset(b, 2.0);
model.AddLinearConstraint(x <= 1.0, "lin_con");
model.AddQuadraticConstraint(x * x <= 1.0, "quad_con");
model.AddIndicatorConstraint(y, x >= 3.0, /*activate_on_zero=*/false,
"ind_con");
model.AddSos1Constraint({y, 1.0 - y}, {1.0, 1.0}, "sos1");
model.AddSos2Constraint({y, 1.0 - y}, {1.0, 1.0}, "sos2");
model.AddSecondOrderConeConstraint({x + y}, 1.0, "soc");
{
ASSERT_OK_AND_ASSIGN(const std::optional<ModelUpdateProto> update,
tracker->ExportModelUpdate(/*remove_names=*/false));
ASSERT_TRUE(update.has_value());
EXPECT_THAT(update->new_variables().names(), ElementsAre("x", "y"));
EXPECT_THAT(SortedValueNames(
update->auxiliary_objectives_updates().new_objectives()),
ElementsAre("objB"));
EXPECT_THAT(update->new_linear_constraints().names(),
ElementsAre("lin_con"));
EXPECT_THAT(SortedValueNames(
update->quadratic_constraint_updates().new_constraints()),
ElementsAre("quad_con"));
EXPECT_THAT(SortedValueNames(
update->indicator_constraint_updates().new_constraints()),
ElementsAre("ind_con"));
EXPECT_THAT(
SortedValueNames(update->sos1_constraint_updates().new_constraints()),
ElementsAre("sos1"));
EXPECT_THAT(
SortedValueNames(update->sos2_constraint_updates().new_constraints()),
ElementsAre("sos2"));
EXPECT_THAT(
SortedValueNames(
update->second_order_cone_constraint_updates().new_constraints()),
ElementsAre("soc"));
}
{
ASSERT_OK_AND_ASSIGN(const std::optional<ModelUpdateProto> update,
tracker->ExportModelUpdate(/*remove_names=*/true));
ASSERT_TRUE(update.has_value());
EXPECT_THAT(update->new_variables().names(), IsEmpty());
EXPECT_THAT(SortedValueNames(
update->auxiliary_objectives_updates().new_objectives()),
ElementsAre(""));
EXPECT_THAT(update->new_linear_constraints().names(), IsEmpty());
EXPECT_THAT(SortedValueNames(
update->quadratic_constraint_updates().new_constraints()),
ElementsAre(""));
EXPECT_THAT(SortedValueNames(
update->indicator_constraint_updates().new_constraints()),
ElementsAre(""));
EXPECT_THAT(
SortedValueNames(update->sos1_constraint_updates().new_constraints()),
ElementsAre(""));
EXPECT_THAT(
SortedValueNames(update->sos2_constraint_updates().new_constraints()),
ElementsAre(""));
EXPECT_THAT(
SortedValueNames(
update->second_order_cone_constraint_updates().new_constraints()),
ElementsAre(""));
}
}
TEST(UpdateTrackerTest, Checkpoint) {
Model model;
std::unique_ptr<UpdateTracker> update_tracker = model.NewUpdateTracker();
const Variable x = model.AddVariable("x");
ASSERT_OK(update_tracker->AdvanceCheckpoint());
model.set_integer(x);
EXPECT_THAT(update_tracker->ExportModelUpdate(),
IsOkAndHolds(Optional(EquivToProto(R"pb(variable_updates {
integers {
ids: [ 0 ]
values: [ true ]
}
})pb"))));
}
TEST(UpdateTrackerTest, DestructionAfterModelDestruction) {
auto model = std::make_unique<Model>();
std::unique_ptr<UpdateTracker> update_tracker = model->NewUpdateTracker();
// Destroy the model first.
model = nullptr;
// Then destroy the tracker.
update_tracker = nullptr;
}
TEST(UpdateTrackerTest, ExportModelAfterModelDestruction) {
auto model = std::make_unique<Model>();
const std::unique_ptr<UpdateTracker> update_tracker =
model->NewUpdateTracker();
model = nullptr;
EXPECT_THAT(update_tracker->ExportModel(),
StatusIs(absl::StatusCode::kInvalidArgument,
internal::kModelIsDestroyed));
}
TEST(UpdateTrackerTest, ExportModelUpdateAfterModelDestruction) {
auto model = std::make_unique<Model>();
const std::unique_ptr<UpdateTracker> update_tracker =
model->NewUpdateTracker();
model = nullptr;
EXPECT_THAT(update_tracker->ExportModelUpdate(),
StatusIs(absl::StatusCode::kInvalidArgument,
internal::kModelIsDestroyed));
}
TEST(UpdateTrackerTest, CheckpointAfterModelDestruction) {
auto model = std::make_unique<Model>();
const std::unique_ptr<UpdateTracker> update_tracker =
model->NewUpdateTracker();
model = nullptr;
EXPECT_THAT(update_tracker->AdvanceCheckpoint(),
StatusIs(absl::StatusCode::kInvalidArgument,
internal::kModelIsDestroyed));
}
TEST(OstreamTest, EmptyModel) {
const Model model("empty_model");
EXPECT_EQ(StreamToString(model),
R"model(Model empty_model:
Objective:
minimize 0
Linear constraints:
Variables:
)model");
}
TEST(OstreamTest, MinimizingLinearObjective) {
Model model("minimize_linear_objective");
const Variable noname = model.AddVariable();
const Variable x = model.AddVariable("x");
const Variable z = model.AddContinuousVariable(-15, 17, "z");
const Variable n = model.AddIntegerVariable(7, 32, "n");
const Variable t = model.AddContinuousVariable(-kInf, 7, "t");
const Variable u = model.AddContinuousVariable(-4, kInf, "u");
const Variable b = model.AddBinaryVariable("b");
const Variable yy = model.AddVariable("y_y");
model.AddLinearConstraint(z + x == 9, "c1");
model.AddLinearConstraint(-3 * n + 2 * t + 2 >= 8);
model.AddLinearConstraint(7 * u - 2 * b + 7 * yy - z <= 32, "c2");
model.AddLinearConstraint(78 <= yy + 4 * noname <= 256, "c3");
model.Minimize(45 * z + 3 * u);
EXPECT_EQ(StreamToString(model),
R"model(Model minimize_linear_objective:
Objective:
minimize 45*z + 3*u
Linear constraints:
c1: x + z = 9
__lin_con#1__: -3*n + 2*t 6
c2: -z + 7*u - 2*b + 7*y_y 32
c3: 78 4*__var#0__ + y_y 256
Variables:
__var#0__ in (-, +)
x in (-, +)
z in [-15, 17]
n (integer) in [7, 32]
t in (-, 7]
u in [-4, +)
b (binary)
y_y in (-, +)
)model");
}
TEST(OstreamTest, MaximizingLinearObjective) {
Model model("maximize_linear_objective");
const Variable x = model.AddVariable("x");
const Variable y = model.AddContinuousVariable(1, 5, "y");
model.AddLinearConstraint(x + y == 9, "c1");
model.Maximize(-2 * x + y);
EXPECT_EQ(StreamToString(model),
R"model(Model maximize_linear_objective:
Objective:
maximize -2*x + y
Linear constraints:
c1: x + y = 9
Variables:
x in (-, +)
y in [1, 5]
)model");
}
TEST(OstreamTest, WithoutName) {
Model model;
const Variable x = model.AddVariable("x");
const Variable y = model.AddContinuousVariable(1, 5, "y");
model.AddLinearConstraint(x + y == 9, "c1");
model.Maximize(-2 * x + y);
EXPECT_EQ(StreamToString(model),
R"model(Model:
Objective:
maximize -2*x + y
Linear constraints:
c1: x + y = 9
Variables:
x in (-, +)
y in [1, 5]
)model");
}
TEST(OstreamTest, MinimizingQuadraticObjective) {
Model model("minimize_quadratic_objective");
const Variable x = model.AddVariable("x");
const Variable y = model.AddContinuousVariable(1, 5, "y");
model.AddLinearConstraint(x + y == 9, "c1");
model.Minimize(-2 * x + y + 7 * y * x - 5 * x * x);
EXPECT_EQ(StreamToString(model),
R"model(Model minimize_quadratic_objective:
Objective:
minimize -5*x² + 7*x*y - 2*x + y
Linear constraints:
c1: x + y = 9
Variables:
x in (-, +)
y in [1, 5]
)model");
}
TEST(OstreamTest, FloatingPointRoundTripVariableBounds) {
Model model("minimize_linear_objective");
model.AddContinuousVariable(kRoundTripTestNumber, 17, "x");
model.AddContinuousVariable(-kInf, kRoundTripTestNumber, "y");
EXPECT_THAT(
StreamToString(model),
AllOf(
HasSubstr(absl::StrCat("x in [", kRoundTripTestNumberStr, ", 17]")),
HasSubstr(absl::StrCat("y in (-∞, ", kRoundTripTestNumberStr, "]"))));
}
// -------------------------- Auxiliary objectives -----------------------------
// {max x, min 3, max 2y + 1} with priorities {2, 3, 5}
// s.t. x, y unbounded
class SimpleAuxiliaryObjectiveTest : public testing::Test {
protected:
SimpleAuxiliaryObjectiveTest()
: model_("auxiliary_objectives"),
x_(model_.AddVariable("x")),
y_(model_.AddVariable("y")),
primary_(model_.primary_objective()),
secondary_(model_.AddMinimizationObjective(3.0, 3, "secondary")),
tertiary_(model_.AddMaximizationObjective(0.0, 5, "tertiary")) {
// Maximize and Minimize wrap SetObjective, hence this tests them.
model_.Maximize(primary_, x_);
model_.set_objective_priority(primary_, 2);
// We also want to exercise AddToObjective.
model_.set_maximize(tertiary_);
model_.AddToObjective(tertiary_, 2.0 * y_);
model_.AddToObjective(tertiary_, 1.0);
}
Model model_;
const Variable x_;
const Variable y_;
const Objective primary_;
const Objective secondary_;
const Objective tertiary_;
};
TEST_F(SimpleAuxiliaryObjectiveTest, Properties) {
EXPECT_EQ(model_.num_auxiliary_objectives(), 2);
EXPECT_EQ(model_.next_auxiliary_objective_id(), 2);
EXPECT_TRUE(model_.has_auxiliary_objective(0));
EXPECT_TRUE(model_.has_auxiliary_objective(1));
EXPECT_FALSE(model_.has_auxiliary_objective(2));
EXPECT_FALSE(model_.has_auxiliary_objective(3));
EXPECT_FALSE(model_.has_auxiliary_objective(-1));
EXPECT_THAT(model_.AuxiliaryObjectives(),
UnorderedElementsAre(secondary_, tertiary_));
EXPECT_THAT(model_.SortedAuxiliaryObjectives(),
ElementsAre(secondary_, tertiary_));
EXPECT_EQ(model_.auxiliary_objective(secondary_.id().value()).name(),
"secondary");
EXPECT_EQ(model_.auxiliary_objective(tertiary_.id().value()).name(),
"tertiary");
EXPECT_EQ(model_.auxiliary_objective(secondary_.typed_id().value()).name(),
"secondary");
EXPECT_EQ(model_.auxiliary_objective(tertiary_.typed_id().value()).name(),
"tertiary");
}
TEST(AuxiliaryObjectiveTest, SenseSetting) {
Model model;
const Objective o = model.AddAuxiliaryObjective(3, "o");
// set_maximize
ASSERT_FALSE(o.maximize());
model.set_maximize(o);
ASSERT_TRUE(o.maximize());
// set_minimize
model.set_minimize(o);
ASSERT_FALSE(o.maximize());
model.set_is_maximize(o, true);
EXPECT_TRUE(o.maximize());
}
TEST(AuxiliaryObjectiveTest, PrioritySetting) {
Model model;
const Objective o = model.AddAuxiliaryObjective(3, "o");
ASSERT_EQ(o.priority(), 3);
model.set_objective_priority(o, 4);
EXPECT_EQ(o.priority(), 4);
}
TEST(AuxiliaryObjectiveTest, OffsetSetting) {
Model model;
const Objective o = model.AddAuxiliaryObjective(3, "o");
ASSERT_EQ(o.offset(), 0.0);
model.set_objective_offset(o, 4.0);
EXPECT_EQ(o.offset(), 4.0);
}
TEST(AuxiliaryObjectiveTest, LinearCoefficientSetting) {
Model model;
const Variable x = model.AddVariable("x");
const Objective o = model.AddAuxiliaryObjective(3, "o");
ASSERT_EQ(o.coefficient(x), 0.0);
model.set_objective_coefficient(o, x, 3.0);
EXPECT_EQ(o.coefficient(x), 3.0);
}
TEST_F(SimpleAuxiliaryObjectiveTest, DeleteAuxiliaryObjective) {
model_.DeleteAuxiliaryObjective(secondary_);
EXPECT_EQ(model_.num_auxiliary_objectives(), 1);
EXPECT_EQ(model_.next_auxiliary_objective_id(), 2);
EXPECT_FALSE(model_.has_auxiliary_objective(0));
EXPECT_TRUE(model_.has_auxiliary_objective(1));
EXPECT_THAT(model_.AuxiliaryObjectives(), UnorderedElementsAre(tertiary_));
}
TEST(AuxiliaryObjectiveTest, NewObjectiveMethods) {
Model model;
const Variable x = model.AddVariable("x");
{
const Objective a = model.AddAuxiliaryObjective(1);
model.Maximize(a, x + 2.0);
EXPECT_TRUE(a.maximize());
EXPECT_EQ(a.offset(), 2.0);
EXPECT_EQ(a.coefficient(x), 1.0);
}
{
const Objective b = model.AddAuxiliaryObjective(2);
model.Minimize(b, x + 2.0);
EXPECT_FALSE(b.maximize());
EXPECT_EQ(b.offset(), 2.0);
EXPECT_EQ(b.coefficient(x), 1.0);
}
{
const Objective c = model.AddMaximizationObjective(x + 2.0, 3);
EXPECT_TRUE(c.maximize());
EXPECT_EQ(c.offset(), 2.0);
EXPECT_EQ(c.coefficient(x), 1.0);
}
{
const Objective d = model.AddMinimizationObjective(x + 2.0, 4);
EXPECT_FALSE(d.maximize());
EXPECT_EQ(d.offset(), 2.0);
EXPECT_EQ(d.coefficient(x), 1.0);
}
{
const Objective e = model.AddAuxiliaryObjective(x + 2.0, true, 4);
EXPECT_TRUE(e.maximize());
EXPECT_EQ(e.offset(), 2.0);
EXPECT_EQ(e.coefficient(x), 1.0);
}
{
const Objective f = model.AddMinimizationObjective(7.0 * x - 3.0, 4);
model.AddToObjective(f, -6.0 * x);
model.AddToObjective(f, 5.0);
EXPECT_FALSE(f.maximize());
EXPECT_EQ(f.offset(), 2.0);
EXPECT_EQ(f.coefficient(x), 1.0);
}
}
TEST_F(SimpleAuxiliaryObjectiveTest, ExportModel) {
EXPECT_THAT(
model_.ExportModel(), EquivToProto(R"pb(
name: "auxiliary_objectives"
variables {
ids: [ 0, 1 ]
lower_bounds: [ -inf, -inf ]
upper_bounds: [ inf, inf ]
integers: [ false, false ]
names: [ "x", "y" ]
}
objective {
maximize: true
linear_coefficients {
ids: [ 0 ]
values: [ 1.0 ]
}
priority: 2
}
auxiliary_objectives {
key: 0
value { maximize: false offset: 3.0 priority: 3 name: "secondary" }
}
auxiliary_objectives {
key: 1
value {
maximize: true
offset: 1.0
linear_coefficients {
ids: [ 1 ]
values: [ 2.0 ]
}
priority: 5
name: "tertiary"
}
}
)pb"));
}
TEST_F(SimpleAuxiliaryObjectiveTest, Streaming) {
EXPECT_EQ(StreamToString(model_),
R"model(Model auxiliary_objectives:
Objectives:
__primary_obj__ (priority 2): maximize x
secondary (priority 3): minimize 3
tertiary (priority 5): maximize 2*y + 1
Linear constraints:
Variables:
x in (-, +)
y in (-, +)
)model");
}
TEST(AuxiliaryObjectiveDeathTest, ObjectiveByIdOutOfBounds) {
Model model;
model.AddAuxiliaryObjective(1);
EXPECT_DEATH(model.auxiliary_objective(-1),
AllOf(HasSubstr("auxiliary objective"), HasSubstr("-1")));
EXPECT_DEATH(model.auxiliary_objective(2),
AllOf(HasSubstr("auxiliary objective"), HasSubstr("2")));
}
TEST(AuxiliaryObjectiveDeathTest, ObjectiveByIdDeleted) {
Model model;
const Objective o = model.AddAuxiliaryObjective(1, "o");
EXPECT_EQ(model.auxiliary_objective(o.id().value()).name(), "o");
model.DeleteAuxiliaryObjective(o);
EXPECT_DEATH(model.auxiliary_objective(o.id().value()),
AllOf(HasSubstr("auxiliary objective"), HasSubstr("0")));
}
TEST(AuxiliaryObjectiveDeathTest, DeletePrimaryObjective) {
Model model;
EXPECT_DEATH(model.DeleteAuxiliaryObjective(model.primary_objective()),
HasSubstr("primary objective"));
}
TEST(AuxiliaryObjectiveDeathTest, DoubleDeleteObjective) {
Model model;
const Objective o = model.AddAuxiliaryObjective(3, "o");
model.DeleteAuxiliaryObjective(o);
EXPECT_DEATH(model.DeleteAuxiliaryObjective(o),
HasSubstr("unrecognized auxiliary objective"));
}
TEST(AuxiliaryObjectiveDeathTest, DeleteInvalidObjective) {
Model model;
const Objective o =
Objective::Auxiliary(model.storage(), AuxiliaryObjectiveId(0));
EXPECT_DEATH(model.DeleteAuxiliaryObjective(o),
HasSubstr("unrecognized auxiliary objective"));
}
TEST(AuxiliaryObjectiveDeathTest, SetObjectiveOtherModel) {
Model model_a("a");
const Objective o_a = model_a.AddAuxiliaryObjective(3);
Model model_b("b");
const Variable x_b = model_b.AddVariable();
EXPECT_DEATH(model_a.SetObjective(o_a, x_b, false),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.SetObjective(o_a, x_b, false),
internal::kObjectsFromOtherModelStorage);
}
TEST(AuxiliaryObjectiveDeathTest, AddToObjectiveOtherModel) {
Model model_a("a");
const Objective o_a = model_a.AddAuxiliaryObjective(3);
Model model_b("b");
const Variable x_b = model_b.AddVariable();
EXPECT_DEATH(model_a.AddToObjective(o_a, x_b),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_b.AddToObjective(o_a, x_b),
internal::kObjectsFromOtherModelStorage);
}
TEST(AuxiliaryObjectiveTest, NonzeroVariablesInLinarObjective) {
Model model;
const Objective o = model.AddAuxiliaryObjective(3);
model.AddVariable();
const Variable y = model.AddVariable();
const Variable z = model.AddVariable();
model.set_objective_coefficient(o, y, 3.0);
model.set_objective_coefficient(o, z, 0.0);
EXPECT_THAT(model.NonzeroVariablesInLinearObjective(o),
UnorderedElementsAre(y));
}
// ------------------------- Quadratic constraints -----------------------------
// max 0
// s.t. x^2 + y^2 <= 1.0 (c)
// 2x*y + 3x >= 0.5 (d)
// x unbounded
// y in {0, 1}
class SimpleQuadraticConstraintTest : public testing::Test {
protected:
SimpleQuadraticConstraintTest()
: model_("quadratic_constraints"),
x_(model_.AddVariable("x")),
y_(model_.AddBinaryVariable("y")),
c_(model_.AddQuadraticConstraint(x_ * x_ + y_ * y_ <= 1.0, "c")),
d_(model_.AddQuadraticConstraint(2.0 * x_ * y_ + 3.0 * y_ >= 0.5,
"d")) {}
Model model_;
const Variable x_;
const Variable y_;
const QuadraticConstraint c_;
const QuadraticConstraint d_;
};
TEST_F(SimpleQuadraticConstraintTest, Properties) {
EXPECT_EQ(model_.num_quadratic_constraints(), 2);
EXPECT_EQ(model_.next_quadratic_constraint_id(), 2);
EXPECT_TRUE(model_.has_quadratic_constraint(0));
EXPECT_TRUE(model_.has_quadratic_constraint(1));
EXPECT_FALSE(model_.has_quadratic_constraint(2));
EXPECT_FALSE(model_.has_quadratic_constraint(3));
EXPECT_FALSE(model_.has_quadratic_constraint(-1));
EXPECT_THAT(model_.QuadraticConstraints(), UnorderedElementsAre(c_, d_));
EXPECT_THAT(model_.SortedQuadraticConstraints(), ElementsAre(c_, d_));
EXPECT_EQ(model_.quadratic_constraint(c_.id()).name(), "c");
EXPECT_EQ(model_.quadratic_constraint(d_.id()).name(), "d");
EXPECT_EQ(model_.quadratic_constraint(c_.typed_id()).name(), "c");
EXPECT_EQ(model_.quadratic_constraint(d_.typed_id()).name(), "d");
}
TEST_F(SimpleQuadraticConstraintTest, DeleteConstraint) {
model_.DeleteQuadraticConstraint(c_);
EXPECT_EQ(model_.num_quadratic_constraints(), 1);
EXPECT_EQ(model_.next_quadratic_constraint_id(), 2);
EXPECT_FALSE(model_.has_quadratic_constraint(0));
EXPECT_TRUE(model_.has_quadratic_constraint(1));
EXPECT_THAT(model_.QuadraticConstraints(), UnorderedElementsAre(d_));
}
TEST_F(SimpleQuadraticConstraintTest, ExportModel) {
EXPECT_THAT(model_.ExportModel(), EquivToProto(R"pb(
name: "quadratic_constraints"
variables {
ids: [ 0, 1 ]
lower_bounds: [ -inf, 0.0 ]
upper_bounds: [ inf, 1.0 ]
integers: [ false, true ]
names: [ "x", "y" ]
}
quadratic_constraints {
key: 0
value: {
lower_bound: -inf
upper_bound: 1.0
quadratic_terms {
row_ids: [ 0, 1 ]
column_ids: [ 0, 1 ]
coefficients: [ 1.0, 1.0 ]
}
name: "c"
}
}
quadratic_constraints {
key: 1
value: {
lower_bound: 0.5
upper_bound: inf
linear_terms {
ids: [ 1 ]
values: [ 3.0 ]
}
quadratic_terms {
row_ids: [ 0 ]
column_ids: [ 1 ]
coefficients: [ 2.0 ]
}
name: "d"
}
}
)pb"));
}
TEST_F(SimpleQuadraticConstraintTest, Streaming) {
EXPECT_EQ(StreamToString(model_),
R"model(Model quadratic_constraints:
Objective:
minimize 0
Linear constraints:
Quadratic constraints:
c: x² + y² 1
d: 2*x*y + 3*y 0.5
Variables:
x in (-, +)
y (binary)
)model");
}
TEST(QuadraticConstraintTest, AddQuadraticConstraintWithoutVariables) {
Model model;
// Here we test a corner case that may not be very useful in practice: the
// case of a bounded LinearExpression that have no terms but its offset.
//
// We want to make sure the code don't assume all LinearExpression have a
// non-null storage().
const QuadraticConstraint c =
model.AddQuadraticConstraint(BoundedQuadraticExpression(0.0, 1.0, 2.0));
EXPECT_EQ(c.lower_bound(), 1.0);
EXPECT_EQ(c.upper_bound(), 2.0);
EXPECT_THAT(c.NonzeroVariables(), IsEmpty());
}
TEST(QuadraticConstraintDeathTest, ConstraintByIdOutOfBounds) {
Model model;
model.AddQuadraticConstraint(BoundedQuadraticExpression(0, 0, 0));
EXPECT_DEATH(model.quadratic_constraint(-1),
AllOf(HasSubstr("quadratic constraint"), HasSubstr("-1")));
EXPECT_DEATH(model.quadratic_constraint(2),
AllOf(HasSubstr("quadratic constraint"), HasSubstr("2")));
}
TEST(QuadraticConstraintDeathTest, ConstraintByIdDeleted) {
Model model;
const QuadraticConstraint c =
model.AddQuadraticConstraint(BoundedQuadraticExpression(0, 0, 0), "c");
EXPECT_EQ(model.quadratic_constraint(c.id()).name(), "c");
model.DeleteQuadraticConstraint(c);
EXPECT_DEATH(model.quadratic_constraint(c.id()),
AllOf(HasSubstr("quadratic constraint"), HasSubstr("0")));
}
TEST(QuadraticConstraintDeathTest, AddConstraintOtherModel) {
Model model_a("a");
Model model_b("b");
const Variable b_x = model_b.AddVariable("x");
const Variable b_y = model_b.AddVariable("y");
EXPECT_DEATH(model_a.AddQuadraticConstraint(2 <= 2 * b_x * b_y + 2, "c"),
internal::kObjectsFromOtherModelStorage);
}
// --------------------- Second-order cone constraints -------------------------
// max 0
// s.t. ||{x, y}||_2 <= 1.0 (c)
// ||{1, 2x - y}||_2 <= 3y - 4 (d)
// x, y unbounded
class SimpleSecondOrderConeConstraintTest : public testing::Test {
protected:
SimpleSecondOrderConeConstraintTest()
: model_("soc_constraints"),
x_(model_.AddVariable("x")),
y_(model_.AddVariable("y")),
c_(model_.AddSecondOrderConeConstraint({x_, y_}, 1.0, "c")),
d_(model_.AddSecondOrderConeConstraint({1.0, 2.0 * x_ - y_},
3.0 * y_ - 4.0, "d")) {}
Model model_;
const Variable x_;
const Variable y_;
const SecondOrderConeConstraint c_;
const SecondOrderConeConstraint d_;
};
TEST_F(SimpleSecondOrderConeConstraintTest, Properties) {
EXPECT_EQ(model_.num_second_order_cone_constraints(), 2);
EXPECT_EQ(model_.next_second_order_cone_constraint_id(), 2);
EXPECT_TRUE(model_.has_second_order_cone_constraint(0));
EXPECT_TRUE(model_.has_second_order_cone_constraint(1));
EXPECT_FALSE(model_.has_second_order_cone_constraint(2));
EXPECT_FALSE(model_.has_second_order_cone_constraint(3));
EXPECT_FALSE(model_.has_second_order_cone_constraint(-1));
EXPECT_THAT(model_.SecondOrderConeConstraints(),
UnorderedElementsAre(c_, d_));
EXPECT_THAT(model_.SortedSecondOrderConeConstraints(), ElementsAre(c_, d_));
EXPECT_EQ(model_.second_order_cone_constraint(c_.id()).name(), "c");
EXPECT_EQ(model_.second_order_cone_constraint(d_.id()).name(), "d");
EXPECT_EQ(model_.second_order_cone_constraint(c_.typed_id()).name(), "c");
EXPECT_EQ(model_.second_order_cone_constraint(d_.typed_id()).name(), "d");
}
TEST_F(SimpleSecondOrderConeConstraintTest, DeleteConstraint) {
model_.DeleteSecondOrderConeConstraint(c_);
EXPECT_EQ(model_.num_second_order_cone_constraints(), 1);
EXPECT_EQ(model_.next_second_order_cone_constraint_id(), 2);
EXPECT_FALSE(model_.has_second_order_cone_constraint(0));
EXPECT_TRUE(model_.has_second_order_cone_constraint(1));
EXPECT_THAT(model_.SecondOrderConeConstraints(), UnorderedElementsAre(d_));
}
TEST_F(SimpleSecondOrderConeConstraintTest, ExportModel) {
EXPECT_THAT(model_.ExportModel(), EquivToProto(R"pb(
name: "soc_constraints"
variables {
ids: [ 0, 1 ]
lower_bounds: [ -inf, -inf ]
upper_bounds: [ inf, inf ]
integers: [ false, false ]
names: [ "x", "y" ]
}
second_order_cone_constraints {
key: 0
value: {
upper_bound { offset: 1.0 }
arguments_to_norm {
ids: [ 0 ]
coefficients: [ 1.0 ]
}
arguments_to_norm {
ids: [ 1 ]
coefficients: [ 1.0 ]
}
name: "c"
}
}
second_order_cone_constraints {
key: 1
value: {
upper_bound {
ids: [ 1 ]
coefficients: [ 3.0 ]
offset: -4.0
}
arguments_to_norm { offset: 1.0 }
arguments_to_norm {
ids: [ 0, 1 ]
coefficients: [ 2.0, -1.0 ]
}
name: "d"
}
}
)pb"));
}
TEST_F(SimpleSecondOrderConeConstraintTest, Streaming) {
EXPECT_EQ(StreamToString(model_),
R"model(Model soc_constraints:
Objective:
minimize 0
Linear constraints:
Second-order cone constraints:
c: ||{x, y}|| 1
d: ||{1, 2*x - y}|| 3*y - 4
Variables:
x in (-, +)
y in (-, +)
)model");
}
TEST(SecondOrderConeConstraintTest,
AddSecondOrderConeConstraintWithoutVariables) {
Model model;
// Here we test a corner case that may not be very useful in practice: the
// case of a LinearExpression that has no terms but its offset.
//
// We want to make sure the code don't assume all LinearExpressions have a
// non-null storage().
const SecondOrderConeConstraint c =
model.AddSecondOrderConeConstraint({2.0}, 1.0, "c");
{
const LinearExpression ub = c.UpperBound();
EXPECT_EQ(ub.offset(), 1.0);
EXPECT_THAT(ub.terms(), IsEmpty());
}
{
const std::vector<LinearExpression> args = c.ArgumentsToNorm();
ASSERT_EQ(args.size(), 1);
EXPECT_EQ(args[0].offset(), 2.0);
EXPECT_THAT(args[0].terms(), IsEmpty());
}
}
TEST(SecondOrderConeConstraintDeathTest, ConstraintByIdOutOfBounds) {
Model model;
model.AddSecondOrderConeConstraint({}, 1.0, "c");
EXPECT_DEATH(
model.second_order_cone_constraint(-1),
AllOf(HasSubstr("second-order cone constraint"), HasSubstr("-1")));
EXPECT_DEATH(
model.second_order_cone_constraint(2),
AllOf(HasSubstr("second-order cone constraint"), HasSubstr("2")));
}
TEST(SecondOrderConeConstraintDeathTest, ConstraintByIdDeleted) {
Model model;
const SecondOrderConeConstraint c =
model.AddSecondOrderConeConstraint({}, 1.0, "c");
EXPECT_EQ(model.second_order_cone_constraint(c.id()).name(), "c");
model.DeleteSecondOrderConeConstraint(c);
EXPECT_DEATH(
model.second_order_cone_constraint(c.id()),
AllOf(HasSubstr("second-order cone constraint"), HasSubstr("0")));
}
TEST(SecondOrderConeConstraintDeathTest, AddConstraintOtherModel) {
Model model_a("a");
Model model_b("b");
const Variable b_x = model_b.AddVariable("x");
EXPECT_DEATH(model_a.AddSecondOrderConeConstraint({b_x}, 1.0, "c"),
internal::kObjectsFromOtherModelStorage);
EXPECT_DEATH(model_a.AddSecondOrderConeConstraint({1.0}, b_x, "c"),
internal::kObjectsFromOtherModelStorage);
}
// --------------------------- SOS1 constraints --------------------------------
// max 0
// s.t. {x, y} is SOS1 with weights {3, 2} (c)
// {2 * y - 1, 1} is SOS1 (d)
// x, y unbounded
class SimpleSos1ConstraintTest : public testing::Test {
protected:
SimpleSos1ConstraintTest()
: model_("sos1_constraints"),
x_(model_.AddVariable("x")),
y_(model_.AddVariable("y")),
c_(model_.AddSos1Constraint({x_, y_}, {3.0, 2.0}, "c")),
d_(model_.AddSos1Constraint({2 * y_ - 1, 1.0}, {}, "d")) {}
Model model_;
const Variable x_;
const Variable y_;
const Sos1Constraint c_;
const Sos1Constraint d_;
};
TEST_F(SimpleSos1ConstraintTest, Properties) {
EXPECT_EQ(model_.num_sos1_constraints(), 2);
EXPECT_EQ(model_.next_sos1_constraint_id(), 2);
EXPECT_TRUE(model_.has_sos1_constraint(0));
EXPECT_TRUE(model_.has_sos1_constraint(1));
EXPECT_FALSE(model_.has_sos1_constraint(2));
EXPECT_FALSE(model_.has_sos1_constraint(3));
EXPECT_FALSE(model_.has_sos1_constraint(-1));
EXPECT_THAT(model_.Sos1Constraints(), UnorderedElementsAre(c_, d_));
EXPECT_THAT(model_.SortedSos1Constraints(), ElementsAre(c_, d_));
EXPECT_EQ(model_.sos1_constraint(c_.id()).name(), "c");
EXPECT_EQ(model_.sos1_constraint(d_.id()).name(), "d");
EXPECT_EQ(model_.sos1_constraint(c_.typed_id()).name(), "c");
EXPECT_EQ(model_.sos1_constraint(d_.typed_id()).name(), "d");
}
TEST_F(SimpleSos1ConstraintTest, DeleteConstraint) {
model_.DeleteSos1Constraint(c_);
EXPECT_EQ(model_.num_sos1_constraints(), 1);
EXPECT_EQ(model_.next_sos1_constraint_id(), 2);
EXPECT_FALSE(model_.has_sos1_constraint(0));
EXPECT_TRUE(model_.has_sos1_constraint(1));
EXPECT_THAT(model_.Sos1Constraints(), UnorderedElementsAre(d_));
}
TEST_F(SimpleSos1ConstraintTest, Streaming) {
EXPECT_EQ(StreamToString(model_),
R"model(Model sos1_constraints:
Objective:
minimize 0
Linear constraints:
SOS1 constraints:
c: {x, y} is SOS1 with weights {3, 2}
d: {2*y - 1, 1} is SOS1
Variables:
x in (-, +)
y in (-, +)
)model");
}
TEST(SimpleSos1ConstraintDeathTest, ConstraintByIdOutOfBounds) {
Model model;
model.AddSos1Constraint({}, {});
EXPECT_DEATH(model.sos1_constraint(-1),
AllOf(HasSubstr("SOS1 constraint"), HasSubstr("-1")));
EXPECT_DEATH(model.sos1_constraint(2),
AllOf(HasSubstr("SOS1 constraint"), HasSubstr("2")));
}
TEST(SimpleSos1ConstraintDeathTest, ConstraintByIdDeleted) {
Model model;
const Sos1Constraint c = model.AddSos1Constraint({}, {}, "c");
EXPECT_EQ(model.sos1_constraint(c.id()).name(), "c");
model.DeleteSos1Constraint(c);
EXPECT_DEATH(model.sos1_constraint(c.id()),
AllOf(HasSubstr("SOS1 constraint"), HasSubstr("0")));
}
TEST(SimpleSos1ConstraintDeathTest, AddConstraintOtherModel) {
Model model_a("a");
Model model_b("b");
const Variable b_x = model_b.AddVariable("x");
EXPECT_DEATH(model_a.AddSos1Constraint({b_x}, {}),
internal::kObjectsFromOtherModelStorage);
}
// --------------------------- SOS2 constraints --------------------------------
// max 0
// s.t. {x, y} is SOS2 with weights {3, 2} (c)
// {2 * y - 1, 1} is SOS2 (d)
// x, y unbounded
class SimpleSos2ConstraintTest : public testing::Test {
protected:
SimpleSos2ConstraintTest()
: model_("sos2_constraints"),
x_(model_.AddVariable("x")),
y_(model_.AddVariable("y")),
c_(model_.AddSos2Constraint({x_, y_}, {3.0, 2.0}, "c")),
d_(model_.AddSos2Constraint({2 * y_ - 1, 1.0}, {}, "d")) {}
Model model_;
const Variable x_;
const Variable y_;
const Sos2Constraint c_;
const Sos2Constraint d_;
};
TEST_F(SimpleSos2ConstraintTest, Properties) {
EXPECT_EQ(model_.num_sos2_constraints(), 2);
EXPECT_EQ(model_.next_sos2_constraint_id(), 2);
EXPECT_TRUE(model_.has_sos2_constraint(0));
EXPECT_TRUE(model_.has_sos2_constraint(1));
EXPECT_FALSE(model_.has_sos2_constraint(2));
EXPECT_FALSE(model_.has_sos2_constraint(3));
EXPECT_FALSE(model_.has_sos2_constraint(-1));
EXPECT_THAT(model_.Sos2Constraints(), UnorderedElementsAre(c_, d_));
EXPECT_THAT(model_.SortedSos2Constraints(), ElementsAre(c_, d_));
EXPECT_EQ(model_.sos2_constraint(c_.id()).name(), "c");
EXPECT_EQ(model_.sos2_constraint(d_.id()).name(), "d");
EXPECT_EQ(model_.sos2_constraint(c_.typed_id()).name(), "c");
EXPECT_EQ(model_.sos2_constraint(d_.typed_id()).name(), "d");
}
TEST_F(SimpleSos2ConstraintTest, DeleteConstraint) {
model_.DeleteSos2Constraint(c_);
EXPECT_EQ(model_.num_sos2_constraints(), 1);
EXPECT_EQ(model_.next_sos2_constraint_id(), 2);
EXPECT_FALSE(model_.has_sos2_constraint(0));
EXPECT_TRUE(model_.has_sos2_constraint(1));
EXPECT_THAT(model_.Sos2Constraints(), UnorderedElementsAre(d_));
}
TEST_F(SimpleSos2ConstraintTest, Streaming) {
EXPECT_EQ(StreamToString(model_),
R"model(Model sos2_constraints:
Objective:
minimize 0
Linear constraints:
SOS2 constraints:
c: {x, y} is SOS2 with weights {3, 2}
d: {2*y - 1, 1} is SOS2
Variables:
x in (-, +)
y in (-, +)
)model");
}
TEST(SimpleSos2ConstraintDeathTest, ConstraintByIdOutOfBounds) {
Model model;
model.AddSos2Constraint({}, {});
EXPECT_DEATH(model.sos2_constraint(-1),
AllOf(HasSubstr("SOS2 constraint"), HasSubstr("-1")));
EXPECT_DEATH(model.sos2_constraint(2),
AllOf(HasSubstr("SOS2 constraint"), HasSubstr("2")));
}
TEST(SimpleSos2ConstraintDeathTest, ConstraintByIdDeleted) {
Model model;
const Sos2Constraint c = model.AddSos2Constraint({}, {}, "c");
EXPECT_EQ(model.sos2_constraint(c.id()).name(), "c");
model.DeleteSos2Constraint(c);
EXPECT_DEATH(model.sos2_constraint(c.id()),
AllOf(HasSubstr("SOS2 constraint"), HasSubstr("0")));
}
TEST(SimpleSos2ConstraintDeathTest, AddConstraintOtherModel) {
Model model_a("a");
Model model_b("b");
const Variable b_x = model_b.AddVariable("x");
EXPECT_DEATH(model_a.AddSos2Constraint({b_x}, {}),
internal::kObjectsFromOtherModelStorage);
}
// ------------------------ Indicator constraints ------------------------------
// max 0
// s.t. x = 1 --> z + 2 <= 3 (c)
// y = 0 --> 1 <= 2 * z + 3 <= 4 (d)
// x, y in {0,1}
// z unbounded
class SimpleIndicatorConstraintTest : public testing::Test {
protected:
SimpleIndicatorConstraintTest()
: model_("indicator_constraints"),
x_(model_.AddBinaryVariable("x")),
y_(model_.AddBinaryVariable("y")),
z_(model_.AddVariable("z")),
c_(model_.AddIndicatorConstraint(x_, z_ + 2 <= 3,
/*activate_on_zero=*/false, "c")),
d_(model_.AddIndicatorConstraint(y_, 1 <= 2 * z_ + 3 <= 4,
/*activate_on_zero=*/true, "d")) {}
Model model_;
const Variable x_;
const Variable y_;
const Variable z_;
const IndicatorConstraint c_;
const IndicatorConstraint d_;
};
TEST_F(SimpleIndicatorConstraintTest, Properties) {
EXPECT_EQ(model_.num_indicator_constraints(), 2);
EXPECT_EQ(model_.next_indicator_constraint_id(), 2);
EXPECT_TRUE(model_.has_indicator_constraint(0));
EXPECT_TRUE(model_.has_indicator_constraint(1));
EXPECT_FALSE(model_.has_indicator_constraint(2));
EXPECT_FALSE(model_.has_indicator_constraint(3));
EXPECT_FALSE(model_.has_indicator_constraint(-1));
EXPECT_THAT(model_.IndicatorConstraints(), UnorderedElementsAre(c_, d_));
EXPECT_THAT(model_.SortedIndicatorConstraints(), ElementsAre(c_, d_));
EXPECT_EQ(model_.indicator_constraint(c_.id()).name(), "c");
EXPECT_EQ(model_.indicator_constraint(d_.id()).name(), "d");
EXPECT_EQ(model_.indicator_constraint(c_.typed_id()).name(), "c");
EXPECT_EQ(model_.indicator_constraint(d_.typed_id()).name(), "d");
}
TEST_F(SimpleIndicatorConstraintTest, DeleteConstraint) {
model_.DeleteIndicatorConstraint(c_);
EXPECT_EQ(model_.num_indicator_constraints(), 1);
EXPECT_EQ(model_.next_indicator_constraint_id(), 2);
EXPECT_FALSE(model_.has_indicator_constraint(0));
EXPECT_TRUE(model_.has_indicator_constraint(1));
EXPECT_THAT(model_.IndicatorConstraints(), UnorderedElementsAre(d_));
}
TEST_F(SimpleIndicatorConstraintTest, Streaming) {
EXPECT_EQ(StreamToString(model_),
R"model(Model indicator_constraints:
Objective:
minimize 0
Linear constraints:
Indicator constraints:
c: x = 1 z 1
d: y = 0 -2 2*z 1
Variables:
x (binary)
y (binary)
z in (-, +)
)model");
}
TEST(SimpleIndicatorConstraintDeathTest, ConstraintByIdOutOfBounds) {
Model model;
const Variable x = model.AddBinaryVariable("x");
model.AddIndicatorConstraint(x, x <= 1);
EXPECT_DEATH(model.indicator_constraint(-1),
AllOf(HasSubstr("indicator_constraint"), HasSubstr("-1")));
EXPECT_DEATH(model.indicator_constraint(2),
AllOf(HasSubstr("indicator_constraint"), HasSubstr("2")));
}
TEST(SimpleIndicatorConstraintDeathTest, ConstraintByIdDeleted) {
Model model;
const Variable x = model.AddBinaryVariable("x");
const IndicatorConstraint c =
model.AddIndicatorConstraint(x, x <= 1, /*activate_on_zero=*/false, "c");
EXPECT_EQ(model.indicator_constraint(c.id()).name(), "c");
model.DeleteIndicatorConstraint(c);
EXPECT_DEATH(model.indicator_constraint(c.id()),
AllOf(HasSubstr("indicator constraint"), HasSubstr("0")));
}
TEST(SimpleIndicatorConstraintDeathTest, AddConstraintOtherModel) {
Model model_a("a");
const Variable a_x = model_a.AddVariable("x");
Model model_b("b");
const Variable b_x = model_b.AddVariable("x");
// The indicator variable should trigger the crash.
EXPECT_DEATH(model_a.AddIndicatorConstraint(b_x, a_x <= 1),
internal::kObjectsFromOtherModelStorage);
// The implied constraint should trigger the crash.
EXPECT_DEATH(model_a.AddIndicatorConstraint(a_x, b_x <= 1),
internal::kObjectsFromOtherModelStorage);
}
} // namespace
} // namespace math_opt
} // namespace operations_research