math_opt: export constraints tests
This commit is contained in:
committed by
Mizux Seiha
parent
0d8db7b56f
commit
5ffb246ccf
@@ -238,6 +238,7 @@ cc_library(
|
||||
name = "message_matchers",
|
||||
hdrs = ["message_matchers.h"],
|
||||
deps = [
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_googletest//:gtest",
|
||||
"@com_google_protobuf//:protobuf",
|
||||
],
|
||||
|
||||
@@ -16,10 +16,83 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gmock/gmock-matchers.h"
|
||||
#include "google/protobuf/message.h"
|
||||
#include "google/protobuf/util/message_differencer.h"
|
||||
|
||||
namespace testing {
|
||||
namespace internal {
|
||||
// Utilities.
|
||||
// How to compare two fields (equal vs. equivalent).
|
||||
typedef ::google::protobuf::util::MessageDifferencer::MessageFieldComparison
|
||||
ProtoFieldComparison;
|
||||
|
||||
// How to compare two floating-points (exact vs. approximate).
|
||||
typedef ::google::protobuf::util::DefaultFieldComparator::FloatComparison
|
||||
ProtoFloatComparison;
|
||||
|
||||
// How to compare repeated fields (whether the order of elements matters).
|
||||
typedef ::google::protobuf::util::MessageDifferencer::RepeatedFieldComparison
|
||||
RepeatedFieldComparison;
|
||||
|
||||
// Whether to compare all fields (full) or only fields present in the
|
||||
// expected protobuf (partial).
|
||||
typedef ::google::protobuf::util::MessageDifferencer::Scope ProtoComparisonScope;
|
||||
|
||||
const ProtoFieldComparison kProtoEqual =
|
||||
::google::protobuf::util::MessageDifferencer::EQUAL;
|
||||
const ProtoFieldComparison kProtoEquiv =
|
||||
::google::protobuf::util::MessageDifferencer::EQUIVALENT;
|
||||
const ProtoFloatComparison kProtoExact =
|
||||
::google::protobuf::util::DefaultFieldComparator::EXACT;
|
||||
const ProtoFloatComparison kProtoApproximate =
|
||||
::google::protobuf::util::DefaultFieldComparator::APPROXIMATE;
|
||||
const RepeatedFieldComparison kProtoCompareRepeatedFieldsRespectOrdering =
|
||||
::google::protobuf::util::MessageDifferencer::AS_LIST;
|
||||
const RepeatedFieldComparison kProtoCompareRepeatedFieldsIgnoringOrdering =
|
||||
::google::protobuf::util::MessageDifferencer::AS_SET;
|
||||
const ProtoComparisonScope kProtoFull = ::google::protobuf::util::MessageDifferencer::FULL;
|
||||
const ProtoComparisonScope kProtoPartial =
|
||||
::google::protobuf::util::MessageDifferencer::PARTIAL;
|
||||
|
||||
// Options for comparing two protobufs.
|
||||
struct ProtoComparison {
|
||||
ProtoComparison()
|
||||
: field_comp(kProtoEqual),
|
||||
float_comp(kProtoExact),
|
||||
treating_nan_as_equal(false),
|
||||
has_custom_margin(false),
|
||||
has_custom_fraction(false),
|
||||
repeated_field_comp(kProtoCompareRepeatedFieldsRespectOrdering),
|
||||
scope(kProtoFull),
|
||||
float_margin(0.0),
|
||||
float_fraction(0.0),
|
||||
ignore_debug_string_format(false),
|
||||
fail_on_no_presence_default_values(false),
|
||||
verified_presence_in_string(false) {}
|
||||
|
||||
ProtoFieldComparison field_comp;
|
||||
ProtoFloatComparison float_comp;
|
||||
bool treating_nan_as_equal;
|
||||
bool has_custom_margin; // only used when float_comp = APPROXIMATE
|
||||
bool has_custom_fraction; // only used when float_comp = APPROXIMATE
|
||||
RepeatedFieldComparison repeated_field_comp;
|
||||
ProtoComparisonScope scope;
|
||||
double float_margin; // only used when has_custom_margin is set.
|
||||
double float_fraction; // only used when has_custom_fraction is set.
|
||||
std::vector<std::string> ignore_fields;
|
||||
std::vector<std::string> ignore_field_paths;
|
||||
std::vector<std::string> unordered_fields;
|
||||
bool ignore_debug_string_format;
|
||||
bool fail_on_no_presence_default_values;
|
||||
bool verified_presence_in_string;
|
||||
};
|
||||
|
||||
// Whether the protobuf must be initialized.
|
||||
const bool kMustBeInitialized = true;
|
||||
const bool kMayBeUninitialized = false;
|
||||
|
||||
class ProtoMatcher {
|
||||
public:
|
||||
@@ -29,6 +102,9 @@ class ProtoMatcher {
|
||||
explicit ProtoMatcher(const MessageType& message)
|
||||
: message_(CloneMessage(message)) {}
|
||||
|
||||
ProtoMatcher(const MessageType& message, bool, ProtoComparison&):
|
||||
message_(CloneMessage(message)) {}
|
||||
|
||||
bool MatchAndExplain(const MessageType& m,
|
||||
testing::MatchResultListener*) const {
|
||||
return EqualsMessage(*message_, m);
|
||||
@@ -69,8 +145,11 @@ class ProtoMatcher {
|
||||
const std::shared_ptr<MessageType> message_;
|
||||
};
|
||||
|
||||
inline ProtoMatcher EqualsProto(const ::google::protobuf::Message& message) {
|
||||
return ProtoMatcher(message);
|
||||
using PolymorphicProtoMatcher = PolymorphicMatcher<ProtoMatcher>;
|
||||
} // namespace internal
|
||||
|
||||
inline internal::ProtoMatcher EqualsProto(const ::google::protobuf::Message& message) {
|
||||
return internal::ProtoMatcher(message);
|
||||
}
|
||||
|
||||
// for Pointwise
|
||||
@@ -80,6 +159,16 @@ MATCHER(EqualsProto, "") {
|
||||
return ::testing::ExplainMatchResult(EqualsProto(b), a, result_listener);
|
||||
}
|
||||
|
||||
// Constructs a matcher that matches the argument if
|
||||
// argument.Equivalent(x) or argument->Equivalent(x) returns true.
|
||||
inline internal::PolymorphicProtoMatcher EquivToProto(
|
||||
const ::google::protobuf::Message& x) {
|
||||
internal::ProtoComparison comp;
|
||||
comp.field_comp = internal::kProtoEquiv;
|
||||
return MakePolymorphicMatcher(
|
||||
internal::ProtoMatcher(x, internal::kMayBeUninitialized, comp));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
|
||||
#endif // OR_TOOLS_BASE_MESSAGE_MATCHERS_H_
|
||||
|
||||
@@ -26,6 +26,21 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "indicator_constraint_test",
|
||||
srcs = ["indicator_constraint_test.cc"],
|
||||
deps = [
|
||||
":indicator_constraint",
|
||||
"//ortools/base:gmock",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/cpp:math_opt",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "storage",
|
||||
srcs = ["storage.cc"],
|
||||
@@ -42,6 +57,20 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "storage_test",
|
||||
srcs = ["storage_test.cc"],
|
||||
deps = [
|
||||
":storage",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/cpp:math_opt",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "validator",
|
||||
srcs = ["validator.cc"],
|
||||
@@ -55,3 +84,18 @@ cc_library(
|
||||
"@com_google_absl//absl/status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "validator_test",
|
||||
srcs = ["validator_test.cc"],
|
||||
deps = [
|
||||
":validator",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/core:model_summary",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -15,6 +15,8 @@ set(NAME ${PROJECT_NAME}_math_opt_constraints_indicator)
|
||||
add_library(${NAME} OBJECT)
|
||||
|
||||
file(GLOB _SRCS "*.h" "*.cc")
|
||||
list(FILTER _SRCS EXCLUDE REGEX ".*_test.cc")
|
||||
|
||||
target_sources(${NAME} PRIVATE ${_SRCS})
|
||||
set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
target_include_directories(${NAME} PUBLIC
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/indicator/indicator_constraint.h"
|
||||
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/cpp/math_opt.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::Optional;
|
||||
using ::testing::Pair;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
TEST(IndicatorConstraintTest, Accessors) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
const Variable z(&storage, storage.AddVariable("z"));
|
||||
SparseCoefficientMap coeffs;
|
||||
coeffs.set(x.typed_id(), 2.0);
|
||||
coeffs.set(y.typed_id(), 3.0);
|
||||
const IndicatorConstraintData data{
|
||||
.lower_bound = -1.0,
|
||||
.upper_bound = 1.0,
|
||||
.linear_terms = std::move(coeffs),
|
||||
.indicator = z.typed_id(),
|
||||
.activate_on_zero = true,
|
||||
.name = "c",
|
||||
};
|
||||
const IndicatorConstraint c(&storage, storage.AddAtomicConstraint(data));
|
||||
EXPECT_EQ(c.name(), "c");
|
||||
EXPECT_THAT(c.indicator_variable(), Optional(z));
|
||||
const BoundedLinearExpression constraint = c.ImpliedConstraint();
|
||||
EXPECT_EQ(constraint.lower_bound, -1.0);
|
||||
EXPECT_EQ(constraint.upper_bound, 1.0);
|
||||
EXPECT_EQ(constraint.expression.offset(), 0.0);
|
||||
EXPECT_THAT(constraint.expression.terms(),
|
||||
UnorderedElementsAre(Pair(x, 2.0), Pair(y, 3.0)));
|
||||
EXPECT_TRUE(c.activate_on_zero());
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintTest, Equality) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
|
||||
const IndicatorConstraint c(
|
||||
&storage, storage.AddAtomicConstraint(
|
||||
IndicatorConstraintData{.upper_bound = 1.0, .name = "c"}));
|
||||
const IndicatorConstraint d(
|
||||
&storage, storage.AddAtomicConstraint(
|
||||
IndicatorConstraintData{.upper_bound = 2.0, .name = "d"}));
|
||||
|
||||
// `d2` is another `IndicatorConstraint` that points the same constraint in
|
||||
// the indexed storage. It should compares == to `d`.
|
||||
const IndicatorConstraint d2(d.storage(), d.typed_id());
|
||||
|
||||
// `e` has identical data as `d`. It should not compares equal to `d`, though.
|
||||
const IndicatorConstraint e(
|
||||
&storage, storage.AddAtomicConstraint(
|
||||
IndicatorConstraintData{.upper_bound = 2.0, .name = "d"}));
|
||||
|
||||
EXPECT_TRUE(c == c);
|
||||
EXPECT_FALSE(c == d);
|
||||
EXPECT_TRUE(d == d2);
|
||||
EXPECT_FALSE(d == e);
|
||||
EXPECT_FALSE(c != c);
|
||||
EXPECT_TRUE(c != d);
|
||||
EXPECT_FALSE(d != d2);
|
||||
EXPECT_TRUE(d != e);
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintTest, NonzeroVariables) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
const Variable z(&storage, storage.AddVariable("z"));
|
||||
SparseCoefficientMap coeffs;
|
||||
coeffs.set(x.typed_id(), 2.0);
|
||||
coeffs.set(y.typed_id(), 3.0);
|
||||
const IndicatorConstraintData data{
|
||||
.lower_bound = -1.0,
|
||||
.upper_bound = 1.0,
|
||||
.linear_terms = std::move(coeffs),
|
||||
.indicator = z.typed_id(),
|
||||
.name = "c",
|
||||
};
|
||||
const IndicatorConstraint c(&storage, storage.AddAtomicConstraint(data));
|
||||
|
||||
EXPECT_THAT(c.NonzeroVariables(), UnorderedElementsAre(x, y, z));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintTest, IndicatorGetter) {
|
||||
Model model;
|
||||
const Variable x = model.AddBinaryVariable("x");
|
||||
const Variable y = model.AddVariable("y");
|
||||
const IndicatorConstraint c = model.AddIndicatorConstraint(x, y <= 1);
|
||||
|
||||
EXPECT_THAT(c.indicator_variable(), Optional(x));
|
||||
|
||||
model.DeleteVariable(x);
|
||||
EXPECT_EQ(c.indicator_variable(), std::nullopt);
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintTest, ToString) {
|
||||
Model model;
|
||||
const Variable x = model.AddBinaryVariable("x");
|
||||
const Variable y = model.AddVariable("y");
|
||||
|
||||
const IndicatorConstraint c = model.AddIndicatorConstraint(
|
||||
x, -3.0 <= 2 * y + 1 <= 3.0, /*activate_on_zero=*/false, "c");
|
||||
EXPECT_EQ(c.ToString(), "x = 1 ⇒ -4 ≤ 2*y ≤ 2");
|
||||
|
||||
const IndicatorConstraint d = model.AddIndicatorConstraint(
|
||||
x, -2 * y == 3, /*activate_on_zero=*/true, "d");
|
||||
EXPECT_EQ(d.ToString(), "x = 0 ⇒ -2*y = 3");
|
||||
|
||||
model.DeleteVariable(x);
|
||||
EXPECT_EQ(c.ToString(), "[unset indicator variable] ⇒ -4 ≤ 2*y ≤ 2");
|
||||
|
||||
model.DeleteIndicatorConstraint(c);
|
||||
EXPECT_EQ(c.ToString(), kDeletedConstraintDefaultDescription);
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintTest, OutputStreaming) {
|
||||
ModelStorage storage;
|
||||
const IndicatorConstraint q(
|
||||
&storage,
|
||||
storage.AddAtomicConstraint(IndicatorConstraintData{.name = "q"}));
|
||||
const IndicatorConstraint anonymous(
|
||||
&storage,
|
||||
storage.AddAtomicConstraint(IndicatorConstraintData{.name = ""}));
|
||||
|
||||
auto to_string = [](IndicatorConstraint c) {
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
};
|
||||
|
||||
EXPECT_EQ(to_string(q), "q");
|
||||
EXPECT_EQ(to_string(anonymous),
|
||||
absl::StrCat("__indic_con#", anonymous.id(), "__"));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintTest, NameAfterDeletion) {
|
||||
Model model;
|
||||
const Variable x = model.AddBinaryVariable("x");
|
||||
const IndicatorConstraint c =
|
||||
model.AddIndicatorConstraint(x, x >= 1, /*activate_on_zero=*/false, "c");
|
||||
ASSERT_EQ(c.name(), "c");
|
||||
|
||||
model.DeleteIndicatorConstraint(c);
|
||||
EXPECT_EQ(c.name(), kDeletedConstraintDefaultDescription);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
126
ortools/math_opt/constraints/indicator/storage_test.cc
Normal file
126
ortools/math_opt/constraints/indicator/storage_test.cc
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/indicator/storage.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/cpp/math_opt.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::EquivToProto;
|
||||
using ::testing::Optional;
|
||||
using ::testing::Pair;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
IndicatorConstraintProto SimpleProto() {
|
||||
IndicatorConstraintProto proto;
|
||||
proto.set_lower_bound(-1.0);
|
||||
proto.set_upper_bound(1.0);
|
||||
proto.set_name("indicator");
|
||||
proto.set_indicator_id(2);
|
||||
proto.set_activate_on_zero(true);
|
||||
proto.mutable_expression()->add_ids(1);
|
||||
proto.mutable_expression()->add_values(2.0);
|
||||
proto.mutable_expression()->add_ids(3);
|
||||
proto.mutable_expression()->add_values(4.0);
|
||||
proto.mutable_expression()->add_ids(5);
|
||||
proto.mutable_expression()->add_values(6.0);
|
||||
return proto;
|
||||
}
|
||||
|
||||
IndicatorConstraintData SimpleData() {
|
||||
IndicatorConstraintData data;
|
||||
data.lower_bound = -1.0;
|
||||
data.upper_bound = 1.0;
|
||||
data.name = "indicator";
|
||||
data.indicator = VariableId(2);
|
||||
data.activate_on_zero = true;
|
||||
data.linear_terms.set(VariableId(1), 2.0);
|
||||
data.linear_terms.set(VariableId(3), 4.0);
|
||||
data.linear_terms.set(VariableId(5), 6.0);
|
||||
return data;
|
||||
}
|
||||
|
||||
TEST(QuadraticStorageDataTest, RelatedVariables) {
|
||||
EXPECT_THAT(SimpleData().RelatedVariables(),
|
||||
UnorderedElementsAre(VariableId(1), VariableId(2), VariableId(3),
|
||||
VariableId(5)));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintDataTest, DeleteVariable) {
|
||||
IndicatorConstraintData data = SimpleData();
|
||||
data.DeleteVariable(VariableId(1));
|
||||
EXPECT_THAT(data.indicator, Optional(VariableId(2)));
|
||||
EXPECT_THAT(
|
||||
data.linear_terms.terms(),
|
||||
UnorderedElementsAre(Pair(VariableId(3), 4.0), Pair(VariableId(5), 6.0)));
|
||||
|
||||
data.DeleteVariable(VariableId(2));
|
||||
EXPECT_EQ(data.indicator, std::nullopt);
|
||||
EXPECT_THAT(
|
||||
data.linear_terms.terms(),
|
||||
UnorderedElementsAre(Pair(VariableId(3), 4.0), Pair(VariableId(5), 6.0)));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintDataTest, FromProto) {
|
||||
const auto data = IndicatorConstraintData::FromProto(SimpleProto());
|
||||
EXPECT_EQ(data.lower_bound, -1.0);
|
||||
EXPECT_EQ(data.upper_bound, 1.0);
|
||||
EXPECT_EQ(data.name, "indicator");
|
||||
EXPECT_THAT(data.indicator, Optional(VariableId(2)));
|
||||
EXPECT_TRUE(data.activate_on_zero);
|
||||
EXPECT_THAT(
|
||||
data.linear_terms.terms(),
|
||||
UnorderedElementsAre(Pair(VariableId(1), 2.0), Pair(VariableId(3), 4.0),
|
||||
Pair(VariableId(5), 6.0)));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintDataTest, FromProtoUnsetIndicator) {
|
||||
IndicatorConstraintProto proto = SimpleProto();
|
||||
proto.clear_indicator_id();
|
||||
const auto data = IndicatorConstraintData::FromProto(proto);
|
||||
EXPECT_EQ(data.lower_bound, -1.0);
|
||||
EXPECT_EQ(data.upper_bound, 1.0);
|
||||
EXPECT_EQ(data.name, "indicator");
|
||||
EXPECT_EQ(data.indicator, std::nullopt);
|
||||
EXPECT_TRUE(data.activate_on_zero);
|
||||
EXPECT_THAT(
|
||||
data.linear_terms.terms(),
|
||||
UnorderedElementsAre(Pair(VariableId(1), 2.0), Pair(VariableId(3), 4.0),
|
||||
Pair(VariableId(5), 6.0)));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintDataTest, Proto) {
|
||||
EXPECT_THAT(SimpleData().Proto(), EquivToProto(SimpleProto()));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintDataTest, ProtoUnsetIndicator) {
|
||||
IndicatorConstraintData data = SimpleData();
|
||||
data.indicator.reset();
|
||||
IndicatorConstraintProto expected = SimpleProto();
|
||||
expected.clear_indicator_id();
|
||||
EXPECT_THAT(data.Proto(), EquivToProto(expected));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
114
ortools/math_opt/constraints/indicator/validator_test.cc
Normal file
114
ortools/math_opt/constraints/indicator/validator_test.cc
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/indicator/validator.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/math_opt/core/model_summary.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::status::StatusIs;
|
||||
|
||||
constexpr double kInf = std::numeric_limits<double>::infinity();
|
||||
|
||||
IdNameBiMap SimpleVariableUniverse(const std::initializer_list<int64_t> ids) {
|
||||
IdNameBiMap universe;
|
||||
CHECK_OK(universe.BulkUpdate({}, ids, {}));
|
||||
return universe;
|
||||
}
|
||||
|
||||
IndicatorConstraintProto SimpleIndicatorConstraintProto() {
|
||||
IndicatorConstraintProto data;
|
||||
data.set_indicator_id(1);
|
||||
data.mutable_expression()->add_ids(2);
|
||||
data.mutable_expression()->add_values(3.0);
|
||||
data.set_lower_bound(-1.0);
|
||||
data.set_upper_bound(1.0);
|
||||
return data;
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintValidatorTest, SimpleConstraintOk) {
|
||||
EXPECT_OK(ValidateConstraint(SimpleIndicatorConstraintProto(),
|
||||
SimpleVariableUniverse({1, 2})));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintValidatorTest, UnsetIndicatorId) {
|
||||
IndicatorConstraintProto constraint = SimpleIndicatorConstraintProto();
|
||||
constraint.clear_indicator_id();
|
||||
EXPECT_OK(ValidateConstraint(constraint, SimpleVariableUniverse({2})));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintValidatorTest, InvalidIndicatorId) {
|
||||
EXPECT_THAT(ValidateConstraint(SimpleIndicatorConstraintProto(),
|
||||
SimpleVariableUniverse({2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
HasSubstr("indicator variable id")));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintValidatorTest, InvalidTermInExpression) {
|
||||
IndicatorConstraintProto constraint = SimpleIndicatorConstraintProto();
|
||||
constraint.mutable_expression()->set_values(0, kInf);
|
||||
EXPECT_THAT(ValidateConstraint(constraint, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("infinite value"),
|
||||
HasSubstr("implied constraint"))));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintValidatorTest, InvalidIdInExpression) {
|
||||
EXPECT_THAT(ValidateConstraint(SimpleIndicatorConstraintProto(),
|
||||
SimpleVariableUniverse({1})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("variable id"),
|
||||
HasSubstr("implied constraint"))));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintValidatorTest, InvalidLowerBound) {
|
||||
IndicatorConstraintProto constraint = SimpleIndicatorConstraintProto();
|
||||
constraint.set_lower_bound(kInf);
|
||||
EXPECT_THAT(ValidateConstraint(constraint, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
HasSubstr("invalid lower bound")));
|
||||
constraint.set_lower_bound(std::numeric_limits<double>::quiet_NaN());
|
||||
EXPECT_THAT(ValidateConstraint(constraint, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
HasSubstr("invalid lower bound")));
|
||||
}
|
||||
|
||||
TEST(IndicatorConstraintValidatorTest, InvalidUpperBound) {
|
||||
IndicatorConstraintProto constraint = SimpleIndicatorConstraintProto();
|
||||
constraint.set_upper_bound(-kInf);
|
||||
EXPECT_THAT(ValidateConstraint(constraint, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
HasSubstr("invalid upper bound")));
|
||||
constraint.set_upper_bound(std::numeric_limits<double>::quiet_NaN());
|
||||
EXPECT_THAT(ValidateConstraint(constraint, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
HasSubstr("invalid upper bound")));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
@@ -30,6 +30,22 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "quadratic_constraint_test",
|
||||
srcs = ["quadratic_constraint_test.cc"],
|
||||
deps = [
|
||||
":quadratic_constraint",
|
||||
"//ortools/base:gmock",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/cpp:math_opt",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"//ortools/math_opt/storage:sparse_matrix",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "storage",
|
||||
srcs = ["storage.cc"],
|
||||
@@ -46,6 +62,21 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "storage_test",
|
||||
srcs = ["storage_test.cc"],
|
||||
deps = [
|
||||
":storage",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/storage:model_storage_types",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"//ortools/math_opt/storage:sparse_matrix",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "validator",
|
||||
srcs = ["validator.cc"],
|
||||
@@ -63,3 +94,18 @@ cc_library(
|
||||
"@com_google_absl//absl/status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "validator_test",
|
||||
srcs = ["validator_test.cc"],
|
||||
deps = [
|
||||
":validator",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/core:model_summary",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -15,6 +15,8 @@ set(NAME ${PROJECT_NAME}_math_opt_constraints_quadratic)
|
||||
add_library(${NAME} OBJECT)
|
||||
|
||||
file(GLOB _SRCS "*.h" "*.cc")
|
||||
list(FILTER _SRCS EXCLUDE REGEX ".*_test.cc")
|
||||
|
||||
target_sources(${NAME} PRIVATE ${_SRCS})
|
||||
set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
target_include_directories(${NAME} PUBLIC
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/quadratic/quadratic_constraint.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/cpp/math_opt.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
#include "ortools/math_opt/storage/sparse_matrix.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::Pair;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
TEST(QuadraticConstraintTest, OutputStreaming) {
|
||||
ModelStorage storage;
|
||||
const QuadraticConstraint q(
|
||||
&storage,
|
||||
storage.AddAtomicConstraint(QuadraticConstraintData{.name = "q"}));
|
||||
const QuadraticConstraint anonymous(
|
||||
&storage, storage.AddAtomicConstraint(QuadraticConstraintData{}));
|
||||
|
||||
auto to_string = [](QuadraticConstraint c) {
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
};
|
||||
|
||||
EXPECT_EQ(to_string(q), "q");
|
||||
EXPECT_EQ(to_string(anonymous),
|
||||
absl::StrCat("__quad_con#", anonymous.id(), "__"));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintTest, Accessors) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
SparseSymmetricMatrix quadratic_terms;
|
||||
quadratic_terms.set(x.typed_id(), y.typed_id(), 4.0);
|
||||
quadratic_terms.set(y.typed_id(), y.typed_id(), 5.0);
|
||||
const QuadraticConstraintData data{
|
||||
.lower_bound = 1.0,
|
||||
.upper_bound = 2.0,
|
||||
.linear_terms = SparseCoefficientMap({{x.typed_id(), 3.0}}),
|
||||
.quadratic_terms = quadratic_terms,
|
||||
.name = "q",
|
||||
};
|
||||
const QuadraticConstraint c(&storage, storage.AddAtomicConstraint(data));
|
||||
EXPECT_EQ(c.lower_bound(), 1.0);
|
||||
EXPECT_EQ(c.upper_bound(), 2.0);
|
||||
EXPECT_EQ(c.name(), "q");
|
||||
|
||||
EXPECT_TRUE(c.is_linear_coefficient_nonzero(x));
|
||||
EXPECT_FALSE(c.is_linear_coefficient_nonzero(y));
|
||||
EXPECT_EQ(c.linear_coefficient(x), 3.0);
|
||||
EXPECT_EQ(c.linear_coefficient(y), 0.0);
|
||||
|
||||
EXPECT_FALSE(c.is_quadratic_coefficient_nonzero(x, x));
|
||||
EXPECT_TRUE(c.is_quadratic_coefficient_nonzero(x, y));
|
||||
EXPECT_TRUE(c.is_quadratic_coefficient_nonzero(y, x));
|
||||
EXPECT_TRUE(c.is_quadratic_coefficient_nonzero(y, y));
|
||||
EXPECT_EQ(c.quadratic_coefficient(x, x), 0.0);
|
||||
EXPECT_EQ(c.quadratic_coefficient(x, y), 4.0);
|
||||
EXPECT_EQ(c.quadratic_coefficient(y, x), 4.0);
|
||||
EXPECT_EQ(c.quadratic_coefficient(y, y), 5.0);
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintTest, Equality) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
|
||||
const QuadraticConstraint c(
|
||||
&storage, storage.AddAtomicConstraint(QuadraticConstraintData{
|
||||
.upper_bound = 1.5, .name = "upper_bounded"}));
|
||||
const QuadraticConstraint d(
|
||||
&storage, storage.AddAtomicConstraint(QuadraticConstraintData{
|
||||
.lower_bound = 0.5, .name = "lower_bounded"}));
|
||||
|
||||
// `d2` is another `QuadraticConstraint` that points the same constraint in
|
||||
// the indexed storage. It should compares == to `d`.
|
||||
const QuadraticConstraint d2(d.storage(), d.typed_id());
|
||||
|
||||
// `e` has identical data as `d`. It should not compares equal to `d`, though.
|
||||
const QuadraticConstraint e(
|
||||
&storage, storage.AddAtomicConstraint(QuadraticConstraintData{
|
||||
.lower_bound = 0.5, .name = "lower_bounded"}));
|
||||
|
||||
EXPECT_TRUE(c == c);
|
||||
EXPECT_FALSE(c == d);
|
||||
EXPECT_TRUE(d == d2);
|
||||
EXPECT_FALSE(d == e);
|
||||
EXPECT_FALSE(c != c);
|
||||
EXPECT_TRUE(c != d);
|
||||
EXPECT_FALSE(d != d2);
|
||||
EXPECT_TRUE(d != e);
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintTest, NonzeroVariables) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
const Variable z(&storage, storage.AddVariable("z"));
|
||||
const Variable w(&storage, storage.AddVariable("w"));
|
||||
SparseSymmetricMatrix quadratic_terms;
|
||||
quadratic_terms.set(y.typed_id(), z.typed_id(), 5.0);
|
||||
const QuadraticConstraintData data{
|
||||
.lower_bound = 1.0,
|
||||
.upper_bound = 2.0,
|
||||
.linear_terms = SparseCoefficientMap({{x.typed_id(), 3.0}}),
|
||||
.quadratic_terms = quadratic_terms,
|
||||
.name = "q",
|
||||
};
|
||||
const QuadraticConstraint q(&storage, storage.AddAtomicConstraint(data));
|
||||
|
||||
EXPECT_THAT(q.NonzeroVariables(), UnorderedElementsAre(x, y, z));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintTest, AsBoundedQuadraticExpression) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
const Variable z(&storage, storage.AddVariable("z"));
|
||||
const Variable w(&storage, storage.AddVariable("w"));
|
||||
SparseSymmetricMatrix quadratic_terms;
|
||||
quadratic_terms.set(y.typed_id(), z.typed_id(), 5.0);
|
||||
const QuadraticConstraintData data{
|
||||
.lower_bound = 1.0,
|
||||
.upper_bound = 2.0,
|
||||
.linear_terms = SparseCoefficientMap({{x.typed_id(), 3.0}}),
|
||||
.quadratic_terms = quadratic_terms,
|
||||
.name = "q",
|
||||
};
|
||||
const QuadraticConstraint q(&storage, storage.AddAtomicConstraint(data));
|
||||
|
||||
const BoundedQuadraticExpression expr = q.AsBoundedQuadraticExpression();
|
||||
EXPECT_EQ(expr.lower_bound, 1.0);
|
||||
EXPECT_EQ(expr.upper_bound, 2.0);
|
||||
EXPECT_EQ(expr.expression.offset(), 0.0);
|
||||
EXPECT_THAT(expr.expression.linear_terms(),
|
||||
UnorderedElementsAre(Pair(x, 3.0)));
|
||||
EXPECT_THAT(expr.expression.quadratic_terms(),
|
||||
UnorderedElementsAre(Pair(QuadraticTermKey(y, z), 5.0)));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintTest, ToString) {
|
||||
Model model;
|
||||
const Variable x = model.AddVariable("x");
|
||||
const Variable y = model.AddVariable("y");
|
||||
|
||||
const QuadraticConstraint c = model.AddQuadraticConstraint(
|
||||
1.0 <= -2 * x + 3 * y - 4.0 * x * x + 5.0 * x * y + 6.0 <= 7.0);
|
||||
EXPECT_EQ(c.ToString(), "-5 ≤ -4*x² + 5*x*y - 2*x + 3*y ≤ 1");
|
||||
|
||||
model.DeleteVariable(y);
|
||||
EXPECT_EQ(c.ToString(), "-5 ≤ -4*x² - 2*x ≤ 1");
|
||||
|
||||
model.DeleteQuadraticConstraint(c);
|
||||
EXPECT_EQ(c.ToString(), kDeletedConstraintDefaultDescription);
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintTest, NameAfterDeletion) {
|
||||
Model model;
|
||||
const Variable x = model.AddVariable("x");
|
||||
const QuadraticConstraint c =
|
||||
model.AddQuadraticConstraint(2 * x * x >= 1, "c");
|
||||
ASSERT_EQ(c.name(), "c");
|
||||
|
||||
model.DeleteQuadraticConstraint(c);
|
||||
EXPECT_EQ(c.name(), kDeletedConstraintDefaultDescription);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
103
ortools/math_opt/constraints/quadratic/storage_test.cc
Normal file
103
ortools/math_opt/constraints/quadratic/storage_test.cc
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/quadratic/storage.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
#include "ortools/math_opt/storage/model_storage_types.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
#include "ortools/math_opt/storage/sparse_matrix.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::EquivToProto;
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::Pair;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
using ::testing::no_adl::FieldsAre;
|
||||
|
||||
QuadraticConstraintProto SimpleProto() {
|
||||
QuadraticConstraintProto proto;
|
||||
proto.set_lower_bound(-1.0);
|
||||
proto.set_upper_bound(1.0);
|
||||
proto.set_name("q");
|
||||
proto.mutable_quadratic_terms()->add_row_ids(1);
|
||||
proto.mutable_quadratic_terms()->add_column_ids(2);
|
||||
proto.mutable_quadratic_terms()->add_coefficients(3.0);
|
||||
proto.mutable_quadratic_terms()->add_row_ids(4);
|
||||
proto.mutable_quadratic_terms()->add_column_ids(4);
|
||||
proto.mutable_quadratic_terms()->add_coefficients(5.0);
|
||||
proto.mutable_linear_terms()->add_ids(6);
|
||||
proto.mutable_linear_terms()->add_values(7.0);
|
||||
return proto;
|
||||
}
|
||||
|
||||
QuadraticConstraintData SimpleData() {
|
||||
QuadraticConstraintData data;
|
||||
data.lower_bound = -1.0;
|
||||
data.upper_bound = 1.0;
|
||||
data.name = "q";
|
||||
data.linear_terms.set(VariableId(6), 7.0);
|
||||
data.quadratic_terms.set(VariableId(1), VariableId(2), 3.0);
|
||||
data.quadratic_terms.set(VariableId(4), VariableId(4), 5.0);
|
||||
return data;
|
||||
}
|
||||
|
||||
TEST(QuadraticStorageDataTest, RelatedVariables) {
|
||||
EXPECT_THAT(SimpleData().RelatedVariables(),
|
||||
UnorderedElementsAre(VariableId(1), VariableId(2), VariableId(4),
|
||||
VariableId(6)));
|
||||
}
|
||||
|
||||
TEST(QuadraticStorageDataTest, DeleteVariable) {
|
||||
QuadraticConstraintData data = SimpleData();
|
||||
data.DeleteVariable(VariableId(2));
|
||||
EXPECT_THAT(data.linear_terms.terms(),
|
||||
UnorderedElementsAre(Pair(VariableId(6), 7.0)));
|
||||
EXPECT_THAT(
|
||||
data.quadratic_terms.Terms(),
|
||||
UnorderedElementsAre(FieldsAre(VariableId(4), VariableId(4), 5.0)));
|
||||
|
||||
data.DeleteVariable(VariableId(6));
|
||||
EXPECT_THAT(data.linear_terms.terms(), IsEmpty());
|
||||
EXPECT_THAT(
|
||||
data.quadratic_terms.Terms(),
|
||||
UnorderedElementsAre(FieldsAre(VariableId(4), VariableId(4), 5.0)));
|
||||
}
|
||||
|
||||
TEST(QuadraticStorageDataTest, FromProto) {
|
||||
const auto data = QuadraticConstraintData::FromProto(SimpleProto());
|
||||
EXPECT_EQ(data.lower_bound, -1.0);
|
||||
EXPECT_EQ(data.upper_bound, 1.0);
|
||||
EXPECT_EQ(data.name, "q");
|
||||
EXPECT_THAT(data.linear_terms.terms(),
|
||||
UnorderedElementsAre(Pair(VariableId(6), 7.0)));
|
||||
EXPECT_THAT(
|
||||
data.quadratic_terms.Terms(),
|
||||
UnorderedElementsAre(FieldsAre(VariableId(1), VariableId(2), 3.0),
|
||||
FieldsAre(VariableId(4), VariableId(4), 5.0)));
|
||||
}
|
||||
|
||||
TEST(QuadraticStorageDataTest, Proto) {
|
||||
EXPECT_THAT(SimpleData().Proto(), EquivToProto(SimpleProto()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
199
ortools/math_opt/constraints/quadratic/validator_test.cc
Normal file
199
ortools/math_opt/constraints/quadratic/validator_test.cc
Normal file
@@ -0,0 +1,199 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/quadratic/validator.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/math_opt/core/model_summary.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::AllOf;
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::status::StatusIs;
|
||||
|
||||
constexpr double kInf = std::numeric_limits<double>::infinity();
|
||||
constexpr double kNan = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
IdNameBiMap SimpleVariableUniverse(const std::initializer_list<int64_t> ids) {
|
||||
IdNameBiMap universe;
|
||||
CHECK_OK(universe.BulkUpdate({}, ids, {}));
|
||||
return universe;
|
||||
}
|
||||
|
||||
QuadraticConstraintProto SimpleQuadraticConstraintProto() {
|
||||
QuadraticConstraintProto data;
|
||||
data.mutable_linear_terms()->add_ids(2);
|
||||
data.mutable_linear_terms()->add_values(2.0);
|
||||
data.mutable_quadratic_terms()->add_row_ids(1);
|
||||
data.mutable_quadratic_terms()->add_column_ids(1);
|
||||
data.mutable_quadratic_terms()->add_coefficients(3.0);
|
||||
data.set_lower_bound(4.0);
|
||||
data.set_upper_bound(5.0);
|
||||
data.set_name("c");
|
||||
return data;
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, EmptyConstraintOk) {
|
||||
EXPECT_OK(ValidateConstraint(QuadraticConstraintProto(),
|
||||
SimpleVariableUniverse({})));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, SimpleConstraintOk) {
|
||||
EXPECT_OK(ValidateConstraint(SimpleQuadraticConstraintProto(),
|
||||
SimpleVariableUniverse({1, 2})));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, BadLinearTermIds) {
|
||||
QuadraticConstraintProto data = SimpleQuadraticConstraintProto();
|
||||
data.mutable_linear_terms()->add_ids(1);
|
||||
data.mutable_linear_terms()->add_values(6.0);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("Expected ids to be strictly increasing"),
|
||||
HasSubstr("bad linear term in quadratic constraint"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, PositiveInfLinearTermValue) {
|
||||
QuadraticConstraintProto data = SimpleQuadraticConstraintProto();
|
||||
data.mutable_linear_terms()->set_values(0, kInf);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("Invalid positive infinite value"),
|
||||
HasSubstr("bad linear term in quadratic constraint"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, NegativeInfLinearTermValue) {
|
||||
QuadraticConstraintProto data = SimpleQuadraticConstraintProto();
|
||||
data.mutable_linear_terms()->set_values(0, -kInf);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("Invalid negative infinite value"),
|
||||
HasSubstr("bad linear term in quadratic constraint"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, UnknownLinearTermId) {
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(SimpleQuadraticConstraintProto(),
|
||||
SimpleVariableUniverse({1})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("id 2 not found"),
|
||||
HasSubstr("bad linear term ID in quadratic constraint"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, BadQuadraticTermIds) {
|
||||
QuadraticConstraintProto data = SimpleQuadraticConstraintProto();
|
||||
data.mutable_quadratic_terms()->set_row_ids(0, 2);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("lower triangular entry"),
|
||||
HasSubstr("bad quadratic term in quadratic constraint"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, PositiveInfQuadraticTermValue) {
|
||||
QuadraticConstraintProto data = SimpleQuadraticConstraintProto();
|
||||
data.mutable_quadratic_terms()->set_coefficients(0, kInf);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("Expected finite coefficients"),
|
||||
HasSubstr("bad quadratic term in quadratic constraint"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, NegativeInfQuadraticTermValue) {
|
||||
QuadraticConstraintProto data = SimpleQuadraticConstraintProto();
|
||||
data.mutable_quadratic_terms()->set_coefficients(0, -kInf);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("Expected finite coefficients"),
|
||||
HasSubstr("bad quadratic term in quadratic constraint"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, UnknownQuadraticTermId) {
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(SimpleQuadraticConstraintProto(),
|
||||
SimpleVariableUniverse({2})),
|
||||
StatusIs(
|
||||
absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("Unknown row_id"),
|
||||
HasSubstr("bad quadratic term ID in quadratic constraint"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, PositiveInfLowerBound) {
|
||||
QuadraticConstraintProto data = SimpleQuadraticConstraintProto();
|
||||
data.set_lower_bound(kInf);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("Invalid positive infinite value"),
|
||||
HasSubstr("bad quadratic constraint lower bound"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, NanLowerBound) {
|
||||
QuadraticConstraintProto data = SimpleQuadraticConstraintProto();
|
||||
data.set_lower_bound(kNan);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("Invalid NaN value"),
|
||||
HasSubstr("bad quadratic constraint lower bound"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, NegativeInfUpperBound) {
|
||||
QuadraticConstraintProto data = SimpleQuadraticConstraintProto();
|
||||
data.set_upper_bound(-kInf);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("Invalid negative infinite value"),
|
||||
HasSubstr("bad quadratic constraint upper bound"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, NanUpperBound) {
|
||||
QuadraticConstraintProto data = SimpleQuadraticConstraintProto();
|
||||
data.set_upper_bound(kNan);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("Invalid NaN value"),
|
||||
HasSubstr("bad quadratic constraint upper bound"))));
|
||||
}
|
||||
|
||||
TEST(QuadraticConstraintDataValidatorTest, InvertedBounds) {
|
||||
QuadraticConstraintProto data = SimpleQuadraticConstraintProto();
|
||||
data.set_upper_bound(data.lower_bound() - 1);
|
||||
EXPECT_THAT(ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("Quadratic constraint"),
|
||||
HasSubstr("bounds are inverted"))));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
@@ -28,6 +28,21 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "second_order_cone_constraint_test",
|
||||
srcs = ["second_order_cone_constraint_test.cc"],
|
||||
deps = [
|
||||
":second_order_cone_constraint",
|
||||
"//ortools/base:gmock",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/constraints/util:model_util",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "storage",
|
||||
srcs = ["storage.cc"],
|
||||
@@ -45,6 +60,21 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "storage_test",
|
||||
srcs = ["storage_test.cc"],
|
||||
deps = [
|
||||
":storage",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "validator",
|
||||
srcs = ["validator.cc"],
|
||||
@@ -59,3 +89,19 @@ cc_library(
|
||||
"@com_google_absl//absl/status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "validator_test",
|
||||
srcs = ["validator_test.cc"],
|
||||
deps = [
|
||||
":validator",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:logging",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/core:model_summary",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -15,6 +15,8 @@ set(NAME ${PROJECT_NAME}_math_opt_constraints_second_order_cone)
|
||||
add_library(${NAME} OBJECT)
|
||||
|
||||
file(GLOB _SRCS "*.h" "*.cc")
|
||||
list(FILTER _SRCS EXCLUDE REGEX ".*_test.cc")
|
||||
|
||||
target_sources(${NAME} PRIVATE ${_SRCS})
|
||||
set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
target_include_directories(${NAME} PUBLIC
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/second_order_cone/second_order_cone_constraint.h"
|
||||
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::Pair;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
TEST(SecondOrderConeConstraintTest, Accessors) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
const Variable z(&storage, storage.AddVariable("z"));
|
||||
const SecondOrderConeConstraintData data{
|
||||
.upper_bound = {.coeffs = SparseCoefficientMap({{x.typed_id(), 1.0}}),
|
||||
.offset = 2.0},
|
||||
.arguments_to_norm = {{.coeffs =
|
||||
SparseCoefficientMap({{y.typed_id(), 3.0}}),
|
||||
.offset = 4.0},
|
||||
{.coeffs =
|
||||
SparseCoefficientMap({{z.typed_id(), 5.0}}),
|
||||
.offset = 6.0}},
|
||||
.name = "soc",
|
||||
};
|
||||
const SecondOrderConeConstraint c(&storage,
|
||||
storage.AddAtomicConstraint(data));
|
||||
EXPECT_EQ(c.name(), "soc");
|
||||
EXPECT_EQ(c.storage(), &storage);
|
||||
{
|
||||
const LinearExpression ub = c.UpperBound();
|
||||
EXPECT_EQ(ub.offset(), 2.0);
|
||||
EXPECT_THAT(ub.terms(), UnorderedElementsAre(Pair(x, 1.0)));
|
||||
}
|
||||
{
|
||||
const std::vector<LinearExpression> args = c.ArgumentsToNorm();
|
||||
ASSERT_EQ(args.size(), 2);
|
||||
EXPECT_EQ(args[0].offset(), 4.0);
|
||||
EXPECT_THAT(args[0].terms(), UnorderedElementsAre(Pair(y, 3.0)));
|
||||
EXPECT_EQ(args[1].offset(), 6.0);
|
||||
EXPECT_THAT(args[1].terms(), UnorderedElementsAre(Pair(z, 5.0)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SecondOrderConeConstraintTest, Equality) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
|
||||
const SecondOrderConeConstraint c(
|
||||
&storage, storage.AddAtomicConstraint(SecondOrderConeConstraintData{
|
||||
.upper_bound = {.offset = 1.0}, .name = "c"}));
|
||||
const SecondOrderConeConstraint d(
|
||||
&storage, storage.AddAtomicConstraint(SecondOrderConeConstraintData{
|
||||
.upper_bound = {.offset = 2.0}, .name = "d"}));
|
||||
|
||||
// `d2` is another `SecondOrderConstraint` that points the same constraint in
|
||||
// the indexed storage. It should compares == to `d`.
|
||||
const SecondOrderConeConstraint d2(d.storage(), d.typed_id());
|
||||
|
||||
// `e` has identical data as `d`. It should not compares equal to `d`, though.
|
||||
const SecondOrderConeConstraint e(
|
||||
&storage, storage.AddAtomicConstraint(SecondOrderConeConstraintData{
|
||||
.upper_bound = {.offset = 2.0}, .name = "d"}));
|
||||
|
||||
EXPECT_TRUE(c == c);
|
||||
EXPECT_FALSE(c == d);
|
||||
EXPECT_TRUE(d == d2);
|
||||
EXPECT_FALSE(d == e);
|
||||
EXPECT_FALSE(c != c);
|
||||
EXPECT_TRUE(c != d);
|
||||
EXPECT_FALSE(d != d2);
|
||||
EXPECT_TRUE(d != e);
|
||||
}
|
||||
|
||||
TEST(SecondOrderConeConstraintTest, NonzeroVariables) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
const Variable z(&storage, storage.AddVariable("z"));
|
||||
const SecondOrderConeConstraintData data{
|
||||
.upper_bound = {.coeffs = SparseCoefficientMap({{x.typed_id(), 1.0}}),
|
||||
.offset = 2.0},
|
||||
.arguments_to_norm = {{.coeffs =
|
||||
SparseCoefficientMap({{y.typed_id(), 3.0}}),
|
||||
.offset = 4.0},
|
||||
{.coeffs =
|
||||
SparseCoefficientMap({{z.typed_id(), 5.0}}),
|
||||
.offset = 6.0}},
|
||||
.name = "soc",
|
||||
};
|
||||
const SecondOrderConeConstraint c(&storage,
|
||||
storage.AddAtomicConstraint(data));
|
||||
|
||||
EXPECT_THAT(c.NonzeroVariables(), UnorderedElementsAre(x, y, z));
|
||||
}
|
||||
|
||||
TEST(SecondOrderConeConstraintTest, ToString) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
const Variable z(&storage, storage.AddVariable("z"));
|
||||
const SecondOrderConeConstraintData data{
|
||||
.upper_bound = {.coeffs = SparseCoefficientMap({{x.typed_id(), 1.0}}),
|
||||
.offset = 2.0},
|
||||
.arguments_to_norm = {{.coeffs =
|
||||
SparseCoefficientMap({{y.typed_id(), 3.0}}),
|
||||
.offset = 4.0},
|
||||
{.coeffs =
|
||||
SparseCoefficientMap({{z.typed_id(), 5.0}}),
|
||||
.offset = 6.0}},
|
||||
.name = "soc",
|
||||
};
|
||||
const SecondOrderConeConstraint c(&storage,
|
||||
storage.AddAtomicConstraint(data));
|
||||
|
||||
EXPECT_EQ(c.ToString(), "||{3*y + 4, 5*z + 6}||₂ ≤ x + 2");
|
||||
|
||||
storage.DeleteAtomicConstraint(c.typed_id());
|
||||
EXPECT_EQ(c.ToString(), kDeletedConstraintDefaultDescription);
|
||||
}
|
||||
|
||||
TEST(SecondOrderConeConstraintTest, OutputStreaming) {
|
||||
ModelStorage storage;
|
||||
const SecondOrderConeConstraint q(
|
||||
&storage,
|
||||
storage.AddAtomicConstraint(SecondOrderConeConstraintData{.name = "q"}));
|
||||
const SecondOrderConeConstraint anonymous(
|
||||
&storage,
|
||||
storage.AddAtomicConstraint(SecondOrderConeConstraintData{.name = ""}));
|
||||
|
||||
auto to_string = [](SecondOrderConeConstraint c) {
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
};
|
||||
|
||||
EXPECT_EQ(to_string(q), "q");
|
||||
EXPECT_EQ(to_string(anonymous),
|
||||
absl::StrCat("__soc_con#", anonymous.id(), "__"));
|
||||
}
|
||||
|
||||
TEST(SecondOrderConeConstraintTest, NameAfterDeletion) {
|
||||
ModelStorage storage;
|
||||
const SecondOrderConeConstraintData data{.name = "soc"};
|
||||
const SecondOrderConeConstraint c(&storage,
|
||||
storage.AddAtomicConstraint(data));
|
||||
|
||||
ASSERT_EQ(c.name(), "soc");
|
||||
|
||||
storage.DeleteAtomicConstraint(c.typed_id());
|
||||
EXPECT_EQ(c.name(), kDeletedConstraintDefaultDescription);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
130
ortools/math_opt/constraints/second_order_cone/storage_test.cc
Normal file
130
ortools/math_opt/constraints/second_order_cone/storage_test.cc
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/second_order_cone/storage.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::AllOf;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::Eq;
|
||||
using ::testing::EquivToProto;
|
||||
using ::testing::Field;
|
||||
using ::testing::Matcher;
|
||||
using ::testing::Property;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
using ::testing::UnorderedElementsAreArray;
|
||||
|
||||
SecondOrderConeConstraintProto SimpleProto() {
|
||||
SecondOrderConeConstraintProto proto;
|
||||
proto.set_name("soc");
|
||||
{
|
||||
auto& expr = *proto.mutable_upper_bound();
|
||||
expr.add_ids(0);
|
||||
expr.add_coefficients(1.0);
|
||||
}
|
||||
{
|
||||
auto& expr = *proto.add_arguments_to_norm();
|
||||
expr.add_ids(3);
|
||||
expr.add_ids(6);
|
||||
expr.add_coefficients(2.0);
|
||||
expr.add_coefficients(3.0);
|
||||
expr.set_offset(4.0);
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
SecondOrderConeConstraintData SimpleData() {
|
||||
return {
|
||||
.upper_bound = {.coeffs = SparseCoefficientMap({{VariableId(0), 1.0}}),
|
||||
.offset = 0.0},
|
||||
.arguments_to_norm = {{.coeffs = SparseCoefficientMap(
|
||||
{{VariableId(3), 2.0}, {VariableId(6), 3.0}}),
|
||||
.offset = 4.0}},
|
||||
.name = "soc"};
|
||||
}
|
||||
|
||||
Matcher<LinearExpressionData> LinearExprEquals(
|
||||
const LinearExpressionData& other) {
|
||||
return AllOf(
|
||||
Field("offset", &LinearExpressionData::offset, Eq(other.offset)),
|
||||
Field("coeffs", &LinearExpressionData::coeffs,
|
||||
Property("terms", &SparseCoefficientMap::terms,
|
||||
UnorderedElementsAreArray(other.coeffs.terms()))));
|
||||
}
|
||||
|
||||
TEST(SecondOrderConeStorageDataTest, RelatedVariables) {
|
||||
EXPECT_THAT(
|
||||
SimpleData().RelatedVariables(),
|
||||
UnorderedElementsAre(VariableId(0), VariableId(3), VariableId(6)));
|
||||
}
|
||||
|
||||
TEST(SecondOrderConeStorageDataTest, DeleteVariable) {
|
||||
SecondOrderConeConstraintData data = SimpleData();
|
||||
data.DeleteVariable(VariableId(3));
|
||||
EXPECT_THAT(
|
||||
data.upper_bound,
|
||||
LinearExprEquals({.coeffs = SparseCoefficientMap({{VariableId(0), 1.0}}),
|
||||
.offset = 0.0}));
|
||||
EXPECT_THAT(data.arguments_to_norm,
|
||||
ElementsAre(LinearExprEquals(
|
||||
{.coeffs = SparseCoefficientMap({{VariableId(6), 3.0}}),
|
||||
.offset = 4.0})));
|
||||
|
||||
data.DeleteVariable(VariableId(0));
|
||||
EXPECT_THAT(data.upper_bound,
|
||||
LinearExprEquals({.coeffs = {}, .offset = 0.0}));
|
||||
EXPECT_THAT(data.arguments_to_norm,
|
||||
ElementsAre(LinearExprEquals(
|
||||
{.coeffs = SparseCoefficientMap({{VariableId(6), 3.0}}),
|
||||
.offset = 4.0})));
|
||||
|
||||
data.DeleteVariable(VariableId(6));
|
||||
EXPECT_THAT(data.upper_bound,
|
||||
LinearExprEquals({.coeffs = {}, .offset = 0.0}));
|
||||
EXPECT_THAT(data.arguments_to_norm,
|
||||
ElementsAre(LinearExprEquals({.coeffs = {}, .offset = 4.0})));
|
||||
}
|
||||
|
||||
TEST(SecondOrderConeStorageDataTest, FromProto) {
|
||||
const auto data = SecondOrderConeConstraintData::FromProto(SimpleProto());
|
||||
EXPECT_EQ(data.name, "soc");
|
||||
EXPECT_THAT(
|
||||
data.upper_bound,
|
||||
LinearExprEquals({.coeffs = SparseCoefficientMap({{VariableId(0), 1.0}}),
|
||||
.offset = 0.0}));
|
||||
EXPECT_THAT(data.arguments_to_norm,
|
||||
ElementsAre(LinearExprEquals(
|
||||
{.coeffs = SparseCoefficientMap(
|
||||
{{VariableId(3), 2.0}, {VariableId(6), 3.0}}),
|
||||
.offset = 4.0})));
|
||||
}
|
||||
|
||||
TEST(SosStorageDataTest, Proto) {
|
||||
EXPECT_THAT(SimpleData().Proto(), EquivToProto(SimpleProto()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/second_order_cone/validator.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/math_opt/core/model_summary.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::status::StatusIs;
|
||||
|
||||
IdNameBiMap SimpleVariableUniverse(const std::initializer_list<int64_t> ids) {
|
||||
IdNameBiMap universe;
|
||||
CHECK_OK(universe.BulkUpdate({}, ids, {}));
|
||||
return universe;
|
||||
}
|
||||
|
||||
SecondOrderConeConstraintProto SimpleSecondOrderConeConstraint() {
|
||||
SecondOrderConeConstraintProto data;
|
||||
{
|
||||
LinearExpressionProto& expr = *data.mutable_upper_bound();
|
||||
expr.add_ids(1);
|
||||
expr.add_coefficients(2.0);
|
||||
expr.set_offset(3.0);
|
||||
}
|
||||
{
|
||||
LinearExpressionProto& expr = *data.add_arguments_to_norm();
|
||||
expr.add_ids(2);
|
||||
expr.add_coefficients(3.0);
|
||||
expr.set_offset(4.0);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
TEST(SecondOrderConeValidatorTest, EmptyConstraintOk) {
|
||||
EXPECT_OK(ValidateConstraint(SecondOrderConeConstraintProto(),
|
||||
SimpleVariableUniverse({})));
|
||||
}
|
||||
|
||||
TEST(SecondOrderConeValidatorTest, SimpleConstraintOk) {
|
||||
EXPECT_OK(ValidateConstraint(SimpleSecondOrderConeConstraint(),
|
||||
SimpleVariableUniverse({1, 2})));
|
||||
}
|
||||
|
||||
TEST(SosConstraintValidatorTest, InvalidUpperBound) {
|
||||
SecondOrderConeConstraintProto data = SimpleSecondOrderConeConstraint();
|
||||
data.mutable_upper_bound()->add_ids(2);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1, 2})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("upper_bound")));
|
||||
}
|
||||
|
||||
TEST(SosConstraintValidatorTest, InvalidArgumentsToNorm) {
|
||||
SecondOrderConeConstraintProto data = SimpleSecondOrderConeConstraint();
|
||||
data.mutable_arguments_to_norm(0)->add_ids(2);
|
||||
EXPECT_THAT(ValidateConstraint(data, SimpleVariableUniverse({1})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
HasSubstr("arguments_to_norm")));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
@@ -29,6 +29,23 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "sos1_constraint_test",
|
||||
srcs = ["sos1_constraint_test.cc"],
|
||||
deps = [
|
||||
":sos1_constraint",
|
||||
"//ortools/base:gmock",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/math_opt/constraints/util:model_util",
|
||||
"//ortools/math_opt/cpp:math_opt",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"//ortools/util:fp_roundtrip_conv_testing",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "sos2_constraint",
|
||||
srcs = ["sos2_constraint.cc"],
|
||||
@@ -45,6 +62,23 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "sos2_constraint_test",
|
||||
srcs = ["sos2_constraint_test.cc"],
|
||||
deps = [
|
||||
":sos2_constraint",
|
||||
"//ortools/base:gmock",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/math_opt/constraints/util:model_util",
|
||||
"//ortools/math_opt/cpp:math_opt",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"//ortools/util:fp_roundtrip_conv_testing",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "storage",
|
||||
hdrs = ["storage.h"],
|
||||
@@ -60,6 +94,21 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "storage_test",
|
||||
srcs = ["storage_test.cc"],
|
||||
deps = [
|
||||
":storage",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "util",
|
||||
hdrs = ["util.h"],
|
||||
@@ -85,3 +134,19 @@ cc_library(
|
||||
"@com_google_absl//absl/status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "validator_test",
|
||||
srcs = ["validator_test.cc"],
|
||||
deps = [
|
||||
":validator",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:logging",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/core:model_summary",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -15,6 +15,8 @@ set(NAME ${PROJECT_NAME}_math_opt_constraints_sos)
|
||||
add_library(${NAME} OBJECT)
|
||||
|
||||
file(GLOB _SRCS "*.h" "*.cc")
|
||||
list(FILTER _SRCS EXCLUDE REGEX ".*_test.cc")
|
||||
|
||||
target_sources(${NAME} PRIVATE ${_SRCS})
|
||||
set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
target_include_directories(${NAME} PUBLIC
|
||||
|
||||
150
ortools/math_opt/constraints/sos/sos1_constraint_test.cc
Normal file
150
ortools/math_opt/constraints/sos/sos1_constraint_test.cc
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/sos/sos1_constraint.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/math_opt.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
#include "ortools/util/fp_roundtrip_conv_testing.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::Pair;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
TEST(Sos1ConstraintTest, Accessors) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
const Sos1ConstraintData data(
|
||||
{{.coeffs = SparseCoefficientMap({{x.typed_id(), 1.0}}), .offset = 0.0},
|
||||
{.coeffs =
|
||||
SparseCoefficientMap({{x.typed_id(), 0.5}, {y.typed_id(), 0.5}}),
|
||||
.offset = -1.0}},
|
||||
{3.0, 2.0}, "c");
|
||||
const Sos1Constraint c(&storage, storage.AddAtomicConstraint(data));
|
||||
EXPECT_EQ(c.name(), "c");
|
||||
ASSERT_EQ(c.num_expressions(), 2);
|
||||
EXPECT_THAT(c.Expression(0).terms(), UnorderedElementsAre(Pair(x, 1.0)));
|
||||
EXPECT_EQ(c.Expression(0).offset(), 0.0);
|
||||
EXPECT_TRUE(c.has_weights());
|
||||
EXPECT_EQ(c.weight(0), 3.0);
|
||||
EXPECT_THAT(c.Expression(1).terms(),
|
||||
UnorderedElementsAre(Pair(x, 0.5), Pair(y, 0.5)));
|
||||
EXPECT_EQ(c.Expression(1).offset(), -1.0);
|
||||
EXPECT_EQ(c.weight(1), 2.0);
|
||||
}
|
||||
|
||||
TEST(Sos1ConstraintTest, Equality) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
|
||||
const Sos1Constraint c(
|
||||
&storage, storage.AddAtomicConstraint(Sos1ConstraintData({}, {}, "c")));
|
||||
const Sos1Constraint d(
|
||||
&storage, storage.AddAtomicConstraint(Sos1ConstraintData({}, {}, "d")));
|
||||
|
||||
// `d2` is another `Sos1Constraint` that points the same constraint in the
|
||||
// indexed storage. It should compares == to `d`.
|
||||
const Sos1Constraint d2(d.storage(), d.typed_id());
|
||||
|
||||
// `e` has identical data as `d`. It should not compares equal to `d`, though.
|
||||
const Sos1Constraint e(
|
||||
&storage, storage.AddAtomicConstraint(Sos1ConstraintData({}, {}, "d")));
|
||||
|
||||
EXPECT_TRUE(c == c);
|
||||
EXPECT_FALSE(c == d);
|
||||
EXPECT_TRUE(d == d2);
|
||||
EXPECT_FALSE(d == e);
|
||||
EXPECT_FALSE(c != c);
|
||||
EXPECT_TRUE(c != d);
|
||||
EXPECT_FALSE(d != d2);
|
||||
EXPECT_TRUE(d != e);
|
||||
}
|
||||
|
||||
TEST(Sos1ConstraintTest, NonzeroVariables) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
const Variable z(&storage, storage.AddVariable("z"));
|
||||
const Variable w(&storage, storage.AddVariable("w"));
|
||||
const Sos1ConstraintData data(
|
||||
{{.coeffs = SparseCoefficientMap({{z.typed_id(), 1.0}}), .offset = 0.0},
|
||||
{.coeffs =
|
||||
SparseCoefficientMap({{x.typed_id(), 0.5}, {y.typed_id(), 0.5}}),
|
||||
.offset = -1.0}},
|
||||
{3.0, 2.0}, "c");
|
||||
const Sos1Constraint c(&storage, storage.AddAtomicConstraint(data));
|
||||
|
||||
EXPECT_THAT(c.NonzeroVariables(), UnorderedElementsAre(x, y, z));
|
||||
}
|
||||
|
||||
TEST(Sos1ConstraintTest, ToString) {
|
||||
Model model;
|
||||
const Variable x = model.AddVariable("x");
|
||||
const Variable y = model.AddVariable("y");
|
||||
|
||||
const Sos1Constraint c =
|
||||
model.AddSos1Constraint({x, y}, {3.0, kRoundTripTestNumber}, "c");
|
||||
EXPECT_EQ(c.ToString(), absl::StrCat("{x, y} is SOS1 with weights {3, ",
|
||||
kRoundTripTestNumberStr, "}"));
|
||||
|
||||
const Sos1Constraint d = model.AddSos1Constraint({2 * y - 1, 1.0}, {}, "d");
|
||||
EXPECT_EQ(d.ToString(), "{2*y - 1, 1} is SOS1");
|
||||
|
||||
model.DeleteSos1Constraint(c);
|
||||
EXPECT_EQ(c.ToString(), kDeletedConstraintDefaultDescription);
|
||||
}
|
||||
|
||||
TEST(Sos1ConstraintTest, OutputStreaming) {
|
||||
ModelStorage storage;
|
||||
const Sos1Constraint q(
|
||||
&storage, storage.AddAtomicConstraint(Sos1ConstraintData({}, {}, "q")));
|
||||
const Sos1Constraint anonymous(
|
||||
&storage, storage.AddAtomicConstraint(Sos1ConstraintData({}, {}, "")));
|
||||
|
||||
auto to_string = [](Sos1Constraint c) {
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
};
|
||||
|
||||
EXPECT_EQ(to_string(q), "q");
|
||||
EXPECT_EQ(to_string(anonymous),
|
||||
absl::StrCat("__sos1_con#", anonymous.id(), "__"));
|
||||
}
|
||||
|
||||
TEST(Sos1ConstraintTest, NameAfterDeletion) {
|
||||
Model model;
|
||||
const Variable x = model.AddVariable("x");
|
||||
const Sos1Constraint c = model.AddSos1Constraint({x}, {}, "c");
|
||||
ASSERT_EQ(c.name(), "c");
|
||||
|
||||
model.DeleteSos1Constraint(c);
|
||||
EXPECT_EQ(c.name(), kDeletedConstraintDefaultDescription);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
150
ortools/math_opt/constraints/sos/sos2_constraint_test.cc
Normal file
150
ortools/math_opt/constraints/sos/sos2_constraint_test.cc
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/sos/sos2_constraint.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/math_opt.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
#include "ortools/util/fp_roundtrip_conv_testing.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::Pair;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
TEST(Sos2ConstraintTest, Accessors) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
const Sos2ConstraintData data(
|
||||
{{.coeffs = SparseCoefficientMap({{x.typed_id(), 1.0}}), .offset = 0.0},
|
||||
{.coeffs =
|
||||
SparseCoefficientMap({{x.typed_id(), 0.5}, {y.typed_id(), 0.5}}),
|
||||
.offset = -1.0}},
|
||||
{3.0, 2.0}, "c");
|
||||
const Sos2Constraint c(&storage, storage.AddAtomicConstraint(data));
|
||||
EXPECT_EQ(c.name(), "c");
|
||||
ASSERT_EQ(c.num_expressions(), 2);
|
||||
EXPECT_THAT(c.Expression(0).terms(), UnorderedElementsAre(Pair(x, 1.0)));
|
||||
EXPECT_EQ(c.Expression(0).offset(), 0.0);
|
||||
EXPECT_TRUE(c.has_weights());
|
||||
EXPECT_EQ(c.weight(0), 3.0);
|
||||
EXPECT_THAT(c.Expression(1).terms(),
|
||||
UnorderedElementsAre(Pair(x, 0.5), Pair(y, 0.5)));
|
||||
EXPECT_EQ(c.Expression(1).offset(), -1.0);
|
||||
EXPECT_EQ(c.weight(1), 2.0);
|
||||
}
|
||||
|
||||
TEST(Sos2ConstraintTest, Equality) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
|
||||
const Sos2Constraint c(
|
||||
&storage, storage.AddAtomicConstraint(Sos2ConstraintData({}, {}, "c")));
|
||||
const Sos2Constraint d(
|
||||
&storage, storage.AddAtomicConstraint(Sos2ConstraintData({}, {}, "d")));
|
||||
|
||||
// `d2` is another `Sos2Constraint` that points the same constraint in the
|
||||
// indexed storage. It should compares == to `d`.
|
||||
const Sos2Constraint d2(d.storage(), d.typed_id());
|
||||
|
||||
// `e` has identical data as `d`. It should not compares equal to `d`, though.
|
||||
const Sos2Constraint e(
|
||||
&storage, storage.AddAtomicConstraint(Sos2ConstraintData({}, {}, "d")));
|
||||
|
||||
EXPECT_TRUE(c == c);
|
||||
EXPECT_FALSE(c == d);
|
||||
EXPECT_TRUE(d == d2);
|
||||
EXPECT_FALSE(d == e);
|
||||
EXPECT_FALSE(c != c);
|
||||
EXPECT_TRUE(c != d);
|
||||
EXPECT_FALSE(d != d2);
|
||||
EXPECT_TRUE(d != e);
|
||||
}
|
||||
|
||||
TEST(Sos2ConstraintTest, NonzeroVariables) {
|
||||
ModelStorage storage;
|
||||
const Variable x(&storage, storage.AddVariable("x"));
|
||||
const Variable y(&storage, storage.AddVariable("y"));
|
||||
const Variable z(&storage, storage.AddVariable("z"));
|
||||
const Variable w(&storage, storage.AddVariable("w"));
|
||||
const Sos2ConstraintData data(
|
||||
{{.coeffs = SparseCoefficientMap({{z.typed_id(), 1.0}}), .offset = 0.0},
|
||||
{.coeffs =
|
||||
SparseCoefficientMap({{x.typed_id(), 0.5}, {y.typed_id(), 0.5}}),
|
||||
.offset = -1.0}},
|
||||
{3.0, 2.0}, "c");
|
||||
const Sos2Constraint c(&storage, storage.AddAtomicConstraint(data));
|
||||
|
||||
EXPECT_THAT(c.NonzeroVariables(), UnorderedElementsAre(x, y, z));
|
||||
}
|
||||
|
||||
TEST(Sos2ConstraintTest, ToString) {
|
||||
Model model;
|
||||
const Variable x = model.AddVariable("x");
|
||||
const Variable y = model.AddVariable("y");
|
||||
|
||||
const Sos2Constraint c =
|
||||
model.AddSos2Constraint({x, y}, {3.0, kRoundTripTestNumber}, "c");
|
||||
EXPECT_EQ(c.ToString(), absl::StrCat("{x, y} is SOS2 with weights {3, ",
|
||||
kRoundTripTestNumberStr, "}"));
|
||||
|
||||
const Sos2Constraint d = model.AddSos2Constraint({2 * y - 1, 1.0}, {}, "d");
|
||||
EXPECT_EQ(d.ToString(), "{2*y - 1, 1} is SOS2");
|
||||
|
||||
model.DeleteSos2Constraint(c);
|
||||
EXPECT_EQ(c.ToString(), kDeletedConstraintDefaultDescription);
|
||||
}
|
||||
|
||||
TEST(Sos2ConstraintTest, OutputStreaming) {
|
||||
ModelStorage storage;
|
||||
const Sos2Constraint q(
|
||||
&storage, storage.AddAtomicConstraint(Sos2ConstraintData({}, {}, "q")));
|
||||
const Sos2Constraint anonymous(
|
||||
&storage, storage.AddAtomicConstraint(Sos2ConstraintData({}, {}, "")));
|
||||
|
||||
auto to_string = [](Sos2Constraint c) {
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
};
|
||||
|
||||
EXPECT_EQ(to_string(q), "q");
|
||||
EXPECT_EQ(to_string(anonymous),
|
||||
absl::StrCat("__sos2_con#", anonymous.id(), "__"));
|
||||
}
|
||||
|
||||
TEST(Sos2ConstraintTest, NameAfterDeletion) {
|
||||
Model model;
|
||||
const Variable x = model.AddVariable("x");
|
||||
const Sos2Constraint c = model.AddSos2Constraint({x}, {}, "c");
|
||||
ASSERT_EQ(c.name(), "c");
|
||||
|
||||
model.DeleteSos2Constraint(c);
|
||||
EXPECT_EQ(c.name(), kDeletedConstraintDefaultDescription);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
156
ortools/math_opt/constraints/sos/storage_test.cc
Normal file
156
ortools/math_opt/constraints/sos/storage_test.cc
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/sos/storage.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
// We test with SOS1; there is no difference with SOS2 at the storage level.
|
||||
using TestConstraintData = Sos1ConstraintData;
|
||||
|
||||
using ::testing::AllOf;
|
||||
using ::testing::Eq;
|
||||
using ::testing::EquivToProto;
|
||||
using ::testing::Field;
|
||||
using ::testing::Matcher;
|
||||
using ::testing::Property;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
using ::testing::UnorderedElementsAreArray;
|
||||
|
||||
SosConstraintProto SimpleProto(const bool with_weights = true) {
|
||||
SosConstraintProto proto;
|
||||
proto.set_name("q");
|
||||
if (with_weights) {
|
||||
proto.add_weights(2.0);
|
||||
proto.add_weights(3.0);
|
||||
}
|
||||
{
|
||||
auto& expr = *proto.add_expressions();
|
||||
expr.add_ids(0);
|
||||
expr.add_coefficients(1.0);
|
||||
}
|
||||
{
|
||||
auto& expr = *proto.add_expressions();
|
||||
expr.add_ids(3);
|
||||
expr.add_ids(6);
|
||||
expr.add_coefficients(2.0);
|
||||
expr.add_coefficients(3.0);
|
||||
expr.set_offset(4.0);
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
TestConstraintData SimpleData(const bool with_weights = true) {
|
||||
std::vector<double> weights;
|
||||
if (with_weights) {
|
||||
weights.push_back(2.0);
|
||||
weights.push_back(3.0);
|
||||
}
|
||||
return TestConstraintData(
|
||||
{{.coeffs = SparseCoefficientMap({{VariableId(0), 1.0}}), .offset = 0.0},
|
||||
{.coeffs =
|
||||
SparseCoefficientMap({{VariableId(3), 2.0}, {VariableId(6), 3.0}}),
|
||||
.offset = 4.0}},
|
||||
weights, "q");
|
||||
}
|
||||
|
||||
Matcher<LinearExpressionData> LinearExprEquals(
|
||||
const LinearExpressionData& other) {
|
||||
return AllOf(
|
||||
Field("offset", &LinearExpressionData::offset, Eq(other.offset)),
|
||||
Field("coeffs", &LinearExpressionData::coeffs,
|
||||
Property("terms", &SparseCoefficientMap::terms,
|
||||
UnorderedElementsAreArray(other.coeffs.terms()))));
|
||||
}
|
||||
|
||||
TEST(SosStorageDataTest, RelatedVariables) {
|
||||
EXPECT_THAT(
|
||||
SimpleData().RelatedVariables(),
|
||||
UnorderedElementsAre(VariableId(0), VariableId(3), VariableId(6)));
|
||||
}
|
||||
|
||||
TEST(SosStorageDataTest, DeleteVariable) {
|
||||
TestConstraintData data = SimpleData();
|
||||
data.DeleteVariable(VariableId(3));
|
||||
ASSERT_THAT(data.num_expressions(), 2);
|
||||
EXPECT_EQ(data.weight(0), 2.0);
|
||||
EXPECT_THAT(
|
||||
data.expression(0),
|
||||
LinearExprEquals({.coeffs = SparseCoefficientMap({{VariableId(0), 1.0}}),
|
||||
.offset = 0.0}));
|
||||
EXPECT_EQ(data.weight(1), 3.0);
|
||||
EXPECT_THAT(
|
||||
data.expression(1),
|
||||
LinearExprEquals({.coeffs = SparseCoefficientMap({{VariableId(6), 3.0}}),
|
||||
.offset = 4.0}));
|
||||
|
||||
data.DeleteVariable(VariableId(0));
|
||||
ASSERT_THAT(data.num_expressions(), 2);
|
||||
EXPECT_EQ(data.weight(0), 2.0);
|
||||
EXPECT_THAT(data.expression(0),
|
||||
LinearExprEquals(LinearExpressionData{.offset = 0.0}));
|
||||
EXPECT_EQ(data.weight(1), 3.0);
|
||||
EXPECT_THAT(
|
||||
data.expression(1),
|
||||
LinearExprEquals({.coeffs = SparseCoefficientMap({{VariableId(6), 3.0}}),
|
||||
.offset = 4.0}));
|
||||
data.DeleteVariable(VariableId(6));
|
||||
ASSERT_THAT(data.num_expressions(), 2);
|
||||
EXPECT_EQ(data.weight(0), 2.0);
|
||||
EXPECT_THAT(data.expression(0),
|
||||
LinearExprEquals(LinearExpressionData{.offset = 0.0}));
|
||||
EXPECT_EQ(data.weight(1), 3.0);
|
||||
EXPECT_THAT(data.expression(1),
|
||||
LinearExprEquals(LinearExpressionData{.offset = 4.0}));
|
||||
}
|
||||
|
||||
TEST(SosStorageDataTest, FromProto) {
|
||||
const auto data = TestConstraintData::FromProto(SimpleProto());
|
||||
EXPECT_EQ(data.name(), "q");
|
||||
ASSERT_THAT(data.num_expressions(), 2);
|
||||
EXPECT_EQ(data.weight(0), 2.0);
|
||||
EXPECT_THAT(
|
||||
data.expression(0),
|
||||
LinearExprEquals({.coeffs = SparseCoefficientMap({{VariableId(0), 1.0}}),
|
||||
.offset = 0.0}));
|
||||
EXPECT_EQ(data.weight(1), 3.0);
|
||||
EXPECT_THAT(
|
||||
data.expression(1),
|
||||
LinearExprEquals({.coeffs = SparseCoefficientMap(
|
||||
{{VariableId(3), 2.0}, {VariableId(6), 3.0}}),
|
||||
.offset = 4.0}));
|
||||
}
|
||||
|
||||
TEST(SosStorageDataTest, Proto) {
|
||||
EXPECT_THAT(SimpleData().Proto(), EquivToProto(SimpleProto()));
|
||||
}
|
||||
|
||||
TEST(SosStorageDataTest, ProtoUnsetWeights) {
|
||||
EXPECT_THAT(SimpleData(/*with_weights=*/false).Proto(),
|
||||
EquivToProto(SimpleProto(/*with_weights=*/false)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
110
ortools/math_opt/constraints/sos/validator_test.cc
Normal file
110
ortools/math_opt/constraints/sos/validator_test.cc
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/sos/validator.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/math_opt/core/model_summary.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::AllOf;
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::status::StatusIs;
|
||||
|
||||
IdNameBiMap SimpleVariableUniverse(const std::initializer_list<int64_t> ids) {
|
||||
IdNameBiMap universe;
|
||||
CHECK_OK(universe.BulkUpdate({}, ids, {}));
|
||||
return universe;
|
||||
}
|
||||
|
||||
SosConstraintProto SimpleSosConstraintProto() {
|
||||
SosConstraintProto data;
|
||||
LinearExpressionProto& expr = *data.add_expressions();
|
||||
expr.add_ids(1);
|
||||
expr.add_coefficients(3.0);
|
||||
expr.set_offset(4.0);
|
||||
data.add_weights(2.0);
|
||||
return data;
|
||||
}
|
||||
|
||||
TEST(SosDataValidatorTest, EmptyConstraintOk) {
|
||||
EXPECT_OK(
|
||||
ValidateConstraint(SosConstraintProto(), SimpleVariableUniverse({})));
|
||||
}
|
||||
|
||||
TEST(SosConstraintValidatorTest, SimpleConstraintOk) {
|
||||
EXPECT_OK(ValidateConstraint(SimpleSosConstraintProto(),
|
||||
SimpleVariableUniverse({1})));
|
||||
}
|
||||
|
||||
TEST(SosConstraintValidatorTest, WeightsExpressionSizeMismatch) {
|
||||
SosConstraintProto data = SimpleSosConstraintProto();
|
||||
data.add_weights(3.0);
|
||||
EXPECT_THAT(
|
||||
ValidateConstraint(data, SimpleVariableUniverse({1})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("mismatch")));
|
||||
}
|
||||
|
||||
TEST(SosConstraintValidatorTest, InvalidLinearExpression) {
|
||||
SosConstraintProto data = SimpleSosConstraintProto();
|
||||
data.mutable_expressions(0)->add_ids(2);
|
||||
EXPECT_THAT(ValidateConstraint(data, SimpleVariableUniverse({1})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
HasSubstr("linear expression")));
|
||||
}
|
||||
|
||||
TEST(SosConstraintValidatorTest, InfWeight) {
|
||||
SosConstraintProto data = SimpleSosConstraintProto();
|
||||
data.set_weights(0, std::numeric_limits<double>::infinity());
|
||||
EXPECT_THAT(ValidateConstraint(data, SimpleVariableUniverse({1})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("value: inf"),
|
||||
HasSubstr("Invalid SOS weight"))));
|
||||
}
|
||||
|
||||
TEST(SosConstraintValidatorTest, NanWeight) {
|
||||
SosConstraintProto data = SimpleSosConstraintProto();
|
||||
data.set_weights(0, std::numeric_limits<double>::quiet_NaN());
|
||||
EXPECT_THAT(ValidateConstraint(data, SimpleVariableUniverse({1})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("value: nan"),
|
||||
HasSubstr("Invalid SOS weight"))));
|
||||
}
|
||||
|
||||
TEST(SosConstraintValidatorTest, RepeatWeight) {
|
||||
SosConstraintProto data;
|
||||
data.add_expressions();
|
||||
data.add_expressions();
|
||||
data.add_weights(1.0);
|
||||
data.add_weights(1.0);
|
||||
EXPECT_THAT(ValidateConstraint(data, SimpleVariableUniverse({1})),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
HasSubstr("duplicate weight")));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
@@ -27,3 +27,18 @@ cc_library(
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "model_util_test",
|
||||
srcs = ["model_util_test.cc"],
|
||||
deps = [
|
||||
":model_util",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/constraints/quadratic:storage",
|
||||
"//ortools/math_opt/cpp:math_opt",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -15,6 +15,8 @@ set(NAME ${PROJECT_NAME}_math_opt_constraints_util)
|
||||
add_library(${NAME} OBJECT)
|
||||
|
||||
file(GLOB _SRCS "*.h" "*.cc")
|
||||
list(FILTER _SRCS EXCLUDE REGEX ".*_test.cc")
|
||||
|
||||
target_sources(${NAME} PRIVATE ${_SRCS})
|
||||
set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
target_include_directories(${NAME} PUBLIC
|
||||
|
||||
85
ortools/math_opt/constraints/util/model_util_test.cc
Normal file
85
ortools/math_opt/constraints/util/model_util_test.cc
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2010-2024 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.
|
||||
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/quadratic/storage.h"
|
||||
#include "ortools/math_opt/cpp/math_opt.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace {
|
||||
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::Pair;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
using TestConstraint = QuadraticConstraint;
|
||||
using TestData = QuadraticConstraintData;
|
||||
using TestConstraintId = QuadraticConstraintId;
|
||||
|
||||
TEST(ModelUtilTest, ToLinearExpression) {
|
||||
const VariableId u(0);
|
||||
const VariableId v(1);
|
||||
|
||||
LinearExpressionData data{.offset = 4.0};
|
||||
data.coeffs.set(u, 2.0);
|
||||
data.coeffs.set(v, 3.0);
|
||||
const ModelStorage storage;
|
||||
const LinearExpression expr = ToLinearExpression(storage, data);
|
||||
EXPECT_EQ(expr.storage(), &storage);
|
||||
EXPECT_EQ(expr.offset(), 4.0);
|
||||
EXPECT_THAT(expr.terms(),
|
||||
UnorderedElementsAre(Pair(Variable(&storage, u), 2.0),
|
||||
Pair(Variable(&storage, v), 3.0)));
|
||||
}
|
||||
|
||||
TEST(ModelUtilTest, FromLinearExpression) {
|
||||
Model model;
|
||||
const Variable x = model.AddVariable();
|
||||
const Variable y = model.AddVariable();
|
||||
const LinearExpression expr = 2.0 * x + 3.0 * y + 4.0;
|
||||
|
||||
const auto [coeffs, offset] = FromLinearExpression(expr);
|
||||
EXPECT_THAT(coeffs.terms(), UnorderedElementsAre(Pair(x.typed_id(), 2.0),
|
||||
Pair(y.typed_id(), 3.0)));
|
||||
EXPECT_EQ(offset, 4.0);
|
||||
}
|
||||
|
||||
TEST(ModelUtilTest, AtomicConstraints) {
|
||||
ModelStorage storage;
|
||||
const TestConstraintId c = storage.AddAtomicConstraint(TestData());
|
||||
const TestConstraintId d = storage.AddAtomicConstraint(TestData());
|
||||
|
||||
EXPECT_THAT(AtomicConstraints<TestConstraint>(storage),
|
||||
UnorderedElementsAre(TestConstraint(&storage, c),
|
||||
TestConstraint(&storage, d)));
|
||||
}
|
||||
|
||||
TEST(ModelUtilTest, SortedAtomicConstraints) {
|
||||
ModelStorage storage;
|
||||
const TestConstraintId c = storage.AddAtomicConstraint(TestData());
|
||||
const TestConstraintId d = storage.AddAtomicConstraint(TestData());
|
||||
|
||||
EXPECT_THAT(
|
||||
SortedAtomicConstraints<TestConstraint>(storage),
|
||||
ElementsAre(TestConstraint(&storage, c), TestConstraint(&storage, d)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::math_opt
|
||||
Reference in New Issue
Block a user