math_opt: update from google3

This commit is contained in:
Corentin Le Molgat
2024-08-05 14:05:39 +02:00
parent 37cd7fb297
commit 022ffe8a79
24 changed files with 255 additions and 484 deletions

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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();

View File

@@ -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.

View File

@@ -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.

View File

@@ -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$")

View File

@@ -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",
],
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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_

View File

@@ -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

View 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

View 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_

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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}"

View File

@@ -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:

View File

@@ -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).

View File

@@ -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;