math_opt: update from google3
This commit is contained in:
@@ -32,6 +32,8 @@ namespace operations_research::math_opt {
|
||||
|
||||
// A value type that references an indicator constraint from ModelStorage.
|
||||
// Usually this type is passed by copy.
|
||||
//
|
||||
// This type implements https://abseil.io/docs/cpp/guides/hash.
|
||||
class IndicatorConstraint {
|
||||
public:
|
||||
// The typed integer used for ids.
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace operations_research::math_opt {
|
||||
|
||||
// A value type that references a quadratic constraint from ModelStorage.
|
||||
// Usually this type is passed by copy.
|
||||
//
|
||||
// This type implements https://abseil.io/docs/cpp/guides/hash.
|
||||
class QuadraticConstraint {
|
||||
public:
|
||||
// The typed integer used for ids.
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace operations_research::math_opt {
|
||||
|
||||
// A value type that references a second-order cone constraint from
|
||||
// ModelStorage. Usually this type is passed by copy.
|
||||
//
|
||||
// This type implements https://abseil.io/docs/cpp/guides/hash.
|
||||
class SecondOrderConeConstraint {
|
||||
public:
|
||||
// The typed integer used for ids.
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace operations_research::math_opt {
|
||||
|
||||
// A value type that references a SOS1 constraint from ModelStorage.
|
||||
// Usually this type is passed by copy.
|
||||
//
|
||||
// This type implements https://abseil.io/docs/cpp/guides/hash.
|
||||
class Sos1Constraint {
|
||||
public:
|
||||
// The typed integer used for ids.
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace operations_research::math_opt {
|
||||
|
||||
// A value type that references a SOS2 constraint from ModelStorage.
|
||||
// Usually this type is passed by copy.
|
||||
//
|
||||
// This type implements https://abseil.io/docs/cpp/guides/hash.
|
||||
class Sos2Constraint {
|
||||
public:
|
||||
// The typed integer used for ids.
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace math_opt {
|
||||
|
||||
// A value type that references a linear constraint from ModelStorage. Usually
|
||||
// this type is passed by copy.
|
||||
//
|
||||
// This type implements https://abseil.io/docs/cpp/guides/hash.
|
||||
class LinearConstraint {
|
||||
public:
|
||||
// The typed integer used for ids.
|
||||
|
||||
@@ -384,7 +384,7 @@ Matcher<std::optional<Basis>> BasisIs(const std::optional<Basis>& expected) {
|
||||
}
|
||||
|
||||
testing::Matcher<std::vector<Solution>> IsNear(
|
||||
const std::vector<Solution>& expected_solutions,
|
||||
absl::Span<const Solution> expected_solutions,
|
||||
const SolutionMatcherOptions options) {
|
||||
if (expected_solutions.empty()) {
|
||||
return IsEmpty();
|
||||
|
||||
@@ -38,6 +38,8 @@ constexpr absl::string_view kDeletedObjectiveDefaultDescription =
|
||||
|
||||
// A value type that references an objective (either primary or auxiliary) from
|
||||
// ModelStorage. Usually this type is passed by copy.
|
||||
//
|
||||
// This type implements https://abseil.io/docs/cpp/guides/hash.
|
||||
class Objective {
|
||||
public:
|
||||
// The type used for ids.
|
||||
|
||||
@@ -118,6 +118,9 @@ class LinearExpression;
|
||||
|
||||
// A value type that references a variable from ModelStorage. Usually this type
|
||||
// is passed by copy.
|
||||
//
|
||||
// This type implements https://abseil.io/docs/cpp/guides/hash (see
|
||||
// VariablesEquality for details about how operator== works).
|
||||
class Variable {
|
||||
public:
|
||||
// The typed integer used for ids.
|
||||
@@ -642,6 +645,8 @@ using QuadraticProductId = std::pair<VariableId, VariableId>;
|
||||
// Invariant:
|
||||
// * variable_ids_.first <= variable_ids_.second. The constructor will
|
||||
// silently correct this if not satisfied by the inputs.
|
||||
//
|
||||
// This type can be used as a key in ABSL hash containers.
|
||||
class QuadraticTermKey {
|
||||
public:
|
||||
// NOTE: this definition is for use by IdMap; clients should not rely upon it.
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
set(NAME ${PROJECT_NAME}_math_opt_io)
|
||||
add_library(${NAME} OBJECT)
|
||||
|
||||
file(GLOB _SRCS "*.h" "*.cc")
|
||||
file(GLOB_RECURSE _SRCS "*.h" "*.cc")
|
||||
list(FILTER _SRCS EXCLUDE REGEX ".*/.*_test.cc")
|
||||
list(FILTER _SRCS EXCLUDE REGEX "/python/.*")
|
||||
list(FILTER _SRCS EXCLUDE REGEX "/lp/matchers\..*")
|
||||
if(NOT USE_SCIP)
|
||||
list(FILTER _SRCS EXCLUDE REGEX "/lp_parser.h$")
|
||||
list(FILTER _SRCS EXCLUDE REGEX "/lp_parser.cc$")
|
||||
|
||||
@@ -28,17 +28,6 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "matchers",
|
||||
testonly = 1,
|
||||
srcs = ["matchers.cc"],
|
||||
hdrs = ["matchers.h"],
|
||||
deps = [
|
||||
":lp_model",
|
||||
"//ortools/base:gmock",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "lp_name",
|
||||
srcs = ["lp_name.cc"],
|
||||
@@ -50,3 +39,17 @@ cc_library(
|
||||
"@com_google_absl//absl/strings:string_view",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "model_utils",
|
||||
srcs = ["model_utils.cc"],
|
||||
hdrs = ["model_utils.h"],
|
||||
deps = [
|
||||
":lp_model",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/base:strong_vector",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
// 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/io/lp/lp_model.h"
|
||||
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/math_opt/io/lp/matchers.h"
|
||||
|
||||
namespace operations_research::lp_format {
|
||||
namespace {
|
||||
|
||||
using ::testing::AllOf;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::Pair;
|
||||
using ::testing::StrEq;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
using ::testing::status::IsOkAndHolds;
|
||||
using ::testing::status::StatusIs;
|
||||
|
||||
constexpr double kInf = std::numeric_limits<double>::infinity();
|
||||
|
||||
TEST(LpModelTest, EmptyModel) {
|
||||
LpModel model;
|
||||
EXPECT_THAT(model.variables(), IsEmpty());
|
||||
EXPECT_THAT(model.variable_names(), IsEmpty());
|
||||
EXPECT_THAT(model.constraints(), IsEmpty());
|
||||
}
|
||||
|
||||
TEST(LpModelTest, AddVariableSuccess) {
|
||||
LpModel model;
|
||||
EXPECT_THAT(model.AddVariable("x"), IsOkAndHolds(VariableIndex{0}));
|
||||
EXPECT_THAT(model.AddVariable("y78"), IsOkAndHolds(VariableIndex{1}));
|
||||
EXPECT_THAT(model.variables(), ElementsAre("x", "y78"));
|
||||
EXPECT_THAT(model.variable_names(),
|
||||
UnorderedElementsAre(Pair("x", VariableIndex{0}),
|
||||
Pair("y78", VariableIndex{1})));
|
||||
}
|
||||
|
||||
TEST(LpModelTest, AddVariableRepeatNameError) {
|
||||
LpModel model;
|
||||
EXPECT_OK(model.AddVariable("xyz"));
|
||||
EXPECT_THAT(model.AddVariable("xyz"),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("duplicate"), HasSubstr("xyz"))));
|
||||
}
|
||||
|
||||
TEST(LpModelTest, AddVariableInvalidNameError) {
|
||||
LpModel model;
|
||||
EXPECT_THAT(model.AddVariable("4x"),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("invalid variable"), HasSubstr("4x"))));
|
||||
}
|
||||
|
||||
TEST(LpModelTest, AddConstraintSuccess) {
|
||||
LpModel model;
|
||||
ASSERT_OK_AND_ASSIGN(const VariableIndex x, model.AddVariable("x"));
|
||||
const Constraint c = {.terms = {{4.0, x}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = 2.5,
|
||||
.name = "c"};
|
||||
EXPECT_THAT(model.AddConstraint(c), IsOkAndHolds(ConstraintIndex{0}));
|
||||
EXPECT_THAT(model.constraints(), ElementsAre(ConstraintEquals(c)));
|
||||
}
|
||||
|
||||
TEST(LpModelTest, AddConstraintNoTermsError) {
|
||||
LpModel model;
|
||||
const Constraint c = {.relation = Relation::kEqual, .rhs = 2.5, .name = "c"};
|
||||
EXPECT_THAT(model.AddConstraint(c),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("term")));
|
||||
}
|
||||
|
||||
TEST(LpModelTest, AddConstraintBadRelationError) {
|
||||
LpModel model;
|
||||
ASSERT_OK_AND_ASSIGN(const VariableIndex x, model.AddVariable("x"));
|
||||
const Constraint c = {.terms = {{4.0, x}},
|
||||
.relation = static_cast<Relation>(-12),
|
||||
.rhs = 2.5,
|
||||
.name = "c"};
|
||||
EXPECT_THAT(
|
||||
model.AddConstraint(c),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("Relation")));
|
||||
}
|
||||
|
||||
TEST(LpModelTest, AddConstraintNanInTermsError) {
|
||||
LpModel model;
|
||||
ASSERT_OK_AND_ASSIGN(const VariableIndex x, model.AddVariable("x"));
|
||||
const Constraint c = {
|
||||
.terms = {{std::numeric_limits<double>::quiet_NaN(), x}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = 2.5,
|
||||
.name = "c"};
|
||||
EXPECT_THAT(model.AddConstraint(c),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("NaN")));
|
||||
}
|
||||
|
||||
TEST(LpModelTest, AddConstraintInfInTermsError) {
|
||||
LpModel model;
|
||||
ASSERT_OK_AND_ASSIGN(const VariableIndex x, model.AddVariable("x"));
|
||||
const Constraint c = {.terms = {{kInf, x}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = 2.5,
|
||||
.name = "c"};
|
||||
EXPECT_THAT(
|
||||
model.AddConstraint(c),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("finite")));
|
||||
}
|
||||
|
||||
TEST(LpModelTest, AddConstraintNanInRhsError) {
|
||||
LpModel model;
|
||||
ASSERT_OK_AND_ASSIGN(const VariableIndex x, model.AddVariable("x"));
|
||||
const Constraint c = {.terms = {{2.0, x}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = std::numeric_limits<double>::quiet_NaN(),
|
||||
.name = "c"};
|
||||
EXPECT_THAT(model.AddConstraint(c),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("NaN")));
|
||||
}
|
||||
|
||||
TEST(LpModelTest, AddConstraintVariableNotInModelError) {
|
||||
LpModel model;
|
||||
const VariableIndex x(0);
|
||||
const Constraint c = {.terms = {{2.0, x}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = 4.0,
|
||||
.name = "c"};
|
||||
EXPECT_THAT(
|
||||
model.AddConstraint(c),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("variable ids")));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string ToString(const T& t) {
|
||||
std::stringstream ss;
|
||||
ss << t;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
TEST(RelationStream, PrintsString) {
|
||||
EXPECT_EQ(ToString(Relation::kEqual), "=");
|
||||
EXPECT_EQ(ToString(Relation::kGreaterOrEqual), ">=");
|
||||
EXPECT_EQ(ToString(Relation::kLessOrEqual), "<=");
|
||||
EXPECT_THAT(ToString(static_cast<Relation>(-1)), HasSubstr("invalid"));
|
||||
}
|
||||
|
||||
TEST(ConstraintStream, PrintsString) {
|
||||
const Constraint c = {
|
||||
.terms = {{1.0, VariableIndex(0)}, {4.5, VariableIndex(3)}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = 5.0,
|
||||
.name = "cat"};
|
||||
// StrEq gives better output than EXPECT_EQ on failure
|
||||
EXPECT_THAT(
|
||||
ToString(c),
|
||||
StrEq("terms: {{1, 0}, {4.5, 3}} relation: = rhs: 5 name: \"cat\""));
|
||||
}
|
||||
|
||||
// These tests are intentionally not exhaustive. Better to test the stream
|
||||
// operator by round tripping with parsing. We do not have a strong contract
|
||||
// on the string produced, and testing against a large string is brittle.
|
||||
TEST(LpModelTest, BasicStreamTest) {
|
||||
LpModel model;
|
||||
ASSERT_OK_AND_ASSIGN(const VariableIndex x, model.AddVariable("x"));
|
||||
const Constraint c = {.terms = {{4.0, x}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = 2.5,
|
||||
.name = "c"};
|
||||
ASSERT_OK(model.AddConstraint(c));
|
||||
// StrEq gives better output than EXPECT_EQ on failure
|
||||
EXPECT_THAT(ToString(model), StrEq("SUBJECT TO\n c: 4 x = 2.5\nEND\n"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::lp_format
|
||||
@@ -1,67 +0,0 @@
|
||||
// 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/io/lp/lp_name.h"
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
|
||||
namespace operations_research::lp_format {
|
||||
namespace {
|
||||
|
||||
using ::testing::AllOf;
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::status::StatusIs;
|
||||
|
||||
TEST(ValidateCharInNameTest, BasicUse) {
|
||||
for (bool is_leading : {false, true}) {
|
||||
SCOPED_TRACE(absl::StrCat("is leading: ", is_leading));
|
||||
for (unsigned char c : {'a', 'A', 'b', 'B', 'z', 'Z', '_', '{', '}'}) {
|
||||
SCOPED_TRACE(absl::StrCat("testing character: ", c));
|
||||
EXPECT_TRUE(ValidateCharInName(c, is_leading));
|
||||
}
|
||||
for (unsigned char c : {'+', '-', '*', '/', ':', '\0'}) {
|
||||
SCOPED_TRACE(absl::StrCat("testing character: ", c));
|
||||
EXPECT_FALSE(ValidateCharInName(c, is_leading));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ValidateCharInNameTest, LeadingChars) {
|
||||
for (bool is_leading : {false, true}) {
|
||||
SCOPED_TRACE(absl::StrCat("is leading: ", is_leading));
|
||||
for (unsigned char c : {'.', '0', '1', '9'}) {
|
||||
SCOPED_TRACE(absl::StrCat("testing character: ", c));
|
||||
const bool should_be_allowed = !is_leading;
|
||||
EXPECT_EQ(ValidateCharInName(c, is_leading), should_be_allowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ValidateNameTest, BasicUse) {
|
||||
EXPECT_OK(ValidateName("x8"));
|
||||
EXPECT_OK(ValidateName("A_b_C"));
|
||||
EXPECT_THAT(ValidateName(""),
|
||||
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("empty")));
|
||||
EXPECT_THAT(ValidateName("8x"), StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("index: 0"),
|
||||
HasSubstr("character: 8"))));
|
||||
EXPECT_THAT(ValidateName("x-8"), StatusIs(absl::StatusCode::kInvalidArgument,
|
||||
AllOf(HasSubstr("index: 1"),
|
||||
HasSubstr("character: -"))));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::lp_format
|
||||
@@ -1,50 +0,0 @@
|
||||
// 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/io/lp/matchers.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/math_opt/io/lp/lp_model.h"
|
||||
|
||||
namespace operations_research::lp_format {
|
||||
namespace {
|
||||
using ::testing::AllOf;
|
||||
using ::testing::ElementsAreArray;
|
||||
using ::testing::Field;
|
||||
using ::testing::Matcher;
|
||||
using ::testing::Property;
|
||||
} // namespace
|
||||
|
||||
Matcher<Constraint> ConstraintEquals(const Constraint& expected) {
|
||||
return AllOf(
|
||||
Field("terms", &Constraint::terms, ElementsAreArray(expected.terms)),
|
||||
Field("relation", &Constraint::relation, expected.relation),
|
||||
Field("rhs", &Constraint::rhs, expected.rhs),
|
||||
Field("name", &Constraint::name, expected.name));
|
||||
}
|
||||
|
||||
Matcher<LpModel> ModelEquals(const LpModel& expected) {
|
||||
std::vector<Matcher<Constraint>> expected_constraints;
|
||||
for (const Constraint& constraint : expected.constraints()) {
|
||||
expected_constraints.push_back(ConstraintEquals(constraint));
|
||||
}
|
||||
return AllOf(Property("variables", &LpModel::variables,
|
||||
ElementsAreArray(expected.variables())),
|
||||
Property("constraints", &LpModel::constraints,
|
||||
ElementsAreArray(expected_constraints)));
|
||||
}
|
||||
|
||||
} // namespace operations_research::lp_format
|
||||
@@ -1,28 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef OR_TOOLS_MATH_OPT_IO_LP_MATCHERS_H_
|
||||
#define OR_TOOLS_MATH_OPT_IO_LP_MATCHERS_H_
|
||||
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/math_opt/io/lp/lp_model.h"
|
||||
|
||||
namespace operations_research::lp_format {
|
||||
|
||||
testing::Matcher<Constraint> ConstraintEquals(const Constraint& expected);
|
||||
|
||||
testing::Matcher<LpModel> ModelEquals(const LpModel& expected);
|
||||
|
||||
} // namespace operations_research::lp_format
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_IO_LP_MATCHERS_H_
|
||||
@@ -1,123 +0,0 @@
|
||||
// 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/io/lp/matchers.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/math_opt/io/lp/lp_model.h"
|
||||
|
||||
namespace operations_research::lp_format {
|
||||
namespace {
|
||||
|
||||
TEST(ConstraintEqualsTest, Equal) {
|
||||
const Constraint c = {
|
||||
.terms = {{1.0, VariableIndex(0)}, {4.0, VariableIndex(3)}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = 5.0,
|
||||
.name = "cat"};
|
||||
EXPECT_THAT(c, ConstraintEquals(c));
|
||||
}
|
||||
|
||||
TEST(ConstraintEqualsTest, WrongNameNoMatch) {
|
||||
const Constraint c = {
|
||||
.terms = {{1.0, VariableIndex(0)}, {4.0, VariableIndex(3)}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = 5.0,
|
||||
.name = "cat"};
|
||||
Constraint d = c;
|
||||
d.name = "dog";
|
||||
EXPECT_THAT(c, Not(ConstraintEquals(d)));
|
||||
}
|
||||
|
||||
TEST(ConstraintEqualsTest, WrongRhsNoMatch) {
|
||||
const Constraint c = {
|
||||
.terms = {{1.0, VariableIndex(0)}, {4.0, VariableIndex(3)}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = 5.0,
|
||||
.name = "cat"};
|
||||
Constraint d = c;
|
||||
d.rhs = 4.0;
|
||||
EXPECT_THAT(c, Not(ConstraintEquals(d)));
|
||||
}
|
||||
|
||||
TEST(ConstraintEqualsTest, WrongRelationNoMatch) {
|
||||
const Constraint c = {
|
||||
.terms = {{1.0, VariableIndex(0)}, {4.0, VariableIndex(3)}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = 5.0,
|
||||
.name = "cat"};
|
||||
Constraint d = c;
|
||||
d.relation = Relation::kGreaterOrEqual;
|
||||
EXPECT_THAT(c, Not(ConstraintEquals(d)));
|
||||
}
|
||||
|
||||
TEST(ConstraintEqualsTest, WrongTermsNoMatch) {
|
||||
const Constraint c = {
|
||||
.terms = {{1.0, VariableIndex(0)}, {4.0, VariableIndex(3)}},
|
||||
.relation = Relation::kEqual,
|
||||
.rhs = 5.0,
|
||||
.name = "cat"};
|
||||
Constraint d = c;
|
||||
d.terms.clear();
|
||||
EXPECT_THAT(c, Not(ConstraintEquals(d)));
|
||||
}
|
||||
|
||||
TEST(ModelEqualsTest, ModelEqualsSelf) {
|
||||
LpModel model;
|
||||
ASSERT_OK_AND_ASSIGN(const VariableIndex x, model.AddVariable("x"));
|
||||
ASSERT_OK(model.AddConstraint({.terms = {{2.0, x}},
|
||||
.relation = Relation::kLessOrEqual,
|
||||
.rhs = 4.0,
|
||||
.name = "c"}));
|
||||
EXPECT_THAT(model, ModelEquals(model));
|
||||
}
|
||||
|
||||
TEST(ModelEqualsTest, EmptyModelsEqual) {
|
||||
LpModel actual;
|
||||
LpModel expected;
|
||||
EXPECT_THAT(actual, ModelEquals(expected));
|
||||
}
|
||||
|
||||
TEST(ModelEqualsTest, DifferentVariablesNotEqual) {
|
||||
LpModel actual;
|
||||
ASSERT_OK(actual.AddVariable("x"));
|
||||
|
||||
LpModel expected;
|
||||
ASSERT_OK(expected.AddVariable("y"));
|
||||
|
||||
EXPECT_THAT(actual, Not(ModelEquals(expected)));
|
||||
}
|
||||
|
||||
TEST(ModelEqualsTest, DifferentConstraintsNotEqual) {
|
||||
LpModel actual;
|
||||
ASSERT_OK_AND_ASSIGN(const VariableIndex x_actual, actual.AddVariable("x"));
|
||||
ASSERT_OK(actual.AddConstraint({.terms = {{2.0, x_actual}},
|
||||
.relation = Relation::kLessOrEqual,
|
||||
.rhs = 4.0,
|
||||
.name = "c"}));
|
||||
|
||||
LpModel expected;
|
||||
ASSERT_OK_AND_ASSIGN(const VariableIndex x_expected,
|
||||
expected.AddVariable("x"));
|
||||
// RHS is different
|
||||
ASSERT_OK(actual.AddConstraint({.terms = {{2.0, x_expected}},
|
||||
.relation = Relation::kLessOrEqual,
|
||||
.rhs = 5.0,
|
||||
.name = "c"}));
|
||||
|
||||
EXPECT_THAT(actual, Not(ModelEquals(expected)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research::lp_format
|
||||
127
ortools/math_opt/io/lp/model_utils.cc
Normal file
127
ortools/math_opt/io/lp/model_utils.cc
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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/io/lp/model_utils.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "ortools/base/status_builder.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
#include "ortools/base/strong_vector.h"
|
||||
#include "ortools/math_opt/io/lp/lp_model.h"
|
||||
|
||||
namespace operations_research::lp_format {
|
||||
|
||||
namespace {
|
||||
|
||||
absl::StatusOr<LpModel> ReorderVariables(
|
||||
const LpModel& model,
|
||||
const util_intops::StrongVector<VariableIndex, VariableIndex>& new_to_old,
|
||||
const bool allow_skip_old) {
|
||||
constexpr VariableIndex kBad(-1);
|
||||
util_intops::StrongVector<VariableIndex, VariableIndex> old_to_new(
|
||||
model.variables().end_index(), kBad);
|
||||
for (const VariableIndex v_new : new_to_old.index_range()) {
|
||||
const VariableIndex v_old = new_to_old[v_new];
|
||||
if (v_old < VariableIndex(0) || v_old >= model.variables().end_index()) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "values of new_to_old be in [0," << model.variables().size()
|
||||
<< "), found: " << v_old;
|
||||
}
|
||||
if (old_to_new[v_old] != kBad) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "found value: " << v_old << " twice in new_to_old";
|
||||
}
|
||||
old_to_new[v_old] = v_new;
|
||||
}
|
||||
if (!allow_skip_old) {
|
||||
for (const VariableIndex v_old : old_to_new.index_range()) {
|
||||
if (old_to_new[v_old] == kBad) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "no new VariableIndex for old VariableIndex: " << v_old;
|
||||
}
|
||||
}
|
||||
}
|
||||
LpModel result;
|
||||
for (const VariableIndex new_var : new_to_old.index_range()) {
|
||||
RETURN_IF_ERROR(
|
||||
result.AddVariable(model.variables()[new_to_old[new_var]]).status())
|
||||
<< "should be unreachable";
|
||||
}
|
||||
// We build the constraints in the new model by iterating over the constraints
|
||||
// in the old model, copying each constraint and then modifying it in place.
|
||||
for (Constraint c : model.constraints()) {
|
||||
for (auto& [unused, var] : c.terms) {
|
||||
if (old_to_new[var] == kBad) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "variable " << var
|
||||
<< " appears in a constraint but is not new_to_old";
|
||||
}
|
||||
var = old_to_new[var];
|
||||
}
|
||||
RETURN_IF_ERROR(result.AddConstraint(c).status())
|
||||
<< "should be unreachable";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LpModel RemoveUnusedVariables(const LpModel& model) {
|
||||
util_intops::StrongVector<VariableIndex, bool> old_vars_used(
|
||||
model.variables().end_index());
|
||||
for (const Constraint& c : model.constraints()) {
|
||||
for (const auto& [unused, var] : c.terms) {
|
||||
old_vars_used[var] = true;
|
||||
}
|
||||
}
|
||||
util_intops::StrongVector<VariableIndex, VariableIndex> new_to_old;
|
||||
for (VariableIndex v_old : old_vars_used.index_range()) {
|
||||
if (old_vars_used[v_old]) {
|
||||
new_to_old.push_back(v_old);
|
||||
}
|
||||
}
|
||||
auto result = ReorderVariables(model, new_to_old, /*allow_skip_old=*/true);
|
||||
CHECK_OK(result);
|
||||
return *std::move(result);
|
||||
}
|
||||
|
||||
absl::StatusOr<LpModel> PermuteVariables(
|
||||
const LpModel& model,
|
||||
const util_intops::StrongVector<VariableIndex, VariableIndex>&
|
||||
new_index_to_old_index) {
|
||||
return ReorderVariables(model, new_index_to_old_index,
|
||||
/*allow_skip_old=*/false);
|
||||
}
|
||||
|
||||
absl::StatusOr<LpModel> PermuteVariables(
|
||||
const LpModel& model,
|
||||
const util_intops::StrongVector<VariableIndex, std::string>&
|
||||
order_by_name) {
|
||||
util_intops::StrongVector<VariableIndex, VariableIndex> new_to_old;
|
||||
for (const std::string& name : order_by_name) {
|
||||
auto it = model.variable_names().find(name);
|
||||
if (it == model.variable_names().end()) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "no variable with name: " << name << " in model";
|
||||
}
|
||||
new_to_old.push_back(it->second);
|
||||
}
|
||||
return PermuteVariables(model, new_to_old);
|
||||
}
|
||||
|
||||
} // namespace operations_research::lp_format
|
||||
59
ortools/math_opt/io/lp/model_utils.h
Normal file
59
ortools/math_opt/io/lp/model_utils.h
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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.
|
||||
|
||||
#ifndef OR_TOOLS_MATH_OPT_IO_LP_MODEL_UTILS_H_
|
||||
#define OR_TOOLS_MATH_OPT_IO_LP_MODEL_UTILS_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "absl/status/statusor.h"
|
||||
#include "ortools/base/strong_vector.h"
|
||||
#include "ortools/math_opt/io/lp/lp_model.h"
|
||||
|
||||
namespace operations_research::lp_format {
|
||||
|
||||
// Returns a copy of `model`, but where the variables appearing in no constraint
|
||||
// have been deleted (and variable order is otherwise preserved).
|
||||
//
|
||||
// Note that because the variables are re-indexed, the constraints will have
|
||||
// different values in `terms`.
|
||||
LpModel RemoveUnusedVariables(const LpModel& model);
|
||||
|
||||
// Returns a copy of `model` where the variables are permuted by
|
||||
// `new_index_to_old_index` (a permutation of the indices of the variables).
|
||||
//
|
||||
// Returns an error if `new_index_to_old_index` is not a valid permutation.
|
||||
//
|
||||
// Note that because the variables are re-indexed, the constraints will have
|
||||
// different values in `terms`.
|
||||
absl::StatusOr<LpModel> PermuteVariables(
|
||||
const LpModel& model,
|
||||
const util_intops::StrongVector<VariableIndex, VariableIndex>&
|
||||
new_index_to_old_index);
|
||||
|
||||
// Returns a copy of `model` where the variables are reordered by
|
||||
// `order_by_name`, where `order_by_name` contains the name of each variable
|
||||
// exactly one time, giving the new ordering.
|
||||
//
|
||||
// Returns an error if `order_by_name` does not contain the name each variable
|
||||
// in the model exactly once.
|
||||
//
|
||||
// Note that because the variables are re-indexed, the constraints will have
|
||||
// different values in `terms`.
|
||||
absl::StatusOr<LpModel> PermuteVariables(
|
||||
const LpModel& model,
|
||||
const util_intops::StrongVector<VariableIndex, std::string>& order_by_name);
|
||||
|
||||
} // namespace operations_research::lp_format
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_IO_LP_MODEL_UTILS_H_
|
||||
@@ -198,11 +198,10 @@ RobustConstraintDualizer::RobustConstraintDualizer(
|
||||
|
||||
} // namespace
|
||||
|
||||
void AddRobustConstraint(
|
||||
const Model& uncertainty_model, const Variable rhs,
|
||||
const std::vector<std::pair<LinearExpression, Variable>>&
|
||||
uncertain_coefficients,
|
||||
Model& main_model) {
|
||||
void AddRobustConstraint(const Model& uncertainty_model, const Variable rhs,
|
||||
absl::Span<const std::pair<LinearExpression, Variable>>
|
||||
uncertain_coefficients,
|
||||
Model& main_model) {
|
||||
RobustConstraintDualizer dualizer(uncertainty_model, rhs,
|
||||
uncertain_coefficients, main_model);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/math_opt/cpp/math_opt.h"
|
||||
|
||||
namespace operations_research {
|
||||
@@ -42,11 +43,10 @@ namespace math_opt {
|
||||
// * uncertain_coefficients: pairs [a(w)_i, x_i] for all i
|
||||
// Input-output argument:
|
||||
// * main_model
|
||||
void AddRobustConstraint(
|
||||
const Model& uncertainty_model, Variable rhs,
|
||||
const std::vector<std::pair<LinearExpression, Variable>>&
|
||||
uncertain_coefficients,
|
||||
Model& main_model);
|
||||
void AddRobustConstraint(const Model& uncertainty_model, Variable rhs,
|
||||
absl::Span<const std::pair<LinearExpression, Variable>>
|
||||
uncertain_coefficients,
|
||||
Model& main_model);
|
||||
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
|
||||
@@ -1279,6 +1279,12 @@ class LinearConstraint:
|
||||
),
|
||||
)
|
||||
|
||||
def as_bounded_linear_expression(self) -> BoundedLinearExpression:
|
||||
"""Returns the bounded expression from lower_bound, upper_bound and terms."""
|
||||
return BoundedLinearExpression(
|
||||
self.lower_bound, LinearSum(self.terms()), self.upper_bound
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
"""Returns the name, or a string containing the id if the name is empty."""
|
||||
return self.name if self.name else f"linear_constraint_{self.id}"
|
||||
|
||||
@@ -160,6 +160,20 @@ class ModelTest(compare_proto.MathOptProtoAssertions, parameterized.TestCase):
|
||||
self.assertEqual(c, mod.get_linear_constraint(0))
|
||||
self.assertEqual(d, mod.get_linear_constraint(1))
|
||||
|
||||
def test_linear_constraint_as_bounded_expression(
|
||||
self, storage_class: StorageClass
|
||||
) -> None:
|
||||
mod = model.Model(name="test_model", storage_class=storage_class)
|
||||
x = mod.add_binary_variable(name="x")
|
||||
y = mod.add_binary_variable(name="y")
|
||||
c = mod.add_linear_constraint(lb=-1.0, ub=2.5, name="c", expr=3 * x - 2 * y)
|
||||
bounded_expr = c.as_bounded_linear_expression()
|
||||
self.assertEqual(bounded_expr.lower_bound, -1.0)
|
||||
self.assertEqual(bounded_expr.upper_bound, 2.5)
|
||||
expr = model.as_flat_linear_expression(bounded_expr.expression)
|
||||
self.assertEqual(expr.offset, 0.0)
|
||||
self.assertDictEqual(dict(expr.terms), {x: 3.0, y: -2.0})
|
||||
|
||||
def test_get_linear_constraint_does_not_exist_key_error(
|
||||
self, storage_class: StorageClass
|
||||
) -> None:
|
||||
|
||||
@@ -1333,7 +1333,7 @@ absl::StatusOr<double> GurobiSolver::GetBestPrimalBound(
|
||||
}
|
||||
|
||||
absl::StatusOr<double> GurobiSolver::GetBestDualBound(
|
||||
const std::vector<SolutionProto>& solutions) const {
|
||||
absl::Span<const SolutionProto> solutions) const {
|
||||
ASSIGN_OR_RETURN(const bool is_maximize, IsMaximize());
|
||||
// GetGurobiBestDualBound() returns the correct bound for problems without
|
||||
// dual solutions (e.g. MIP).
|
||||
|
||||
@@ -201,7 +201,7 @@ class GurobiSolver : public SolverInterface {
|
||||
|
||||
absl::StatusOr<double> GetGurobiBestDualBound() const;
|
||||
absl::StatusOr<double> GetBestDualBound(
|
||||
const std::vector<SolutionProto>& solutions) const;
|
||||
absl::Span<const SolutionProto> solutions) const;
|
||||
absl::StatusOr<double> GetBestPrimalBound(
|
||||
absl::Span<const SolutionProto> solutions) const;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user