math_opt: sync with google3

This commit is contained in:
Corentin Le Molgat
2022-05-18 16:37:37 +02:00
committed by Mizux Seiha
parent 87b718d298
commit 4efe5d84ba
35 changed files with 1850 additions and 918 deletions

View File

@@ -39,7 +39,12 @@ cc_library(
"//ortools/base",
"//ortools/base:linked_hash_map",
"//ortools/base:map_util",
"//ortools/base:status_builder",
"//ortools/base:status_macros",
"//ortools/math_opt:model_cc_proto",
"//ortools/math_opt:model_update_cc_proto",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
],
)

View File

@@ -151,7 +151,7 @@ absl::StatusOr<std::unique_ptr<ModelStorage>> ModelStorage::FromModelProto(
// models. Thus a model built by ModelStorage can contain duplicated
// names. And since we use FromModelProto() to implement Clone(), we must make
// sure duplicated names don't fail.
RETURN_IF_ERROR(ValidateModel(model_proto, /*check_names=*/false));
RETURN_IF_ERROR(ValidateModel(model_proto, /*check_names=*/false).status());
auto storage = std::make_unique<ModelStorage>(model_proto.name());
@@ -810,21 +810,23 @@ absl::Status ModelStorage::ApplyUpdateProto(
const ModelUpdateProto& update_proto) {
// Check the update first.
{
ModelSummary summary;
// We have to use sorted keys since IdNameBiMap expect Insert() to be called
// in sorted order.
// Do not check for duplicate names, as with FromModelProto();
ModelSummary summary(/*check_names=*/false);
// IdNameBiMap requires Insert() calls to be in sorted id order.
for (const auto id : SortedVariables()) {
summary.variables.Insert(id.value(), variable_name(id));
RETURN_IF_ERROR(summary.variables.Insert(id.value(), variable_name(id)))
<< "invalid variable id in model";
}
summary.variables.SetNextFreeId(next_variable_id_.value());
RETURN_IF_ERROR(summary.variables.SetNextFreeId(next_variable_id_.value()));
for (const auto id : SortedLinearConstraints()) {
summary.linear_constraints.Insert(id.value(), linear_constraint_name(id));
RETURN_IF_ERROR(summary.linear_constraints.Insert(
id.value(), linear_constraint_name(id)))
<< "invalid linear constraint id in model";
}
summary.linear_constraints.SetNextFreeId(
next_linear_constraint_id_.value());
// We don't check the names for the same reason as in FromModelProto().
RETURN_IF_ERROR(ValidateModelUpdateAndSummary(update_proto, summary,
/*check_names=*/false));
RETURN_IF_ERROR(summary.linear_constraints.SetNextFreeId(
next_linear_constraint_id_.value()));
RETURN_IF_ERROR(ValidateModelUpdate(update_proto, summary))
<< "update not valid";
}
// Remove deleted variables and constraints.

View File

@@ -22,13 +22,117 @@
namespace operations_research {
namespace math_opt {
namespace {
// TODO(b/232526223): this is an exact copy of
// CheckIdsRangeAndStrictlyIncreasing from ids_validator.h, find a way to share
// the code.
absl::Status CheckIdsRangeAndStrictlyIncreasing2(
absl::Span<const int64_t> ids) {
int64_t previous{-1};
for (int i = 0; i < ids.size(); previous = ids[i], ++i) {
if (ids[i] < 0 || ids[i] == std::numeric_limits<int64_t>::max()) {
return util::InvalidArgumentErrorBuilder()
<< "Expected ids to be nonnegative and not max(int64_t) but at "
"index "
<< i << " found id: " << ids[i];
}
if (ids[i] <= previous) {
return util::InvalidArgumentErrorBuilder()
<< "Expected ids to be strictly increasing, but at index " << i
<< " found id: " << ids[i] << " and at previous index " << i - 1
<< " found id: " << ids[i - 1];
}
}
return absl::OkStatus();
}
} // namespace
IdNameBiMap::IdNameBiMap(
std::initializer_list<std::pair<int64_t, absl::string_view>> ids) {
std::initializer_list<std::pair<int64_t, absl::string_view>> ids)
: IdNameBiMap(/*check_names=*/true) {
for (const auto& pair : ids) {
Insert(pair.first, std::string(pair.second));
CHECK_OK(Insert(pair.first, std::string(pair.second)));
}
}
IdNameBiMap::IdNameBiMap(const IdNameBiMap& other) { *this = other; }
IdNameBiMap& IdNameBiMap::operator=(const IdNameBiMap& other) {
if (&other == this) {
return *this;
}
next_free_id_ = other.next_free_id_;
id_to_name_ = other.id_to_name_;
if (!other.nonempty_name_to_id_.has_value()) {
nonempty_name_to_id_ = std::nullopt;
} else {
nonempty_name_to_id_.emplace();
for (const auto& [id, name] : id_to_name_) {
if (!name.empty()) {
const auto [it, success] =
nonempty_name_to_id_->insert({absl::string_view(name), id});
CHECK(success); // CHECK is OK, other cannot have duplicate names and
// non nullopt nonempty_name_to_id_.
}
}
}
return *this;
}
absl::Status IdNameBiMap::BulkUpdate(
absl::Span<const int64_t> deleted_ids, absl::Span<const int64_t> new_ids,
const absl::Span<const std::string* const> names) {
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing2(deleted_ids))
<< "invalid deleted ids";
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing2(new_ids))
<< "invalid new ids";
if (!names.empty() && names.size() != new_ids.size()) {
return util::InvalidArgumentErrorBuilder()
<< "names had size " << names.size()
<< " but should either be empty of have size matching new_ids which "
"has size "
<< new_ids.size();
}
for (const int64_t id : deleted_ids) {
RETURN_IF_ERROR(Erase(id));
}
for (int i = 0; i < new_ids.size(); ++i) {
RETURN_IF_ERROR(
Insert(new_ids[i], names.empty() ? std::string{} : *names[i]));
}
return absl::OkStatus();
}
ModelSummary::ModelSummary(const bool check_names)
: variables(check_names), linear_constraints(check_names) {}
absl::StatusOr<ModelSummary> ModelSummary::Create(const ModelProto& model,
const bool check_names) {
ModelSummary summary(check_names);
RETURN_IF_ERROR(summary.variables.BulkUpdate({}, model.variables().ids(),
model.variables().names()))
<< "Model.variables are invalid";
RETURN_IF_ERROR(summary.linear_constraints.BulkUpdate(
{}, model.linear_constraints().ids(), model.linear_constraints().names()))
<< "Model.linear_constraints are invalid";
return summary;
}
absl::Status ModelSummary::Update(const ModelUpdateProto& model_update) {
RETURN_IF_ERROR(variables.BulkUpdate(model_update.deleted_variable_ids(),
model_update.new_variables().ids(),
model_update.new_variables().names()))
<< "invalid variables";
RETURN_IF_ERROR(linear_constraints.BulkUpdate(
model_update.deleted_linear_constraint_ids(),
model_update.new_linear_constraints().ids(),
model_update.new_linear_constraints().names()))
<< "invalid linear constraints";
return absl::OkStatus();
}
} // namespace math_opt
} // namespace operations_research

View File

@@ -21,10 +21,15 @@
#include <utility>
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "ortools/base/linked_hash_map.h"
#include "ortools/base/logging.h"
#include "ortools/base/map_util.h"
#include "ortools/base/status_builder.h"
#include "ortools/base/status_macros.h"
#include "ortools/math_opt/model.pb.h"
#include "ortools/math_opt/model_update.pb.h"
namespace operations_research {
namespace math_opt {
@@ -37,25 +42,46 @@ namespace math_opt {
// * Ids are non-negative.
// * Ids are not equal to std::numeric_limits<int64_t>::max()
// * Ids removed are never reused.
// * Names must be either empty or unique.
// * Names must be either empty or unique when built with check_names=true.
class IdNameBiMap {
public:
IdNameBiMap() = default;
// If check_names=false, the names need not be unique and the reverse mapping
// of name to id is not available.
explicit IdNameBiMap(bool check_names = true)
: nonempty_name_to_id_(
check_names ? std::make_optional<
absl::flat_hash_map<absl::string_view, int64_t>>()
: std::nullopt) {}
// Needs a custom copy constructor/assign because absl::string_view to
// internal data is held as a member. No custom move is needed.
IdNameBiMap(const IdNameBiMap& other);
IdNameBiMap& operator=(const IdNameBiMap& other);
IdNameBiMap(IdNameBiMap&& other) = default;
IdNameBiMap& operator=(IdNameBiMap&& other) = default;
// This constructor CHECKs that the input ids are sorted in increasing
// order. This constructor is expected to be used only for unit tests of
// validation code.
IdNameBiMap(std::initializer_list<std::pair<int64_t, absl::string_view>> ids);
// Inserts the provided id and associate the provided name to it. CHECKs that
// id >= next_free_id() and that when the name is nonempty it is not already
// present. As a side effect it updates next_free_id to id + 1.
inline void Insert(int64_t id, std::string name);
// Inserts the provided id and associate the provided name to it.
//
// An error is returned if:
// * id is negative
// * id is not at least next_free_id()
// * id is max(int64_t)
// * name is a duplicated and check_names was true at construction.
//
// As a side effect it updates next_free_id to id + 1.
inline absl::Status Insert(int64_t id, std::string name);
// Removes the given id. CHECKs that it is present.
inline void Erase(int64_t id);
// Removes the given id, or returns an error if id is not present.
inline absl::Status Erase(int64_t id);
inline bool HasId(int64_t id) const;
// Will always return false if name is empty or if check_names was false.
inline bool HasName(absl::string_view name) const;
inline bool Empty() const;
inline int Size() const;
@@ -64,23 +90,26 @@ class IdNameBiMap {
// non-negative).
inline int64_t next_free_id() const;
// Updates next_free_id(). CHECKs that the provided id is greater than any
// exiting id and non negative.
//
// In practice this should only be used to increase the next_free_id() value
// in cases where a ModelSummary is built with an existing model but we know
// some ids of removed elements have already been used.
inline void SetNextFreeId(int64_t new_next_free_id);
// Updates next_free_id(). Succeeds when the provided id is greater than every
// exiting id and is non-negative.
inline absl::Status SetNextFreeId(int64_t new_next_free_id);
// Iteration order is in increasing id order.
const gtl::linked_hash_map<int64_t, std::string>& id_to_name() const {
return id_to_name_;
}
const absl::flat_hash_map<absl::string_view, int64_t>& nonempty_name_to_id()
const {
// Is std::nullopt if check_names was false at construction.
const std::optional<absl::flat_hash_map<absl::string_view, int64_t>>&
nonempty_name_to_id() const {
return nonempty_name_to_id_;
}
// Warning: this may be mutated (partially updated) if an error is returned.
absl::Status BulkUpdate(absl::Span<const int64_t> deleted_ids,
absl::Span<const int64_t> new_ids,
absl::Span<const std::string* const> names);
private:
// Next unused id.
int64_t next_free_id_ = 0;
@@ -88,10 +117,18 @@ class IdNameBiMap {
// Pointer stability for name strings and iterating in insertion order are
// both needed (so we do not use flat_hash_map).
gtl::linked_hash_map<int64_t, std::string> id_to_name_;
absl::flat_hash_map<absl::string_view, int64_t> nonempty_name_to_id_;
std::optional<absl::flat_hash_map<absl::string_view, int64_t>>
nonempty_name_to_id_;
};
// TODO(b/232619901): In the guide for how to add new constraints, include how
// this class must updated.
struct ModelSummary {
explicit ModelSummary(bool check_names = true);
static absl::StatusOr<ModelSummary> Create(const ModelProto& model,
bool check_names = true);
absl::Status Update(const ModelUpdateProto& model_update);
IdNameBiMap variables;
IdNameBiMap linear_constraints;
};
@@ -100,35 +137,61 @@ struct ModelSummary {
// Inline function implementations
////////////////////////////////////////////////////////////////////////////////
void IdNameBiMap::Insert(const int64_t id, std::string name) {
CHECK_GE(id, next_free_id_);
CHECK_LT(id, std::numeric_limits<int64_t>::max());
absl::Status IdNameBiMap::Insert(const int64_t id, std::string name) {
if (id < next_free_id_) {
return util::InvalidArgumentErrorBuilder()
<< "expected id=" << id
<< " to be at least next_free_id_=" << next_free_id_
<< " (ids should be nonnegative and inserted in strictly increasing "
"order)";
}
if (id == std::numeric_limits<int64_t>::max()) {
return absl::InvalidArgumentError("id of max(int64_t) is not allowed");
}
next_free_id_ = id + 1;
const auto [it, success] = id_to_name_.emplace(id, std::move(name));
CHECK(success) << "id: " << id;
CHECK(success); // CHECK is okay, we have the invariant that next_free_id_ is
// larger than everything in the map.
const absl::string_view name_view(it->second);
if (!name_view.empty()) {
gtl::InsertOrDie(&nonempty_name_to_id_, name_view, id);
if (nonempty_name_to_id_.has_value() && !name_view.empty()) {
const auto [it, success] = nonempty_name_to_id_->insert({name_view, id});
if (!success) {
return util::InvalidArgumentErrorBuilder()
<< "duplicate name inserted: " << name_view;
}
}
return absl::OkStatus();
}
void IdNameBiMap::Erase(const int64_t id) {
absl::Status IdNameBiMap::Erase(const int64_t id) {
const auto it = id_to_name_.find(id);
CHECK(it != id_to_name_.end()) << id;
if (it == id_to_name_.end()) {
return util::InvalidArgumentErrorBuilder()
<< "cannot delete missing id " << id;
}
const absl::string_view name_view(it->second);
if (!name_view.empty()) {
CHECK_EQ(1, nonempty_name_to_id_.erase(name_view))
if (nonempty_name_to_id_.has_value() && !name_view.empty()) {
// CHECK OK, name_view being in nonempty_name_to_id_ when the above is met
// is a class invariant.
CHECK_EQ(nonempty_name_to_id_->erase(name_view), 1)
<< "name: " << name_view << " id: " << id;
}
id_to_name_.erase(it);
return absl::OkStatus();
}
bool IdNameBiMap::HasId(const int64_t id) const {
return id_to_name_.contains(id);
}
bool IdNameBiMap::HasName(const absl::string_view name) const {
CHECK(!name.empty());
return nonempty_name_to_id_.contains(name);
if (name.empty()) {
return false;
}
if (!nonempty_name_to_id_.has_value()) {
return false;
}
return nonempty_name_to_id_->contains(name);
}
bool IdNameBiMap::Empty() const { return id_to_name_.empty(); }
@@ -137,14 +200,23 @@ int IdNameBiMap::Size() const { return id_to_name_.size(); }
int64_t IdNameBiMap::next_free_id() const { return next_free_id_; }
void IdNameBiMap::SetNextFreeId(const int64_t new_next_free_id) {
absl::Status IdNameBiMap::SetNextFreeId(const int64_t new_next_free_id) {
if (!Empty()) {
const int64_t largest_id = id_to_name_.back().first;
CHECK_GT(new_next_free_id, largest_id);
if (new_next_free_id <= largest_id) {
return util::InvalidArgumentErrorBuilder()
<< "new_next_free_id=" << new_next_free_id
<< " must be greater than largest_id=" << largest_id;
}
} else {
CHECK_GE(new_next_free_id, 0);
if (new_next_free_id < 0) {
return util::InvalidArgumentErrorBuilder()
<< "new_next_free_id=" << new_next_free_id
<< " must be nonnegative";
}
}
next_free_id_ = new_next_free_id;
return absl::OkStatus();
}
} // namespace math_opt

View File

@@ -51,39 +51,6 @@ namespace math_opt {
namespace {
template <typename IdNameContainer>
void UpdateIdNameMap(const absl::Span<const int64_t> deleted_ids,
const IdNameContainer& container, IdNameBiMap& bimap) {
for (const int64_t deleted_id : deleted_ids) {
bimap.Erase(deleted_id);
}
for (int i = 0; i < container.ids_size(); ++i) {
std::string name;
if (!container.names().empty()) {
name = container.names(i);
}
bimap.Insert(container.ids(i), std::move(name));
}
}
ModelSummary MakeSummary(const ModelProto& model) {
ModelSummary summary;
UpdateIdNameMap<VariablesProto>({}, model.variables(), summary.variables);
UpdateIdNameMap<LinearConstraintsProto>({}, model.linear_constraints(),
summary.linear_constraints);
return summary;
}
void UpdateSummaryFromModelUpdate(const ModelUpdateProto& model_update,
ModelSummary& summary) {
UpdateIdNameMap<VariablesProto>(model_update.deleted_variable_ids(),
model_update.new_variables(),
summary.variables);
UpdateIdNameMap<LinearConstraintsProto>(
model_update.deleted_linear_constraint_ids(),
model_update.new_linear_constraints(), summary.linear_constraints);
}
// Returns an InternalError with the input status message if the input status is
// not OK.
absl::Status ToInternalError(const absl::Status original) {
@@ -188,12 +155,12 @@ absl::StatusOr<std::unique_ptr<Solver>> Solver::New(
const SolverTypeProto solver_type, const ModelProto& model,
const InitArgs& arguments) {
RETURN_IF_ERROR(internal::ValidateInitArgs(arguments, solver_type));
RETURN_IF_ERROR(ValidateModel(model));
ASSIGN_OR_RETURN(ModelSummary summary, ValidateModel(model));
ASSIGN_OR_RETURN(
auto underlying_solver,
AllSolversRegistry::Instance()->Create(solver_type, model, arguments));
auto result = absl::WrapUnique(
new Solver(std::move(underlying_solver), MakeSummary(model)));
new Solver(std::move(underlying_solver), std::move(summary)));
return result;
}
@@ -259,13 +226,18 @@ absl::StatusOr<bool> Solver::Update(const ModelUpdateProto& model_update) {
// We will reset it in code paths where no error occur.
fatal_failure_occurred_ = true;
// TODO(b/232264333): we are modifying model_summary_ but when CanUpdate
// returns false we are not in a good state. With a different design we can
// avoid this copy, which can be non-negligible.
ModelSummary backup = model_summary_;
RETURN_IF_ERROR(ValidateModelUpdate(model_update, model_summary_));
RETURN_IF_ERROR(ValidateModelUpdateAndSummary(model_update, model_summary_));
if (!underlying_solver_->CanUpdate(model_update)) {
model_summary_ = std::move(backup);
fatal_failure_occurred_ = false;
return false;
}
UpdateSummaryFromModelUpdate(model_update, model_summary_);
RETURN_IF_ERROR(underlying_solver_->Update(model_update));
fatal_failure_occurred_ = false;

View File

@@ -12,6 +12,42 @@ cc_library(
],
)
cc_library(
name = "basis_status",
srcs = ["basis_status.cc"],
hdrs = ["basis_status.h"],
deps = [
":enums",
"//ortools/math_opt:solution_cc_proto",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "sparse_containers",
srcs = ["sparse_containers.cc"],
hdrs = ["sparse_containers.h"],
deps = [
":basis_status",
":linear_constraint",
":variable_and_expressions",
"//ortools/base",
"//ortools/base:status_macros",
"//ortools/math_opt:solution_cc_proto",
"//ortools/math_opt:sparse_containers_cc_proto",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/core:sparse_vector_view",
"//ortools/math_opt/validators:ids_validator",
"//ortools/math_opt/validators:sparse_vector_validator",
"//ortools/util:status_macros",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "model",
srcs = ["model.cc"],
@@ -84,8 +120,10 @@ cc_library(
srcs = ["solution.cc"],
hdrs = ["solution.h"],
deps = [
":basis_status",
":enums",
":linear_constraint",
":sparse_containers",
":variable_and_expressions",
"//ortools/base",
"//ortools/base:intops",
@@ -112,11 +150,13 @@ cc_library(
"//ortools/base",
"//ortools/base:protoutil",
"//ortools/base:status_macros",
"//ortools/util:status_macros",
"//ortools/math_opt:result_cc_proto",
"//ortools/math_opt:solution_cc_proto",
"//ortools/math_opt/core:model_storage",
"//ortools/port:proto_utils",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
@@ -143,6 +183,7 @@ cc_library(
":enums",
":key_types",
":map_filter",
":sparse_containers",
":variable_and_expressions",
"//ortools/base",
"//ortools/base:intops",

View File

@@ -0,0 +1,50 @@
// Copyright 2010-2021 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/cpp/basis_status.h"
#include <optional>
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "ortools/math_opt/cpp/enums.h"
namespace operations_research::math_opt {
std::optional<absl::string_view> Enum<BasisStatus>::ToOptString(
BasisStatus value) {
switch (value) {
case BasisStatus::kFree:
return "free";
case BasisStatus::kAtLowerBound:
return "at_lower_bound";
case BasisStatus::kAtUpperBound:
return "at_upper_bound";
case BasisStatus::kFixedValue:
return "fixed_value";
case BasisStatus::kBasic:
return "basic";
}
return std::nullopt;
}
absl::Span<const BasisStatus> Enum<BasisStatus>::AllValues() {
static constexpr BasisStatus kBasisStatusValues[] = {
BasisStatus::kFree, BasisStatus::kAtLowerBound,
BasisStatus::kAtUpperBound, BasisStatus::kFixedValue,
BasisStatus::kBasic,
};
return absl::MakeConstSpan(kBasisStatusValues);
}
} // namespace operations_research::math_opt

View File

@@ -0,0 +1,46 @@
// Copyright 2010-2021 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_CPP_BASIS_STATUS_H_
#define OR_TOOLS_MATH_OPT_CPP_BASIS_STATUS_H_
#include <cstdint>
#include "ortools/math_opt/cpp/enums.h" // IWYU pragma: export
#include "ortools/math_opt/solution.pb.h"
namespace operations_research::math_opt {
// Status of a variable/constraint in a LP basis.
enum class BasisStatus : int8_t {
// The variable/constraint is free (it has no finite bounds).
kFree = BASIS_STATUS_FREE,
// The variable/constraint is at its lower bound (which must be finite).
kAtLowerBound = BASIS_STATUS_AT_LOWER_BOUND,
// The variable/constraint is at its upper bound (which must be finite).
kAtUpperBound = BASIS_STATUS_AT_UPPER_BOUND,
// The variable/constraint has identical finite lower and upper bounds.
kFixedValue = BASIS_STATUS_FIXED_VALUE,
// The variable/constraint is basic.
kBasic = BASIS_STATUS_BASIC,
};
MATH_OPT_DEFINE_ENUM(BasisStatus, BASIS_STATUS_UNSPECIFIED);
} // namespace operations_research::math_opt
#endif // OR_TOOLS_MATH_OPT_CPP_BASIS_STATUS_H_

View File

@@ -31,6 +31,7 @@
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/core/sparse_vector_view.h"
#include "ortools/math_opt/cpp/map_filter.h"
#include "ortools/math_opt/cpp/sparse_containers.h"
#include "ortools/math_opt/cpp/variable_and_expressions.h"
#include "ortools/math_opt/sparse_containers.pb.h"
@@ -38,14 +39,6 @@ namespace operations_research {
namespace math_opt {
namespace {
std::vector<std::pair<VariableId, double>> SortedVariableValues(
const VariableMap<double>& var_map) {
std::vector<std::pair<VariableId, double>> result(var_map.raw_map().begin(),
var_map.raw_map().end());
std::sort(result.begin(), result.end());
return result;
}
// Container must be an iterable on some type T where
// const ModelStorage* T::storage() const
// is defined.
@@ -111,8 +104,8 @@ CallbackData::CallbackData(const ModelStorage* storage,
mip_stats(proto.mip_stats()) {
CHECK(EnumFromProto(proto.event()).has_value());
if (proto.has_primal_solution_vector()) {
solution = VariableMap<double>(
storage, MakeView(proto.primal_solution_vector()).as_map<VariableId>());
solution = VariableValuesFromProto(storage, proto.primal_solution_vector())
.value();
}
auto maybe_time = util_time::DecodeGoogleApiProto(proto.runtime());
CHECK_OK(maybe_time.status());
@@ -153,11 +146,7 @@ CallbackResultProto CallbackResult::Proto() const {
CallbackResultProto result;
result.set_terminate(terminate);
for (const VariableMap<double>& solution : suggested_solutions) {
SparseDoubleVectorProto* solution_vector = result.add_suggested_solutions();
for (const auto& [typed_id, value] : SortedVariableValues(solution)) {
solution_vector->add_ids(typed_id.value());
solution_vector->add_values(value);
}
*result.add_suggested_solutions() = VariableValuesToProto(solution);
}
for (const GeneratedLinearConstraint& constraint : new_constraints) {
CallbackResultProto::GeneratedLinearConstraint* constraint_proto =
@@ -167,11 +156,8 @@ CallbackResultProto CallbackResult::Proto() const {
constraint.linear_constraint.lower_bound_minus_offset());
constraint_proto->set_upper_bound(
constraint.linear_constraint.upper_bound_minus_offset());
for (const auto& [typed_id, value] : SortedVariableValues(
constraint.linear_constraint.expression.terms())) {
constraint_proto->mutable_linear_expression()->add_ids(typed_id.value());
constraint_proto->mutable_linear_expression()->add_values(value);
}
*constraint_proto->mutable_linear_expression() =
VariableValuesToProto(constraint.linear_constraint.expression.terms());
}
return result;
}

View File

@@ -158,6 +158,9 @@ class IdMap {
inline void insert(InputIt first, InputIt last);
inline void insert(std::initializer_list<value_type> ilist);
template <typename M>
inline std::pair<iterator, bool> insert_or_assign(const K& k, M&& v);
inline std::pair<iterator, bool> emplace(const K& k, V v);
template <typename... Args>
inline std::pair<iterator, bool> try_emplace(const K& k, Args&&... args);
@@ -349,7 +352,7 @@ IdMap<K, V>::const_iterator::const_iterator(
template <typename K, typename V>
IdMap<K, V>::IdMap(const ModelStorage* storage, StorageType values)
: storage_(storage), map_(std::move(values)) {
: storage_(values.empty() ? nullptr : storage), map_(std::move(values)) {
if (!map_.empty()) {
CHECK(storage_ != nullptr);
}
@@ -421,6 +424,16 @@ void IdMap<K, V>::insert(std::initializer_list<value_type> ilist) {
insert(ilist.begin(), ilist.end());
}
template <typename K, typename V>
template <typename M>
std::pair<typename IdMap<K, V>::iterator, bool> IdMap<K, V>::insert_or_assign(
const K& k, M&& v) {
CheckOrSetModel(k);
auto initial_ret = map_.insert_or_assign(k.typed_id(), std::forward<M>(v));
return std::make_pair(iterator(this, std::move(initial_ret.first)),
initial_ret.second);
}
template <typename K, typename V>
std::pair<typename IdMap<K, V>::iterator, bool> IdMap<K, V>::emplace(const K& k,
V v) {

View File

@@ -217,7 +217,7 @@ IdSet<K>::const_iterator::const_iterator(
template <typename K>
IdSet<K>::IdSet(const ModelStorage* storage, StorageType values)
: storage_(storage), set_(std::move(values)) {
: storage_(values.empty() ? nullptr : storage), set_(std::move(values)) {
if (!set_.empty()) {
CHECK(storage_ != nullptr);
}

View File

@@ -20,46 +20,21 @@
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "ortools/base/logging.h"
#include "ortools/base/strong_int.h"
#include "ortools/base/status_builder.h"
#include "ortools/base/status_macros.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/core/sparse_vector_view.h"
#include "ortools/math_opt/cpp/linear_constraint.h"
#include "ortools/math_opt/cpp/sparse_containers.h"
#include "ortools/math_opt/cpp/variable_and_expressions.h"
#include "ortools/math_opt/solution.pb.h"
#include "ortools/math_opt/sparse_containers.pb.h"
#include "ortools/math_opt/validators/ids_validator.h"
#include "ortools/math_opt/validators/sparse_vector_validator.h"
#include "ortools/util/status_macros.h"
namespace operations_research {
namespace math_opt {
namespace {
template <typename Key>
IdMap<Key, double> ValuesFrom(const ModelStorage* const model,
const SparseDoubleVectorProto& vars_proto) {
return IdMap<Key, double>(
model, MakeView(vars_proto).as_map<typename Key::IdType>());
}
template <typename Key>
IdMap<Key, BasisStatus> BasisValues(
const ModelStorage* const model,
const SparseBasisStatusVector& basis_proto) {
absl::flat_hash_map<typename Key::IdType, BasisStatus> id_map;
for (const auto& [id, basis_status_proto] : MakeView(basis_proto)) {
// CHECK fails on BASIS_STATUS_UNSPECIFIED (the validation code should have
// tested that).
// We need to cast because the C++ proto API stores repeated enums as ints.
//
// On top of that iOS 11 does not support .value() on optionals so we must
// use operator*.
const std::optional<BasisStatus> basis_status =
EnumFromProto(static_cast<BasisStatusProto>(basis_status_proto));
CHECK(basis_status.has_value());
id_map[static_cast<typename Key::IdType>(id)] = *basis_status;
}
return IdMap<Key, BasisStatus>(model, std::move(id_map));
}
} // namespace
std::optional<absl::string_view> Enum<SolutionStatus>::ToOptString(
SolutionStatus value) {
@@ -83,110 +58,169 @@ absl::Span<const SolutionStatus> Enum<SolutionStatus>::AllValues() {
return absl::MakeConstSpan(kSolutionStatusValues);
}
std::optional<absl::string_view> Enum<BasisStatus>::ToOptString(
BasisStatus value) {
switch (value) {
case BasisStatus::kFree:
return "free";
case BasisStatus::kAtLowerBound:
return "at_lower_bound";
case BasisStatus::kAtUpperBound:
return "at_upper_bound";
case BasisStatus::kFixedValue:
return "fixed_value";
case BasisStatus::kBasic:
return "basic";
}
return std::nullopt;
}
absl::Span<const BasisStatus> Enum<BasisStatus>::AllValues() {
static constexpr BasisStatus kBasisStatusValues[] = {
BasisStatus::kFree, BasisStatus::kAtLowerBound,
BasisStatus::kAtUpperBound, BasisStatus::kFixedValue,
BasisStatus::kBasic,
};
return absl::MakeConstSpan(kBasisStatusValues);
}
PrimalSolution PrimalSolution::FromProto(
absl::StatusOr<PrimalSolution> PrimalSolution::FromProto(
const ModelStorage* model,
const PrimalSolutionProto& primal_solution_proto) {
PrimalSolution primal_solution;
primal_solution.variable_values =
ValuesFrom<Variable>(model, primal_solution_proto.variable_values());
OR_ASSIGN_OR_RETURN3(
primal_solution.variable_values,
VariableValuesFromProto(model, primal_solution_proto.variable_values()),
_ << "invalid variable_values");
primal_solution.objective_value = primal_solution_proto.objective_value();
// TODO(b/209014770): consider adding a function to simplify this pattern.
const std::optional<SolutionStatus> feasibility_status =
EnumFromProto(primal_solution_proto.feasibility_status());
CHECK(feasibility_status.has_value());
if (!feasibility_status.has_value()) {
return absl::InvalidArgumentError("feasibility_status must be specified");
}
primal_solution.feasibility_status = *feasibility_status;
return primal_solution;
}
PrimalRay PrimalRay::FromProto(const ModelStorage* model,
const PrimalRayProto& primal_ray_proto) {
return {.variable_values =
ValuesFrom<Variable>(model, primal_ray_proto.variable_values())};
PrimalSolutionProto PrimalSolution::Proto() const {
PrimalSolutionProto result;
*result.mutable_variable_values() = VariableValuesToProto(variable_values);
result.set_objective_value(objective_value);
result.set_feasibility_status(EnumToProto(feasibility_status));
return result;
}
DualSolution DualSolution::FromProto(
absl::StatusOr<PrimalRay> PrimalRay::FromProto(
const ModelStorage* model, const PrimalRayProto& primal_ray_proto) {
PrimalRay result;
OR_ASSIGN_OR_RETURN3(
result.variable_values,
VariableValuesFromProto(model, primal_ray_proto.variable_values()),
_ << "invalid variable_values");
return result;
}
PrimalRayProto PrimalRay::Proto() const {
PrimalRayProto result;
*result.mutable_variable_values() = VariableValuesToProto(variable_values);
return result;
}
absl::StatusOr<DualSolution> DualSolution::FromProto(
const ModelStorage* model, const DualSolutionProto& dual_solution_proto) {
DualSolution dual_solution;
dual_solution.dual_values =
ValuesFrom<LinearConstraint>(model, dual_solution_proto.dual_values());
dual_solution.reduced_costs =
ValuesFrom<Variable>(model, dual_solution_proto.reduced_costs());
OR_ASSIGN_OR_RETURN3(
dual_solution.dual_values,
LinearConstraintValuesFromProto(model, dual_solution_proto.dual_values()),
_ << "invalid dual_values");
OR_ASSIGN_OR_RETURN3(
dual_solution.reduced_costs,
VariableValuesFromProto(model, dual_solution_proto.reduced_costs()),
_ << "invalid reduced_costs");
if (dual_solution_proto.has_objective_value()) {
dual_solution.objective_value = dual_solution_proto.objective_value();
}
// TODO(b/209014770): consider adding a function to simplify this pattern.
const std::optional<SolutionStatus> feasibility_status =
EnumFromProto(dual_solution_proto.feasibility_status());
CHECK(feasibility_status.has_value());
if (!feasibility_status.has_value()) {
return absl::InvalidArgumentError("feasibility_status must be specified");
}
dual_solution.feasibility_status = *feasibility_status;
return dual_solution;
}
DualRay DualRay::FromProto(const ModelStorage* model,
const DualRayProto& dual_ray_proto) {
return {.dual_values =
ValuesFrom<LinearConstraint>(model, dual_ray_proto.dual_values()),
.reduced_costs =
ValuesFrom<Variable>(model, dual_ray_proto.reduced_costs())};
DualSolutionProto DualSolution::Proto() const {
DualSolutionProto result;
*result.mutable_dual_values() = LinearConstraintValuesToProto(dual_values);
*result.mutable_reduced_costs() = VariableValuesToProto(reduced_costs);
if (objective_value.has_value()) {
result.set_objective_value(*objective_value);
}
result.set_feasibility_status(EnumToProto(feasibility_status));
return result;
}
Basis Basis::FromProto(const ModelStorage* model,
const BasisProto& basis_proto) {
absl::StatusOr<DualRay> DualRay::FromProto(const ModelStorage* model,
const DualRayProto& dual_ray_proto) {
DualRay result;
OR_ASSIGN_OR_RETURN3(
result.dual_values,
LinearConstraintValuesFromProto(model, dual_ray_proto.dual_values()),
_ << "invalid dual_values");
OR_ASSIGN_OR_RETURN3(
result.reduced_costs,
VariableValuesFromProto(model, dual_ray_proto.reduced_costs()),
_ << "invalid reduced_costs");
return result;
}
DualRayProto DualRay::Proto() const {
DualRayProto result;
*result.mutable_dual_values() = LinearConstraintValuesToProto(dual_values);
*result.mutable_reduced_costs() = VariableValuesToProto(reduced_costs);
return result;
}
absl::StatusOr<Basis> Basis::FromProto(const ModelStorage* model,
const BasisProto& basis_proto) {
Basis basis;
basis.constraint_status =
BasisValues<LinearConstraint>(model, basis_proto.constraint_status());
basis.variable_status =
BasisValues<Variable>(model, basis_proto.variable_status());
// TODO(b/209014770): consider adding a function to simplify this pattern.
OR_ASSIGN_OR_RETURN3(
basis.constraint_status,
LinearConstraintBasisFromProto(model, basis_proto.constraint_status()),
_ << "invalid constraint_status");
OR_ASSIGN_OR_RETURN3(
basis.variable_status,
VariableBasisFromProto(model, basis_proto.variable_status()),
_ << "invalid variable_status");
const std::optional<SolutionStatus> basic_dual_feasibility =
EnumFromProto(basis_proto.basic_dual_feasibility());
CHECK(basic_dual_feasibility.has_value());
if (!basic_dual_feasibility.has_value()) {
return absl::InvalidArgumentError(
"basic_dual_feasibility for a basis must be specified");
}
basis.basic_dual_feasibility = *basic_dual_feasibility;
return basis;
}
Solution Solution::FromProto(const ModelStorage* model,
const SolutionProto& solution_proto) {
BasisProto Basis::Proto() const {
BasisProto result;
*result.mutable_constraint_status() =
LinearConstraintBasisToProto(constraint_status);
*result.mutable_variable_status() = VariableBasisToProto(variable_status);
result.set_basic_dual_feasibility(EnumToProto(basic_dual_feasibility));
return result;
}
absl::StatusOr<Solution> Solution::FromProto(
const ModelStorage* model, const SolutionProto& solution_proto) {
Solution solution;
if (solution_proto.has_primal_solution()) {
solution.primal_solution =
PrimalSolution::FromProto(model, solution_proto.primal_solution());
OR_ASSIGN_OR_RETURN3(
solution.primal_solution,
PrimalSolution::FromProto(model, solution_proto.primal_solution()),
_ << "invalid primal_solution");
}
if (solution_proto.has_dual_solution()) {
solution.dual_solution =
DualSolution::FromProto(model, solution_proto.dual_solution());
OR_ASSIGN_OR_RETURN3(
solution.dual_solution,
DualSolution::FromProto(model, solution_proto.dual_solution()),
_ << "invalid dual_solution");
}
if (solution_proto.has_basis()) {
solution.basis = Basis::FromProto(model, solution_proto.basis());
OR_ASSIGN_OR_RETURN3(solution.basis,
Basis::FromProto(model, solution_proto.basis()),
_ << "invalid basis");
}
return solution;
}
SolutionProto Solution::Proto() const {
SolutionProto result;
if (primal_solution.has_value()) {
*result.mutable_primal_solution() = primal_solution->Proto();
}
if (dual_solution.has_value()) {
*result.mutable_dual_solution() = dual_solution->Proto();
}
if (basis.has_value()) {
*result.mutable_basis() = basis->Proto();
}
return result;
}
} // namespace math_opt
} // namespace operations_research

View File

@@ -20,6 +20,7 @@
#include "absl/types/optional.h"
#include "absl/types/span.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/basis_status.h"
#include "ortools/math_opt/cpp/enums.h" // IWYU pragma: export
#include "ortools/math_opt/cpp/linear_constraint.h"
#include "ortools/math_opt/cpp/variable_and_expressions.h"
@@ -43,26 +44,6 @@ enum class SolutionStatus {
MATH_OPT_DEFINE_ENUM(SolutionStatus, SOLUTION_STATUS_UNSPECIFIED);
// Status of a variable/constraint in a LP basis.
enum class BasisStatus : int8_t {
// The variable/constraint is free (it has no finite bounds).
kFree = BASIS_STATUS_FREE,
// The variable/constraint is at its lower bound (which must be finite).
kAtLowerBound = BASIS_STATUS_AT_LOWER_BOUND,
// The variable/constraint is at its upper bound (which must be finite).
kAtUpperBound = BASIS_STATUS_AT_UPPER_BOUND,
// The variable/constraint has identical finite lower and upper bounds.
kFixedValue = BASIS_STATUS_FIXED_VALUE,
// The variable/constraint is basic.
kBasic = BASIS_STATUS_BASIC,
};
MATH_OPT_DEFINE_ENUM(BasisStatus, BASIS_STATUS_UNSPECIFIED);
// A solution to an optimization problem.
//
// E.g. consider a simple linear program:
@@ -76,10 +57,18 @@ MATH_OPT_DEFINE_ENUM(BasisStatus, BASIS_STATUS_UNSPECIFIED);
// For the general case of a MathOpt optimization model, see
// go/mathopt-solutions for details.
struct PrimalSolution {
static PrimalSolution FromProto(
// Returns the PrimalSolution equivalent of primal_solution_proto.
//
// Returns an error when:
// * VariableValuesFromProto(primal_solution_proto.variable_values) fails.
// * the feasibility_status is not specified.
static absl::StatusOr<PrimalSolution> FromProto(
const ModelStorage* model,
const PrimalSolutionProto& primal_solution_proto);
// Returns the proto equivalent of this.
PrimalSolutionProto Proto() const;
VariableMap<double> variable_values;
double objective_value = 0.0;
@@ -107,8 +96,15 @@ struct PrimalSolution {
// For the general case of a MathOpt optimization model, see
// go/mathopt-solutions for details.
struct PrimalRay {
static PrimalRay FromProto(const ModelStorage* model,
const PrimalRayProto& primal_ray_proto);
// Returns the PrimalRay equivalent of primal_ray_proto.
//
// Returns an error when
// VariableValuesFromProto(primal_ray_proto.variable_values) fails.
static absl::StatusOr<PrimalRay> FromProto(
const ModelStorage* model, const PrimalRayProto& primal_ray_proto);
// Returns the proto equivalent of this.
PrimalRayProto Proto() const;
VariableMap<double> variable_values;
};
@@ -128,8 +124,17 @@ struct PrimalRay {
// For the general case, see go/mathopt-solutions and go/mathopt-dual (and
// note that the dual objective depends on r in the general case).
struct DualSolution {
static DualSolution FromProto(const ModelStorage* model,
const DualSolutionProto& dual_solution_proto);
// Returns the DualSolution equivalent of dual_solution_proto.
//
// Returns an error when any of:
// * VariableValuesFromProto(dual_solution_proto.reduced_costs) fails.
// * LinearConstraintValuesFromProto(dual_solution_proto.dual_values) fails.
// * dual_solution_proto.feasibility_status is not specified.
static absl::StatusOr<DualSolution> FromProto(
const ModelStorage* model, const DualSolutionProto& dual_solution_proto);
// Returns the proto equivalent of this.
DualSolutionProto Proto() const;
LinearConstraintMap<double> dual_values;
VariableMap<double> reduced_costs;
@@ -159,8 +164,16 @@ struct DualSolution {
// For the general case, see go/mathopt-solutions and go/mathopt-dual (and
// note that the dual objective depends on r in the general case).
struct DualRay {
static DualRay FromProto(const ModelStorage* model,
const DualRayProto& dual_ray_proto);
// Returns the DualRay equivalent of dual_ray_proto.
//
// Returns an error when either of:
// * VariableValuesFromProto(dual_ray_proto.reduced_costs) fails.
// * LinearConstraintValuesFromProto(dual_ray_proto.dual_values) fails.
static absl::StatusOr<DualRay> FromProto(const ModelStorage* model,
const DualRayProto& dual_ray_proto);
// Returns the proto equivalent of this.
DualRayProto Proto() const;
LinearConstraintMap<double> dual_values;
VariableMap<double> reduced_costs;
@@ -193,12 +206,17 @@ struct DualRay {
// See go/mathopt-basis for treatment of the general case and an explanation
// of how a dual solution is determined for a basis.
struct Basis {
// Returns a Basis built from the input indexed_basis, CHECKing that no
// values is BASIS_STATUS_UNSPECIFIED. No check is done on other values so
// out of bounds values e.g. BasisStatusProto_MAX+1 won't raise an
// assertion. See SpaseBasisStatusVectorIsValid().
static Basis FromProto(const ModelStorage* model,
const BasisProto& basis_proto);
// Returns the equivalent Basis object for basis_proto.
//
// Returns an error if:
// * VariableBasisFromProto(basis_proto.variable_status) fails.
// * LinearConstraintBasisFromProto(basis_proto.constraint_status) fails.
// * basis_proto.basic_dual_feasibility is unspecified.
static absl::StatusOr<Basis> FromProto(const ModelStorage* model,
const BasisProto& basis_proto);
// Returns the proto equivalent of this.
BasisProto Proto() const;
LinearConstraintMap<BasisStatus> constraint_status;
VariableMap<BasisStatus> variable_status;
@@ -218,8 +236,16 @@ struct Basis {
// 3. Other continuous solvers often return a primal and dual solution
// solution that are connected in a solver-dependent form.
struct Solution {
static Solution FromProto(const ModelStorage* model,
const SolutionProto& solution_proto);
// Returns the Solution equivalent of solution_proto.
//
// Returns an error if FromProto() fails on any field that is not std::nullopt
// (see the static FromProto() functions for each field type for details).
static absl::StatusOr<Solution> FromProto(
const ModelStorage* model, const SolutionProto& solution_proto);
// Returns the proto equivalent of this.
SolutionProto Proto() const;
std::optional<PrimalSolution> primal_solution;
std::optional<DualSolution> dual_solution;
std::optional<Basis> basis;

View File

@@ -26,35 +26,16 @@
#include "absl/types/span.h"
#include "ortools/base/logging.h"
#include "ortools/base/protoutil.h"
#include "ortools/base/status_macros.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/linear_constraint.h"
#include "ortools/math_opt/cpp/variable_and_expressions.h"
#include "ortools/math_opt/solution.pb.h"
#include "ortools/port/proto_utils.h"
#include "ortools/util/status_macros.h"
namespace operations_research {
namespace math_opt {
namespace {
// Converts a map with BasisStatusProto values to a map with BasisStatus values
// CHECKing that no values are BASIS_STATUS_UNSPECIFIED (the validation code
// should have tested that).
//
// TODO(b/201344491): use FromProto() factory methods on solution members and
// remove the need for this conversion from `IndexedSolutions`.
template <typename TypedIndex>
absl::flat_hash_map<TypedIndex, BasisStatus> BasisStatusMapFromProto(
const absl::flat_hash_map<TypedIndex, BasisStatusProto>& proto_map) {
absl::flat_hash_map<TypedIndex, BasisStatus> cpp_map;
for (const auto& [id, proto_value] : proto_map) {
const std::optional<BasisStatus> opt_status = EnumFromProto(proto_value);
CHECK(opt_status.has_value());
cpp_map.emplace(id, *opt_status);
}
return cpp_map;
}
} // namespace
std::optional<absl::string_view> Enum<FeasibilityStatus>::ToOptString(
FeasibilityStatus value) {
@@ -173,12 +154,10 @@ Termination Termination::NoSolutionFound(const Limit limit,
return termination;
}
TerminationProto Termination::ToProto() const {
TerminationProto Termination::Proto() const {
TerminationProto proto;
proto.set_reason(EnumToProto(reason));
if (limit.has_value()) {
proto.set_limit(EnumToProto(*limit));
}
proto.set_limit(EnumToProto(limit));
proto.set_detail(detail);
return proto;
}
@@ -188,32 +167,16 @@ bool Termination::limit_reached() const {
reason == TerminationReason::kNoSolutionFound;
}
Termination Termination::FromProto(const TerminationProto& termination_proto) {
const bool limit_reached =
termination_proto.reason() == TERMINATION_REASON_FEASIBLE ||
termination_proto.reason() == TERMINATION_REASON_NO_SOLUTION_FOUND;
const bool has_limit = termination_proto.limit() != LIMIT_UNSPECIFIED;
CHECK_EQ(limit_reached, has_limit)
<< "Termination reason should be TERMINATION_REASON_FEASIBLE or "
"TERMINATION_REASON_NO_SOLUTION_FOUND if and only if limit is "
"specified, but found reason="
<< ProtoEnumToString(termination_proto.reason())
<< " and limit=" << ProtoEnumToString(termination_proto.limit());
if (has_limit) {
const std::optional<Limit> opt_limit =
EnumFromProto(termination_proto.limit());
CHECK(opt_limit.has_value());
if (termination_proto.reason() == TERMINATION_REASON_FEASIBLE) {
return Feasible(*opt_limit, termination_proto.detail());
}
return NoSolutionFound(*opt_limit, termination_proto.detail());
}
const std::optional<TerminationReason> opt_reason =
absl::StatusOr<Termination> Termination::FromProto(
const TerminationProto& termination_proto) {
const std::optional<TerminationReason> reason =
EnumFromProto(termination_proto.reason());
CHECK(opt_reason.has_value());
return Termination(*opt_reason, termination_proto.detail());
if (!reason.has_value()) {
return absl::InvalidArgumentError("reason must be specified");
}
Termination result(*reason, termination_proto.detail());
result.limit = EnumFromProto(termination_proto.limit());
return result;
}
std::ostream& operator<<(std::ostream& ostr, const Termination& termination) {
@@ -235,7 +198,7 @@ std::string Termination::ToString() const {
return stream.str();
}
ProblemStatusProto ProblemStatus::ToProto() const {
ProblemStatusProto ProblemStatus::Proto() const {
ProblemStatusProto proto;
proto.set_primal_status(EnumToProto(primal_status));
proto.set_dual_status(EnumToProto(dual_status));
@@ -243,21 +206,22 @@ ProblemStatusProto ProblemStatus::ToProto() const {
return proto;
}
ProblemStatus ProblemStatus::FromProto(
absl::StatusOr<ProblemStatus> ProblemStatus::FromProto(
const ProblemStatusProto& problem_status_proto) {
ProblemStatus result;
// TODO(b/209014770): consider adding a function to simplify this pattern.
const std::optional<FeasibilityStatus> opt_primal_status =
const std::optional<FeasibilityStatus> primal_status =
EnumFromProto(problem_status_proto.primal_status());
const std::optional<FeasibilityStatus> opt_dual_status =
if (!primal_status.has_value()) {
return absl::InvalidArgumentError("primal_status must be specified");
}
const std::optional<FeasibilityStatus> dual_status =
EnumFromProto(problem_status_proto.dual_status());
CHECK(opt_primal_status.has_value());
CHECK(opt_dual_status.has_value());
result.primal_status = *opt_primal_status;
result.dual_status = *opt_dual_status;
result.primal_or_dual_infeasible =
problem_status_proto.primal_or_dual_infeasible();
return result;
if (!dual_status.has_value()) {
return absl::InvalidArgumentError("dual_status must be specified");
}
return ProblemStatus{.primal_status = *primal_status,
.dual_status = *dual_status,
.primal_or_dual_infeasible =
problem_status_proto.primal_or_dual_infeasible()};
}
std::ostream& operator<<(std::ostream& ostr,
@@ -276,13 +240,14 @@ std::string ProblemStatus::ToString() const {
return stream.str();
}
SolveStatsProto SolveStats::ToProto() const {
absl::StatusOr<SolveStatsProto> SolveStats::Proto() const {
SolveStatsProto proto;
CHECK_OK(
util_time::EncodeGoogleApiProto(solve_time, proto.mutable_solve_time()));
RETURN_IF_ERROR(
util_time::EncodeGoogleApiProto(solve_time, proto.mutable_solve_time()))
<< "invalid solve_time (value must be finite)";
proto.set_best_primal_bound(best_primal_bound);
proto.set_best_dual_bound(best_dual_bound);
*proto.mutable_problem_status() = problem_status.ToProto();
*proto.mutable_problem_status() = problem_status.Proto();
proto.set_simplex_iterations(simplex_iterations);
proto.set_barrier_iterations(barrier_iterations);
proto.set_first_order_iterations(first_order_iterations);
@@ -290,14 +255,19 @@ SolveStatsProto SolveStats::ToProto() const {
return proto;
}
SolveStats SolveStats::FromProto(const SolveStatsProto& solve_stats_proto) {
absl::StatusOr<SolveStats> SolveStats::FromProto(
const SolveStatsProto& solve_stats_proto) {
SolveStats result;
result.solve_time =
util_time::DecodeGoogleApiProto(solve_stats_proto.solve_time()).value();
OR_ASSIGN_OR_RETURN3(
result.solve_time,
util_time::DecodeGoogleApiProto(solve_stats_proto.solve_time()),
_ << "invalid solve_time");
result.best_primal_bound = solve_stats_proto.best_primal_bound();
result.best_dual_bound = solve_stats_proto.best_dual_bound();
result.problem_status =
ProblemStatus::FromProto(solve_stats_proto.problem_status());
OR_ASSIGN_OR_RETURN3(
result.problem_status,
ProblemStatus::FromProto(solve_stats_proto.problem_status()),
_ << "invalid problem_status");
result.simplex_iterations = solve_stats_proto.simplex_iterations();
result.barrier_iterations = solve_stats_proto.barrier_iterations();
result.first_order_iterations = solve_stats_proto.first_order_iterations();
@@ -324,27 +294,80 @@ std::string SolveStats::ToString() const {
return stream.str();
}
SolveResult SolveResult::FromProto(const ModelStorage* model,
const SolveResultProto& solve_result_proto) {
SolveResult result(Termination::FromProto(solve_result_proto.termination()));
result.solve_stats = SolveStats::FromProto(solve_result_proto.solve_stats());
absl::Status CheckSolverSpecificOutputEmpty(const SolveResultProto& result) {
if (result.solver_specific_output_case() ==
SolveResultProto::SOLVER_SPECIFIC_OUTPUT_NOT_SET) {
return absl::OkStatus();
}
return util::InvalidArgumentErrorBuilder()
<< "cannot set solver specific output twice, was already "
<< static_cast<int>(result.solver_specific_output_case());
}
for (const SolutionProto& solution : solve_result_proto.solutions()) {
result.solutions.push_back(Solution::FromProto(model, solution));
absl::StatusOr<SolveResultProto> SolveResult::Proto() const {
SolveResultProto result;
*result.mutable_termination() = termination.Proto();
OR_ASSIGN_OR_RETURN3(*result.mutable_solve_stats(), solve_stats.Proto(),
_ << "invalid solve_stats");
for (const Solution& solution : solutions) {
*result.add_solutions() = solution.Proto();
}
for (const PrimalRayProto& primal_ray : solve_result_proto.primal_rays()) {
result.primal_rays.push_back(PrimalRay::FromProto(model, primal_ray));
for (const PrimalRay& primal_ray : primal_rays) {
*result.add_primal_rays() = primal_ray.Proto();
}
for (const DualRayProto& dual_ray : solve_result_proto.dual_rays()) {
result.dual_rays.push_back(DualRay::FromProto(model, dual_ray));
for (const DualRay& dual_ray : dual_rays) {
*result.add_dual_rays() = dual_ray.Proto();
}
if (solve_result_proto.has_gscip_output()) {
result.gscip_solver_specific_output =
std::move(solve_result_proto.gscip_output());
// See yaqs/5107601535926272 on checking if a proto is empty.
if (gscip_solver_specific_output.ByteSizeLong() > 0) {
*result.mutable_gscip_output() = gscip_solver_specific_output;
}
return result;
}
absl::StatusOr<SolveResult> SolveResult::FromProto(
const ModelStorage* model, const SolveResultProto& solve_result_proto) {
OR_ASSIGN_OR_RETURN3(auto termination,
Termination::FromProto(solve_result_proto.termination()),
_ << "invalid termination");
SolveResult result(std::move(termination));
OR_ASSIGN_OR_RETURN3(result.solve_stats,
SolveStats::FromProto(solve_result_proto.solve_stats()),
_ << "invalid solve_stats");
for (int i = 0; i < solve_result_proto.solutions_size(); ++i) {
OR_ASSIGN_OR_RETURN3(
auto solution,
Solution::FromProto(model, solve_result_proto.solutions(i)),
_ << "invalid solution at index " << i);
result.solutions.push_back(std::move(solution));
}
for (int i = 0; i < solve_result_proto.primal_rays_size(); ++i) {
OR_ASSIGN_OR_RETURN3(
auto primal_ray,
PrimalRay::FromProto(model, solve_result_proto.primal_rays(i)),
_ << "invalid primal ray at index " << i);
result.primal_rays.push_back(std::move(primal_ray));
}
for (int i = 0; i < solve_result_proto.dual_rays_size(); ++i) {
OR_ASSIGN_OR_RETURN3(
auto dual_ray,
DualRay::FromProto(model, solve_result_proto.dual_rays(i)),
_ << "invalid dual ray at index " << i);
result.dual_rays.push_back(std::move(dual_ray));
}
switch (solve_result_proto.solver_specific_output_case()) {
case SolveResultProto::kGscipOutput:
result.gscip_solver_specific_output = solve_result_proto.gscip_output();
return result;
case SolveResultProto::SOLVER_SPECIFIC_OUTPUT_NOT_SET:
return result;
}
return util::InvalidArgumentErrorBuilder()
<< "unexpected value of solver_specific_output_case "
<< solve_result_proto.solver_specific_output_case();
}
bool SolveResult::has_primal_feasible_solution() const {
return !solutions.empty() && solutions[0].primal_solution.has_value() &&
(solutions[0].primal_solution->feasibility_status ==

View File

@@ -19,6 +19,7 @@
#include <utility>
#include <vector>
#include "absl/status/statusor.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "ortools/base/logging.h"
@@ -79,10 +80,11 @@ struct ProblemStatus {
// infeasibility, unboundedness, or both).
bool primal_or_dual_infeasible = false;
static ProblemStatus FromProto(
// Returns an error if the primal_status or dual_status is unspecified.
static absl::StatusOr<ProblemStatus> FromProto(
const ProblemStatusProto& problem_status_proto);
ProblemStatusProto ToProto() const;
ProblemStatusProto Proto() const;
std::string ToString() const;
};
@@ -108,7 +110,6 @@ struct SolveStats {
// may be non-trivial even when no primal feasible solutions are returned.
// * best_dual_bound is always better (smaller for minimization and larger
// for maximization) than best_primal_bound.
double best_primal_bound = 0.0;
// Solver claims the optimal value is equal or worse (larger for
@@ -137,10 +138,12 @@ struct SolveStats {
int node_count = 0;
// Will CHECK fail on invalid input, if problem_status is invalid.
static SolveStats FromProto(const SolveStatsProto& solve_stats_proto);
// Returns an error if converting the problem_status or solve_time fails.
static absl::StatusOr<SolveStats> FromProto(
const SolveStatsProto& solve_stats_proto);
SolveStatsProto ToProto() const;
// Will return an error if solve_time is not finite.
absl::StatusOr<SolveStatsProto> Proto() const;
std::string ToString() const;
};
@@ -258,9 +261,15 @@ struct Termination {
// functions Feasible and NoSolutionFound.
explicit Termination(TerminationReason reason, std::string detail = {});
// Additional information in `limit` when value is kFeasible or
// kNoSolutionFound, see `limit` for details.
TerminationReason reason;
// Is set iff reason is kFeasible or kNoSolutionFound.
// A Termination within a SolveResult returned by math_opt::Solve() satisfies
// some additional invariants:
// * limit is set iff reason is kFeasible or kNoSolutionFound.
// * if the limit is kCutoff, the termination reason will be
// kNoSolutionFound.
std::optional<Limit> limit;
// Additional typically solver specific information about termination.
@@ -272,20 +281,16 @@ struct Termination {
// kNoSolutionFound, and limit is not empty).
bool limit_reached() const;
// Will CHECK fail on invalid input, if reason is unspecified, if limit is
// set when reason is not TERMINATION_REASON_FEASIBLE or
// TERMINATION_REASON_NO_SOLUTION_FOUND, or if limit is unspecified when
// reason is TERMINATION_REASON_FEASIBLE or
// TERMINATION_REASON_NO_SOLUTION_FOUND (see solution_validator.h).
static Termination FromProto(const TerminationProto& termination_proto);
// Sets the reason to kFeasible
static Termination Feasible(Limit limit, std::string detail = {});
// Sets the reason to kNoSolutionFound
static Termination NoSolutionFound(Limit limit, std::string detail = {});
TerminationProto ToProto() const;
// Will return an error if termination_proto.reason is UNSPECIFIED.
static absl::StatusOr<Termination> FromProto(
const TerminationProto& termination_proto);
TerminationProto Proto() const;
std::string ToString() const;
};
@@ -332,15 +337,43 @@ struct SolveResult {
// Solver specific output from Gscip. Only populated if Gscip is used.
GScipOutput gscip_solver_specific_output;
static SolveResult FromProto(const ModelStorage* model,
const SolveResultProto& solve_result_proto);
// Returns the SolveResult equivalent of solve_result_proto.
//
// Returns an error if:
// * Any solution or ray cannot be read from proto (e.g. on a subfield,
// ids.size != values.size).
// * termination or solve_result cannot be read from proto.
// See the FromProto() functions for these types for details.
//
// Note: this is (intentionally) a much weaker test than ValidateResult(). The
// guarantees are just strong enough to ensure that a SolveResult and
// SolveResultProto can round trip cleanly, e.g. we do not check that a
// termination reason optimal implies that there is at least one primal
// feasible solution.
//
// While ValidateResult() is called automatically when you are solving
// locally, users who are reading a solution from disk, solving remotely, or
// getting their SolveResultProto (or SolveResult) by any other means are
// encouraged to either call ValidateResult() themselves, do their own
// validation, or not rely on the strong guarantees of ValidateResult()
// and just treat SolveResult as a simple struct.
static absl::StatusOr<SolveResult> FromProto(
const ModelStorage* model, const SolveResultProto& solve_result_proto);
// Returns the proto equivalent of this.
//
// Note that the proto uses a oneof for solver specific output. This method
// will fail if multiple solver specific outputs are set. TODO(b/231134639):
// investigate removing the oneof from the proto.
absl::StatusOr<SolveResultProto> Proto() const;
absl::Duration solve_time() const { return solve_stats.solve_time; }
// Indicates if at least one primal feasible solution is available.
//
// When termination.reason is TerminationReason::kOptimal, this is guaranteed
// to be true and need not be checked.
// When termination.reason is TerminationReason::kOptimal or
// TerminationReason::kFeasible, this is guaranteed to be true and need not be
// checked.
bool has_primal_feasible_solution() const;
// The objective value of the best primal feasible solution. Will CHECK fail
@@ -367,20 +400,27 @@ struct SolveResult {
// are no primal rays.
const VariableMap<double>& ray_variable_values() const;
// Indicates if the best primal solution has an associated dual feasible
// solution.
// Indicates if the best solution has an associated dual feasible solution.
//
// This is NOT guaranteed to be true when termination.reason is
// TerminationReason::kOptimal. It also may be true even when the best primal
// solution is not feasible.
// TerminationReason::kOptimal. It also may be true even when the best
// solution does not have an associated primal feasible solution.
bool has_dual_feasible_solution() const;
// The dual values from the best dual solution. Will CHECK fail if there
// are no dual solutions.
// The dual values associated to the best solution.
//
// If there is at least one primal feasible solution, this corresponds to the
// dual values associated to the best primal feasible solution. Will CHECK
// fail if the best solution does not have an associated dual feasible
// solution.
const LinearConstraintMap<double>& dual_values() const;
// The reduced from the best dual solution. Will CHECK fail if there
// are no dual solutions.
// The reduced costs associated to the best solution.
//
// If there is at least one primal feasible solution, this corresponds to the
// reduced costs associated to the best primal feasible solution. Will CHECK
// fail if the best solution does not have an associated dual feasible
// solution.
const VariableMap<double>& reduced_costs() const;
// Indicates if at least one dual ray is available.
@@ -397,13 +437,13 @@ struct SolveResult {
// are no dual rays.
const VariableMap<double>& ray_reduced_costs() const;
// Indicates if at least one basis is available.
// Indicates if the best solution has an associated basis.
bool has_basis() const;
// The constraint basis status for the first primal/dual pair.
// The constraint basis status for the best solution.
const LinearConstraintMap<BasisStatus>& constraint_status() const;
// The variable basis status for the first primal/dual pair.
// The variable basis status for the best solution.
const VariableMap<BasisStatus>& variable_status() const;
};

View File

@@ -0,0 +1,154 @@
// Copyright 2010-2021 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/cpp/sparse_containers.h"
namespace operations_research::math_opt {
namespace {
// SparseVectorProtoType should be SparseDoubleVector or SparseBasisStatusVector
template <typename SparseVectorProtoType>
absl::Status CheckSparseVectorProto(const SparseVectorProtoType& vec) {
RETURN_IF_ERROR(CheckIdsAndValuesSize(MakeView(vec)));
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(vec.ids()));
return absl::OkStatus();
}
template <typename Key>
absl::StatusOr<IdMap<Key, BasisStatus>> BasisVectorFromProto(
const ModelStorage* const model,
const SparseBasisStatusVector& basis_proto) {
using IdType = typename Key::IdType;
absl::flat_hash_map<IdType, BasisStatus> raw_map;
raw_map.reserve(basis_proto.ids_size());
for (const auto& [id, basis_status_proto_int] : MakeView(basis_proto)) {
const auto basis_status_proto =
static_cast<BasisStatusProto>(basis_status_proto_int);
const std::optional<BasisStatus> basis_status =
EnumFromProto(basis_status_proto);
if (!basis_status.has_value()) {
return util::InvalidArgumentErrorBuilder()
<< "basis status not specified for id " << id;
}
raw_map[IdType(id)] = *basis_status;
}
return IdMap<Key, BasisStatus>(model, std::move(raw_map));
}
template <typename Key>
SparseDoubleVectorProto IdMapToProto(const IdMap<Key, double>& id_map) {
using IdType = typename Key::IdType;
SparseDoubleVectorProto result;
std::vector<std::pair<IdType, double>> sorted_entries(
id_map.raw_map().begin(), id_map.raw_map().end());
std::sort(sorted_entries.begin(), sorted_entries.end());
for (const auto& [id, val] : sorted_entries) {
result.add_ids(id.value());
result.add_values(val);
}
return result;
}
template <typename Key>
SparseBasisStatusVector BasisIdMapToProto(
const IdMap<Key, BasisStatus>& basis_map) {
using IdType = typename Key::IdType;
SparseBasisStatusVector result;
std::vector<std::pair<IdType, BasisStatus>> sorted_entries(
basis_map.raw_map().begin(), basis_map.raw_map().end());
std::sort(sorted_entries.begin(), sorted_entries.end());
for (const auto& [id, val] : sorted_entries) {
result.add_ids(id.value());
result.add_values(EnumToProto(val));
}
return result;
}
absl::Status VariableIdsExist(const ModelStorage* const model,
const absl::Span<const int64_t> ids) {
for (const int64_t id : ids) {
if (!model->has_variable(VariableId(id))) {
return util::InvalidArgumentErrorBuilder()
<< "no variable with id " << id << " exists";
}
}
return absl::OkStatus();
}
absl::Status LinearConstraintIdsExist(const ModelStorage* const model,
const absl::Span<const int64_t> ids) {
for (const int64_t id : ids) {
if (!model->has_linear_constraint(LinearConstraintId(id))) {
return util::InvalidArgumentErrorBuilder()
<< "no linear constraint with id " << id << " exists";
}
}
return absl::OkStatus();
}
} // namespace
absl::StatusOr<VariableMap<double>> VariableValuesFromProto(
const ModelStorage* const model,
const SparseDoubleVectorProto& vars_proto) {
RETURN_IF_ERROR(CheckSparseVectorProto(vars_proto));
RETURN_IF_ERROR(VariableIdsExist(model, vars_proto.ids()));
return VariableMap<double>(model, MakeView(vars_proto).as_map<VariableId>());
}
SparseDoubleVectorProto VariableValuesToProto(
const VariableMap<double>& variable_values) {
return IdMapToProto(variable_values);
}
absl::StatusOr<LinearConstraintMap<double>> LinearConstraintValuesFromProto(
const ModelStorage* const model,
const SparseDoubleVectorProto& lin_cons_proto) {
RETURN_IF_ERROR(CheckSparseVectorProto(lin_cons_proto));
RETURN_IF_ERROR(LinearConstraintIdsExist(model, lin_cons_proto.ids()));
return LinearConstraintMap<double>(
model, MakeView(lin_cons_proto).as_map<LinearConstraintId>());
}
SparseDoubleVectorProto LinearConstraintValuesToProto(
const LinearConstraintMap<double>& linear_constraint_values) {
return IdMapToProto(linear_constraint_values);
}
absl::StatusOr<VariableMap<BasisStatus>> VariableBasisFromProto(
const ModelStorage* const model,
const SparseBasisStatusVector& basis_proto) {
RETURN_IF_ERROR(CheckSparseVectorProto(basis_proto));
RETURN_IF_ERROR(VariableIdsExist(model, basis_proto.ids()));
return BasisVectorFromProto<Variable>(model, basis_proto);
}
SparseBasisStatusVector VariableBasisToProto(
const VariableMap<BasisStatus>& basis_values) {
return BasisIdMapToProto(basis_values);
}
absl::StatusOr<LinearConstraintMap<BasisStatus>> LinearConstraintBasisFromProto(
const ModelStorage* const model,
const SparseBasisStatusVector& basis_proto) {
RETURN_IF_ERROR(CheckSparseVectorProto(basis_proto));
RETURN_IF_ERROR(LinearConstraintIdsExist(model, basis_proto.ids()));
return BasisVectorFromProto<LinearConstraint>(model, basis_proto);
}
SparseBasisStatusVector LinearConstraintBasisToProto(
const LinearConstraintMap<BasisStatus>& basis_values) {
return BasisIdMapToProto(basis_values);
}
} // namespace operations_research::math_opt

View File

@@ -0,0 +1,104 @@
// Copyright 2010-2021 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_CPP_SPARSE_CONTAINERS_H_
#define OR_TOOLS_MATH_OPT_CPP_SPARSE_CONTAINERS_H_
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "ortools/base/logging.h"
#include "ortools/base/status_builder.h"
#include "ortools/base/status_macros.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/core/sparse_vector_view.h"
#include "ortools/math_opt/cpp/basis_status.h"
#include "ortools/math_opt/cpp/linear_constraint.h"
#include "ortools/math_opt/cpp/variable_and_expressions.h"
#include "ortools/math_opt/solution.pb.h"
#include "ortools/math_opt/sparse_containers.pb.h"
#include "ortools/math_opt/validators/ids_validator.h"
#include "ortools/math_opt/validators/sparse_vector_validator.h"
#include "ortools/util/status_macros.h"
namespace operations_research::math_opt {
// Returns the VariableMap<double> equivalent to `vars_proto`.
//
// Requires that (or returns a status error):
// * vars_proto.ids and vars_proto.values have equal size.
// * vars_proto.ids is sorted.
// * vars_proto.ids has elements in [0, max(int64_t)).
// * vars_proto.ids has elements that are variables in `model`.
//
// Note that the values of vars_proto.values are not checked (it may have NaNs).
absl::StatusOr<VariableMap<double>> VariableValuesFromProto(
const ModelStorage* const model, const SparseDoubleVectorProto& vars_proto);
// Returns the proto equivalent of variable_values.
SparseDoubleVectorProto VariableValuesToProto(
const VariableMap<double>& variable_values);
// Returns the LinearConstraintMap<double> equivalent to `lin_cons_proto`.
//
// Requires that (or returns a status error):
// * lin_cons_proto.ids and lin_cons_proto.values have equal size.
// * lin_cons_proto.ids is sorted.
// * lin_cons_proto.ids has elements in [0, max(int64_t)).
// * lin_cons_proto.ids has elements that are linear constraints in `model`.
//
// Note that the values of lin_cons_proto.values are not checked (it may have
// NaNs).
absl::StatusOr<LinearConstraintMap<double>> LinearConstraintValuesFromProto(
const ModelStorage* const model,
const SparseDoubleVectorProto& lin_cons_proto);
// Returns the proto equivalent of linear_constraint_values.
SparseDoubleVectorProto LinearConstraintValuesToProto(
const LinearConstraintMap<double>& linear_constraint_values);
// Returns the VariableMap<BasisStatus> equivalent to `basis_proto`.
//
// Requires that (or returns a status error):
// * basis_proto.ids and basis_proto.values have equal size.
// * basis_proto.ids is sorted.
// * basis_proto.ids has elements in [0, max(int64_t)).
// * basis_proto.ids has elements that are variables in `model`.
// * basis_proto.values does not contain UNSPECIFIED and has valid enum values.
absl::StatusOr<VariableMap<BasisStatus>> VariableBasisFromProto(
const ModelStorage* const model,
const SparseBasisStatusVector& basis_proto);
// Returns the proto equivalent of basis_values.
SparseBasisStatusVector VariableBasisToProto(
const VariableMap<BasisStatus>& basis_values);
// Returns the LinearConstraintMap<BasisStatus> equivalent to `basis_proto`.
//
// Requires that (or returns a status error):
// * basis_proto.ids and basis_proto.values have equal size.
// * basis_proto.ids is sorted.
// * basis_proto.ids has elements in [0, max(int64_t)).
// * basis_proto.ids has elements that are linear constraints in `model`.
// * basis_proto.values does not contain UNSPECIFIED and has valid enum values.
absl::StatusOr<LinearConstraintMap<BasisStatus>> LinearConstraintBasisFromProto(
const ModelStorage* const model,
const SparseBasisStatusVector& basis_proto);
// Returns the proto equivalent of basis_values.
SparseBasisStatusVector LinearConstraintBasisToProto(
const LinearConstraintMap<BasisStatus>& basis_values);
} // namespace operations_research::math_opt
#endif // OR_TOOLS_MATH_OPT_CPP_SPARSE_CONTAINERS_H_

View File

@@ -267,6 +267,24 @@ std::ostream& operator<<(std::ostream& ostr, const QuadraticExpression& expr) {
return ostr;
}
std::ostream& operator<<(std::ostream& ostr,
const BoundedQuadraticExpression& bounded_expression) {
// TODO(b/170991498): use bijective conversion from double to base-10 string
// to make sure we can reproduce bugs.
const double lb = bounded_expression.lower_bound;
const double ub = bounded_expression.upper_bound;
if (lb == ub) {
ostr << bounded_expression.expression << " = " << lb;
} else if (lb == -kInf) {
ostr << bounded_expression.expression << "" << ub;
} else if (ub == kInf) {
ostr << bounded_expression.expression << "" << lb;
} else {
ostr << lb << "" << bounded_expression.expression << "" << ub;
}
return ostr;
}
#ifdef MATH_OPT_USE_EXPRESSION_COUNTERS
QuadraticExpression::QuadraticExpression() { ++num_calls_default_constructor_; }

View File

@@ -477,8 +477,8 @@ inline bool operator!=(const Variable& lhs, const Variable& rhs);
// A LinearExpression with a lower bound.
struct LowerBoundedLinearExpression {
// Users are not expected to use this constructor. Instead they should build
// this object using the overloads of >= and <= operators. For example `x + y
// Users are not expected to use this constructor. Instead, they should build
// this object using overloads of the >= and <= operators. For example, `x + y
// >= 3`.
inline LowerBoundedLinearExpression(LinearExpression expression,
double lower_bound);
@@ -489,7 +489,7 @@ struct LowerBoundedLinearExpression {
// A LinearExpression with an upper bound.
struct UpperBoundedLinearExpression {
// Users are not expected to use this constructor. Instead they should build
// this object using the overloads of >= and <= operators. For example `x + y
// this object using overloads of the >= and <= operators. For example, `x + y
// <= 3`.
inline UpperBoundedLinearExpression(LinearExpression expression,
double upper_bound);
@@ -500,8 +500,8 @@ struct UpperBoundedLinearExpression {
// A LinearExpression with upper and lower bounds.
struct BoundedLinearExpression {
// Users are not expected to use this constructor. Instead they should build
// this object using the overloads of >= and <= operators. For example `3 <= x
// + y <= 3`.
// this object using overloads of the >=, <=, and == operators. For example,
// `3 <= x + y <= 3`.
inline BoundedLinearExpression(LinearExpression expression,
double lower_bound, double upper_bound);
// Users are not expected to use this constructor. This implicit conversion
@@ -1019,6 +1019,207 @@ inline QuadraticExpression operator*(QuadraticExpression lhs, double rhs);
inline QuadraticExpression operator/(QuadraticExpression lhs, double rhs);
// A QuadraticExpression with a lower bound.
struct LowerBoundedQuadraticExpression {
// Users are not expected to use this constructor. Instead, they should build
// this object using overloads of the >= and <= operators. For example, `x * y
// >= 3`.
inline LowerBoundedQuadraticExpression(QuadraticExpression expression,
double lower_bound);
// Users are not expected to explicitly use the following constructor.
inline LowerBoundedQuadraticExpression( // NOLINT
LowerBoundedLinearExpression lb_expression);
QuadraticExpression expression;
double lower_bound;
};
// A QuadraticExpression with an upper bound.
struct UpperBoundedQuadraticExpression {
// Users are not expected to use this constructor. Instead, they should build
// this object using overloads of the >= and <= operators. For example, `x * y
// <= 3`.
inline UpperBoundedQuadraticExpression(QuadraticExpression expression,
double upper_bound);
// Users are not expected to explicitly use the following constructor.
inline UpperBoundedQuadraticExpression( // NOLINT
UpperBoundedLinearExpression ub_expression);
QuadraticExpression expression;
double upper_bound;
};
// A QuadraticExpression with upper and lower bounds.
struct BoundedQuadraticExpression {
// Users are not expected to use this constructor. Instead, they should build
// this object using overloads of the >=, <=, and == operators. For example,
// `3 <= x * y <= 3`.
inline BoundedQuadraticExpression(QuadraticExpression expression,
double lower_bound, double upper_bound);
// Users are not expected to explicitly use the following constructors.
inline BoundedQuadraticExpression( // NOLINT
internal::VariablesEquality var_equality);
inline BoundedQuadraticExpression( // NOLINT
LowerBoundedLinearExpression lb_expression);
inline BoundedQuadraticExpression( // NOLINT
UpperBoundedLinearExpression ub_expression);
inline BoundedQuadraticExpression( // NOLINT
BoundedLinearExpression bounded_expression);
inline BoundedQuadraticExpression( // NOLINT
LowerBoundedQuadraticExpression lb_expression);
inline BoundedQuadraticExpression( // NOLINT
UpperBoundedQuadraticExpression ub_expression);
// Returns the actual lower_bound after taking into account the quadratic
// expression offset.
inline double lower_bound_minus_offset() const;
// Returns the actual upper_bound after taking into account the quadratic
// expression offset.
inline double upper_bound_minus_offset() const;
QuadraticExpression expression;
double lower_bound;
double upper_bound;
};
std::ostream& operator<<(std::ostream& ostr,
const BoundedQuadraticExpression& bounded_expression);
// We intentionally pass the QuadraticExpression argument by value so that we
// don't make unnecessary copies of temporary objects by using the move
// constructor and the returned values optimization (RVO).
inline LowerBoundedQuadraticExpression operator>=(QuadraticExpression lhs,
double rhs);
inline LowerBoundedQuadraticExpression operator>=(QuadraticTerm lhs,
double rhs);
inline LowerBoundedQuadraticExpression operator<=(double lhs,
QuadraticExpression rhs);
inline LowerBoundedQuadraticExpression operator<=(double lhs,
QuadraticTerm rhs);
inline UpperBoundedQuadraticExpression operator>=(double lhs,
QuadraticExpression rhs);
inline UpperBoundedQuadraticExpression operator>=(double lhs,
QuadraticTerm rhs);
inline UpperBoundedQuadraticExpression operator<=(QuadraticExpression lhs,
double rhs);
inline UpperBoundedQuadraticExpression operator<=(QuadraticTerm lhs,
double rhs);
// We intentionally pass the UpperBoundedQuadraticExpression and
// LowerBoundedQuadraticExpression arguments by value so that we don't
// make unnecessary copies of temporary objects by using the move constructor
// and the returned values optimization (RVO).
inline BoundedQuadraticExpression operator>=(
UpperBoundedQuadraticExpression lhs, double rhs);
inline BoundedQuadraticExpression operator>=(
double lhs, LowerBoundedQuadraticExpression rhs);
inline BoundedQuadraticExpression operator<=(
LowerBoundedQuadraticExpression lhs, double rhs);
inline BoundedQuadraticExpression operator<=(
double lhs, UpperBoundedQuadraticExpression rhs);
// We intentionally pass one QuadraticExpression argument by value so that we
// don't make unnecessary copies of temporary objects by using the move
// constructor and the returned values optimization (RVO).
// Comparisons with lhs = QuadraticExpression
inline BoundedQuadraticExpression operator>=(QuadraticExpression lhs,
const QuadraticExpression& rhs);
inline BoundedQuadraticExpression operator>=(QuadraticExpression lhs,
QuadraticTerm rhs);
inline BoundedQuadraticExpression operator>=(QuadraticExpression lhs,
const LinearExpression& rhs);
inline BoundedQuadraticExpression operator>=(QuadraticExpression lhs,
LinearTerm rhs);
inline BoundedQuadraticExpression operator>=(QuadraticExpression lhs,
Variable rhs);
inline BoundedQuadraticExpression operator<=(QuadraticExpression lhs,
const QuadraticExpression& rhs);
inline BoundedQuadraticExpression operator<=(QuadraticExpression lhs,
QuadraticTerm rhs);
inline BoundedQuadraticExpression operator<=(QuadraticExpression lhs,
const LinearExpression& rhs);
inline BoundedQuadraticExpression operator<=(QuadraticExpression lhs,
LinearTerm rhs);
inline BoundedQuadraticExpression operator<=(QuadraticExpression lhs,
Variable rhs);
inline BoundedQuadraticExpression operator==(QuadraticExpression lhs,
const QuadraticExpression& rhs);
inline BoundedQuadraticExpression operator==(QuadraticExpression lhs,
QuadraticTerm rhs);
inline BoundedQuadraticExpression operator==(QuadraticExpression lhs,
const LinearExpression& rhs);
inline BoundedQuadraticExpression operator==(QuadraticExpression lhs,
LinearTerm rhs);
inline BoundedQuadraticExpression operator==(QuadraticExpression lhs,
Variable rhs);
inline BoundedQuadraticExpression operator==(QuadraticExpression lhs,
double rhs);
// Comparisons with lhs = QuadraticTerm
inline BoundedQuadraticExpression operator>=(QuadraticTerm lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator>=(QuadraticTerm lhs,
QuadraticTerm rhs);
inline BoundedQuadraticExpression operator>=(QuadraticTerm lhs,
LinearExpression rhs);
inline BoundedQuadraticExpression operator>=(QuadraticTerm lhs, LinearTerm rhs);
inline BoundedQuadraticExpression operator>=(QuadraticTerm lhs, Variable rhs);
inline BoundedQuadraticExpression operator<=(QuadraticTerm lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator<=(QuadraticTerm lhs,
QuadraticTerm rhs);
inline BoundedQuadraticExpression operator<=(QuadraticTerm lhs,
LinearExpression rhs);
inline BoundedQuadraticExpression operator<=(QuadraticTerm lhs, LinearTerm rhs);
inline BoundedQuadraticExpression operator<=(QuadraticTerm lhs, Variable rhs);
inline BoundedQuadraticExpression operator==(QuadraticTerm lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator==(QuadraticTerm lhs,
QuadraticTerm rhs);
inline BoundedQuadraticExpression operator==(QuadraticTerm lhs,
LinearExpression rhs);
inline BoundedQuadraticExpression operator==(QuadraticTerm lhs, LinearTerm rhs);
inline BoundedQuadraticExpression operator==(QuadraticTerm lhs, Variable rhs);
inline BoundedQuadraticExpression operator==(QuadraticTerm lhs, double rhs);
// Comparisons with lhs = LinearExpression
inline BoundedQuadraticExpression operator>=(const LinearExpression& lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator>=(LinearExpression lhs,
QuadraticTerm rhs);
inline BoundedQuadraticExpression operator<=(const LinearExpression& lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator<=(LinearExpression lhs,
QuadraticTerm rhs);
inline BoundedQuadraticExpression operator==(const LinearExpression& lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator==(LinearExpression lhs,
QuadraticTerm rhs);
// Comparisons with lhs = LinearTerm
inline BoundedQuadraticExpression operator>=(LinearTerm lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator>=(LinearTerm lhs, QuadraticTerm rhs);
inline BoundedQuadraticExpression operator<=(LinearTerm lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator<=(LinearTerm lhs, QuadraticTerm rhs);
inline BoundedQuadraticExpression operator==(LinearTerm lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator==(LinearTerm lhs, QuadraticTerm rhs);
// Comparisons with lhs = Variable
inline BoundedQuadraticExpression operator>=(Variable lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator>=(Variable lhs, QuadraticTerm rhs);
inline BoundedQuadraticExpression operator<=(Variable lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator<=(Variable lhs, QuadraticTerm rhs);
inline BoundedQuadraticExpression operator==(Variable lhs,
QuadraticExpression rhs);
inline BoundedQuadraticExpression operator==(Variable lhs, QuadraticTerm rhs);
// Comparisons with lhs = Double
inline BoundedQuadraticExpression operator==(double lhs, QuadraticTerm rhs);
inline BoundedQuadraticExpression operator==(double lhs,
QuadraticExpression rhs);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Inline function implementations /////////////////////////////////////////////
@@ -2434,6 +2635,405 @@ QuadraticExpression QuadraticExpression::InnerProduct(
return result;
}
/////////////////////////////////////////////////////////////////////////////////
// LowerBoundedQuadraticExpression
// UpperBoundedQuadraticExpression
// BoundedQuadraticExpression
////////////////////////////////////////////////////////////////////////////////
LowerBoundedQuadraticExpression::LowerBoundedQuadraticExpression(
QuadraticExpression expression, const double lower_bound)
: expression(std::move(expression)), lower_bound(lower_bound) {}
LowerBoundedQuadraticExpression::LowerBoundedQuadraticExpression(
LowerBoundedLinearExpression lb_expression)
: expression(std::move(lb_expression.expression)),
lower_bound(lb_expression.lower_bound) {}
UpperBoundedQuadraticExpression::UpperBoundedQuadraticExpression(
QuadraticExpression expression, const double upper_bound)
: expression(std::move(expression)), upper_bound(upper_bound) {}
UpperBoundedQuadraticExpression::UpperBoundedQuadraticExpression(
UpperBoundedLinearExpression ub_expression)
: expression(std::move(ub_expression.expression)),
upper_bound(ub_expression.upper_bound) {}
BoundedQuadraticExpression::BoundedQuadraticExpression(
QuadraticExpression expression, const double lower_bound,
const double upper_bound)
: expression(std::move(expression)),
lower_bound(lower_bound),
upper_bound(upper_bound) {}
BoundedQuadraticExpression::BoundedQuadraticExpression(
internal::VariablesEquality var_equality)
: lower_bound(0), upper_bound(0) {
expression += var_equality.lhs;
expression -= var_equality.rhs;
}
BoundedQuadraticExpression::BoundedQuadraticExpression(
LowerBoundedLinearExpression lb_expression)
: expression(std::move(lb_expression.expression)),
lower_bound(lb_expression.lower_bound),
upper_bound(std::numeric_limits<double>::infinity()) {}
BoundedQuadraticExpression::BoundedQuadraticExpression(
UpperBoundedLinearExpression ub_expression)
: expression(std::move(ub_expression.expression)),
lower_bound(-std::numeric_limits<double>::infinity()),
upper_bound(ub_expression.upper_bound) {}
BoundedQuadraticExpression::BoundedQuadraticExpression(
BoundedLinearExpression bounded_expression)
: expression(std::move(bounded_expression.expression)),
lower_bound(bounded_expression.lower_bound),
upper_bound(bounded_expression.upper_bound) {}
BoundedQuadraticExpression::BoundedQuadraticExpression(
LowerBoundedQuadraticExpression lb_expression)
: expression(std::move(lb_expression.expression)),
lower_bound(lb_expression.lower_bound),
upper_bound(std::numeric_limits<double>::infinity()) {}
BoundedQuadraticExpression::BoundedQuadraticExpression(
UpperBoundedQuadraticExpression ub_expression)
: expression(std::move(ub_expression.expression)),
lower_bound(-std::numeric_limits<double>::infinity()),
upper_bound(ub_expression.upper_bound) {}
double BoundedQuadraticExpression::lower_bound_minus_offset() const {
return lower_bound - expression.offset();
}
double BoundedQuadraticExpression::upper_bound_minus_offset() const {
return upper_bound - expression.offset();
}
LowerBoundedQuadraticExpression operator>=(QuadraticExpression lhs,
const double rhs) {
return LowerBoundedQuadraticExpression(std::move(lhs), rhs);
}
LowerBoundedQuadraticExpression operator>=(const QuadraticTerm lhs,
const double rhs) {
return LowerBoundedQuadraticExpression(lhs, rhs);
}
LowerBoundedQuadraticExpression operator<=(const double lhs,
QuadraticExpression rhs) {
return LowerBoundedQuadraticExpression(std::move(rhs), lhs);
}
LowerBoundedQuadraticExpression operator<=(const double lhs,
const QuadraticTerm rhs) {
return LowerBoundedQuadraticExpression(rhs, lhs);
}
UpperBoundedQuadraticExpression operator>=(const double lhs,
QuadraticExpression rhs) {
return UpperBoundedQuadraticExpression(std::move(rhs), lhs);
}
UpperBoundedQuadraticExpression operator>=(const double lhs,
const QuadraticTerm rhs) {
return UpperBoundedQuadraticExpression(rhs, lhs);
}
UpperBoundedQuadraticExpression operator<=(QuadraticExpression lhs,
const double rhs) {
return UpperBoundedQuadraticExpression(std::move(lhs), rhs);
}
UpperBoundedQuadraticExpression operator<=(const QuadraticTerm lhs,
const double rhs) {
return UpperBoundedQuadraticExpression(lhs, rhs);
}
BoundedQuadraticExpression operator>=(UpperBoundedQuadraticExpression lhs,
const double rhs) {
return BoundedQuadraticExpression(std::move(lhs.expression), rhs,
lhs.upper_bound);
}
BoundedQuadraticExpression operator>=(const double lhs,
LowerBoundedQuadraticExpression rhs) {
return BoundedQuadraticExpression(std::move(rhs.expression), rhs.lower_bound,
lhs);
}
BoundedQuadraticExpression operator<=(LowerBoundedQuadraticExpression lhs,
const double rhs) {
return BoundedQuadraticExpression(std::move(lhs.expression), lhs.lower_bound,
rhs);
}
BoundedQuadraticExpression operator<=(const double lhs,
UpperBoundedQuadraticExpression rhs) {
return BoundedQuadraticExpression(std::move(rhs.expression), lhs,
rhs.upper_bound);
}
BoundedQuadraticExpression operator>=(QuadraticExpression lhs,
const QuadraticExpression& rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(std::move(lhs), 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator>=(QuadraticExpression lhs,
const QuadraticTerm rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(std::move(lhs), 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator>=(QuadraticExpression lhs,
const LinearExpression& rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(std::move(lhs), 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator>=(QuadraticExpression lhs,
const LinearTerm rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(std::move(lhs), 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator>=(QuadraticExpression lhs,
const Variable rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(std::move(lhs), 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator<=(QuadraticExpression lhs,
const QuadraticExpression& rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(
std::move(lhs), -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator<=(QuadraticExpression lhs,
const QuadraticTerm rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(
std::move(lhs), -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator<=(QuadraticExpression lhs,
const LinearExpression& rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(
std::move(lhs), -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator<=(QuadraticExpression lhs,
const LinearTerm rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(
std::move(lhs), -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator<=(QuadraticExpression lhs,
const Variable rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(
std::move(lhs), -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator==(QuadraticExpression lhs,
const QuadraticExpression& rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(std::move(lhs), 0, 0);
}
BoundedQuadraticExpression operator==(QuadraticExpression lhs,
const QuadraticTerm rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(std::move(lhs), 0, 0);
}
BoundedQuadraticExpression operator==(QuadraticExpression lhs,
const LinearExpression& rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(std::move(lhs), 0, 0);
}
BoundedQuadraticExpression operator==(QuadraticExpression lhs,
const LinearTerm rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(std::move(lhs), 0, 0);
}
BoundedQuadraticExpression operator==(QuadraticExpression lhs,
const Variable rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(std::move(lhs), 0, 0);
}
BoundedQuadraticExpression operator==(QuadraticExpression lhs,
const double rhs) {
lhs -= rhs;
return BoundedQuadraticExpression(std::move(lhs), 0, 0);
}
BoundedQuadraticExpression operator>=(const QuadraticTerm lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(
std::move(rhs), -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator>=(const QuadraticTerm lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(
rhs - lhs, -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator>=(const QuadraticTerm lhs,
LinearExpression rhs) {
return BoundedQuadraticExpression(
std::move(rhs) - lhs, -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator>=(const QuadraticTerm lhs,
const LinearTerm rhs) {
return BoundedQuadraticExpression(
rhs - lhs, -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator>=(const QuadraticTerm lhs,
const Variable rhs) {
return BoundedQuadraticExpression(
rhs - lhs, -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator<=(const QuadraticTerm lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(std::move(rhs), 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator<=(const QuadraticTerm lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator<=(const QuadraticTerm lhs,
LinearExpression rhs) {
return BoundedQuadraticExpression(std::move(rhs) - lhs, 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator<=(const QuadraticTerm lhs,
const LinearTerm rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator<=(const QuadraticTerm lhs,
const Variable rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator==(const QuadraticTerm lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(std::move(rhs), 0, 0);
}
BoundedQuadraticExpression operator==(const QuadraticTerm lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0, 0);
}
BoundedQuadraticExpression operator==(const QuadraticTerm lhs,
LinearExpression rhs) {
return BoundedQuadraticExpression(std::move(rhs) - lhs, 0, 0);
}
BoundedQuadraticExpression operator==(const QuadraticTerm lhs,
const LinearTerm rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0, 0);
}
BoundedQuadraticExpression operator==(const QuadraticTerm lhs,
const Variable rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0, 0);
}
BoundedQuadraticExpression operator==(const QuadraticTerm lhs,
const double rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0, 0);
}
BoundedQuadraticExpression operator>=(const LinearExpression& lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(
std::move(rhs), -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator>=(LinearExpression lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(
rhs - std::move(lhs), -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator<=(const LinearExpression& lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(std::move(rhs), 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator<=(LinearExpression lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(rhs - std::move(lhs), 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator==(const LinearExpression& lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(std::move(rhs), 0, 0);
}
BoundedQuadraticExpression operator==(LinearExpression lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(rhs - std::move(lhs), 0, 0);
}
// LinearTerm --
BoundedQuadraticExpression operator>=(const LinearTerm lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(
std::move(rhs), -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator>=(const LinearTerm lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(
rhs - lhs, -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator<=(const LinearTerm lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(std::move(rhs), 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator<=(const LinearTerm lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator==(const LinearTerm lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(std::move(rhs), 0, 0);
}
BoundedQuadraticExpression operator==(const LinearTerm lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0, 0);
}
// Variable --
BoundedQuadraticExpression operator>=(const Variable lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(
std::move(rhs), -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator>=(const Variable lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(
rhs - lhs, -std::numeric_limits<double>::infinity(), 0);
}
BoundedQuadraticExpression operator<=(const Variable lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(std::move(rhs), 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator<=(const Variable lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0,
std::numeric_limits<double>::infinity());
}
BoundedQuadraticExpression operator==(const Variable lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(std::move(rhs), 0, 0);
}
BoundedQuadraticExpression operator==(const Variable lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0, 0);
}
// Double --
BoundedQuadraticExpression operator==(const double lhs,
QuadraticExpression rhs) {
rhs -= lhs;
return BoundedQuadraticExpression(std::move(rhs), 0, 0);
}
BoundedQuadraticExpression operator==(const double lhs,
const QuadraticTerm rhs) {
return BoundedQuadraticExpression(rhs - lhs, 0, 0);
}
} // namespace math_opt
} // namespace operations_research

View File

@@ -50,10 +50,6 @@ absl::Status IsSupported(const MPModelProto& model) {
return absl::OkStatus();
}
absl::Status IsSupported(const math_opt::ModelProto& model) {
return ValidateModel(model);
}
bool AnyVarNamed(const MPModelProto& model) {
for (const MPVariableProto& var : model.variable()) {
if (var.name().length() > 0) {
@@ -205,7 +201,7 @@ MPModelProtoToMathOptModel(const ::operations_research::MPModelProto& model) {
absl::StatusOr<::operations_research::MPModelProto> MathOptModelToMPModelProto(
const ::operations_research::math_opt::ModelProto& model) {
RETURN_IF_ERROR(IsSupported(model));
RETURN_IF_ERROR(ValidateModel(model).status());
const bool vars_have_name = model.variables().names_size() > 0;
const bool constraints_have_name =

View File

@@ -232,6 +232,8 @@ enum LimitProto {
// All information regarding why a call to Solve() terminated.
message TerminationProto {
// Additional information in `limit` when value is TERMINATION_REASON_FEASIBLE
// or TERMINATION_REASON_NO_SOLUTION_FOUND, see `limit` for details.
TerminationReasonProto reason = 1;
// Is LIMIT_UNSPECIFIED unless reason is TERMINATION_REASON_FEASIBLE or

View File

@@ -17,6 +17,7 @@
#include <string_view>
#include <utility>
#include "ortools/base/logging.h"
#include "ortools/base/cleanup.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
@@ -180,7 +181,7 @@ absl::Status Gurobi::AddVars(const absl::Span<const int> vbegin,
const absl::Span<const char> vtype,
const absl::Span<const std::string> names) {
CHECK_EQ(vind.size(), vval.size());
const int num_vars = lb.size();
const int num_vars = static_cast<int>(lb.size());
CHECK_EQ(ub.size(), num_vars);
CHECK_EQ(vtype.size(), num_vars);
double* c_obj = nullptr;
@@ -220,7 +221,7 @@ absl::Status Gurobi::DelVars(const absl::Span<const int> ind) {
absl::Status Gurobi::AddConstrs(const absl::Span<const char> sense,
const absl::Span<const double> rhs,
const absl::Span<const std::string> names) {
const int num_cons = sense.size();
const int num_cons = static_cast<int>(sense.size());
CHECK_EQ(rhs.size(), num_cons);
char** c_names = nullptr;
std::vector<char*> c_names_data;
@@ -247,7 +248,7 @@ absl::Status Gurobi::DelConstrs(const absl::Span<const int> ind) {
absl::Status Gurobi::AddQpTerms(const absl::Span<const int> qrow,
const absl::Span<const int> qcol,
const absl::Span<const double> qval) {
const int numqnz = qrow.size();
const int numqnz = static_cast<int>(qrow.size());
CHECK_EQ(qcol.size(), numqnz);
CHECK_EQ(qval.size(), numqnz);
return ToStatus(GRBaddqpterms(
@@ -257,10 +258,43 @@ absl::Status Gurobi::AddQpTerms(const absl::Span<const int> qrow,
absl::Status Gurobi::DelQ() { return ToStatus(GRBdelq(gurobi_model_)); }
absl::Status Gurobi::AddQConstr(const absl::Span<const int> lind,
const absl::Span<const double> lval,
const absl::Span<const int> qrow,
const absl::Span<const int> qcol,
const absl::Span<const double> qval,
const char sense, const double rhs,
const std::string& name) {
const int numlnz = static_cast<int>(lind.size());
CHECK_EQ(lval.size(), numlnz);
const int numqlnz = static_cast<int>(qrow.size());
CHECK_EQ(qcol.size(), numqlnz);
CHECK_EQ(qval.size(), numqlnz);
return ToStatus(GRBaddqconstr(
/*model=*/gurobi_model_,
/*numlnz=*/numlnz,
/*lind=*/const_cast<int*>(lind.data()),
/*lval=*/const_cast<double*>(lval.data()),
/*numqlnz=*/numqlnz,
/*qrow=*/const_cast<int*>(qrow.data()),
/*qcol=*/const_cast<int*>(qcol.data()),
/*qval=*/const_cast<double*>(qval.data()),
/*sense=*/sense,
/*rhs=*/rhs,
/*constrname=*/const_cast<char*>(name.c_str())));
}
absl::Status Gurobi::DelQConstrs(const absl::Span<const int> ind) {
return ToStatus(GRBdelqconstrs(gurobi_model_, static_cast<int>(ind.size()),
const_cast<int*>(ind.data())));
}
absl::Status Gurobi::ChgCoeffs(const absl::Span<const int> cind,
const absl::Span<const int> vind,
const absl::Span<const double> val) {
const int num_changes = cind.size();
const int num_changes = static_cast<int>(cind.size());
CHECK_EQ(vind.size(), num_changes);
CHECK_EQ(val.size(), num_changes);
return ToStatus(GRBchgcoeffs(
@@ -451,7 +485,7 @@ absl::StatusOr<std::vector<char>> Gurobi::GetCharAttrArray(
absl::Status Gurobi::SetIntAttrList(const char* const name,
const absl::Span<const int> ind,
const absl::Span<const int> new_values) {
const int len = ind.size();
const int len = static_cast<int>(ind.size());
CHECK_EQ(new_values.size(), len);
return ToStatus(GRBsetintattrlist(gurobi_model_, name, len,
const_cast<int*>(ind.data()),
@@ -461,7 +495,7 @@ absl::Status Gurobi::SetIntAttrList(const char* const name,
absl::Status Gurobi::SetDoubleAttrList(
const char* const name, const absl::Span<const int> ind,
const absl::Span<const double> new_values) {
const int len = ind.size();
const int len = static_cast<int>(ind.size());
CHECK_EQ(new_values.size(), len);
return ToStatus(GRBsetdblattrlist(gurobi_model_, name, len,
const_cast<int*>(ind.data()),
@@ -471,7 +505,7 @@ absl::Status Gurobi::SetDoubleAttrList(
absl::Status Gurobi::SetCharAttrList(const char* const name,
const absl::Span<const int> ind,
const absl::Span<const char> new_values) {
const int len = ind.size();
const int len = static_cast<int>(ind.size());
CHECK_EQ(new_values.size(), len);
return ToStatus(GRBsetcharattrlist(gurobi_model_, name, len,
const_cast<int*>(ind.data()),
@@ -559,7 +593,7 @@ absl::StatusOr<std::string> Gurobi::CallbackContext::CbGetMessage() const {
absl::Status Gurobi::CallbackContext::CbCut(
const absl::Span<const int> cutind, const absl::Span<const double> cutval,
const char cutsense, const double cutrhs) const {
const int cut_len = cutind.size();
const int cut_len = static_cast<int>(cutind.size());
CHECK_EQ(cutval.size(), cut_len);
return gurobi_->ToStatus(
GRBcbcut(cb_data_, cut_len, const_cast<int*>(cutind.data()),
@@ -569,7 +603,7 @@ absl::Status Gurobi::CallbackContext::CbCut(
absl::Status Gurobi::CallbackContext::CbLazy(
const absl::Span<const int> lazyind, const absl::Span<const double> lazyval,
const char lazysense, const double lazyrhs) const {
const int lazy_len = lazyind.size();
const int lazy_len = static_cast<int>(lazyind.size());
CHECK_EQ(lazyval.size(), lazy_len);
return gurobi_->ToStatus(
GRBcblazy(cb_data_, lazy_len, const_cast<int*>(lazyind.data()),

View File

@@ -331,6 +331,23 @@ class Gurobi {
// Deletes all quadratic objective coefficients.
absl::Status DelQ();
// Calls GRBaddqconstr().
//
// Requirements:
// * lind and lval must be equal length.
// * qrow, qcol, and qval must be equal length.
absl::Status AddQConstr(absl::Span<const int> lind,
absl::Span<const double> lval,
absl::Span<const int> qrow,
absl::Span<const int> qcol,
absl::Span<const double> qval, char sense, double rhs,
const std::string& name);
// Calls GRBdelqconstrs().
//
// Deletes the specified quadratic constraints.
absl::Status DelQConstrs(const absl::Span<const int> ind);
//////////////////////////////////////////////////////////////////////////////
// Linear constraint matrix queries.
//////////////////////////////////////////////////////////////////////////////

View File

@@ -36,6 +36,7 @@
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/file.h"
#include "ortools/base/init_google.h"
#include "ortools/base/logging.h"
@@ -117,6 +118,9 @@ ABSL_FLAG(operations_research::math_opt::SolverType, solver_type,
operations_research::math_opt::AllSolversRegistry::Instance()
->RegisteredSolvers(),
", ", SolverTypeProtoFormatter())));
ABSL_FLAG(std::string, solve_parameters, "",
"SolveParameters in text-proto format. Note that the time limit is "
"overridden by the --time_limit flag.");
ABSL_FLAG(bool, solver_logs, false,
"use a message callback to print the solver convergence logs");
ABSL_FLAG(absl::Duration, time_limit, absl::InfiniteDuration(),
@@ -306,9 +310,16 @@ absl::Status RunSolver() {
}
// Solve the problem.
SolveParametersProto solve_parameters_proto;
QCHECK(google::protobuf::TextFormat::ParseFromString(
absl::GetFlag(FLAGS_solve_parameters), &solve_parameters_proto))
<< "Unable to parse --solve_parameters";
ASSIGN_OR_RETURN(const SolveParameters solve_parameters,
SolveParameters::FromProto(solve_parameters_proto));
SolveArguments solve_args = {
.parameters = {.time_limit = absl::GetFlag(FLAGS_time_limit)},
.parameters = solve_parameters,
};
solve_args.parameters.time_limit = absl::GetFlag(FLAGS_time_limit);
if (absl::GetFlag(FLAGS_solver_logs)) {
solve_args.message_callback = PrinterMessageCallback(std::cout, "logs| ");
}

View File

@@ -53,31 +53,12 @@ cc_library(
],
)
cc_library(
name = "name_validator",
srcs = ["name_validator.cc"],
hdrs = ["name_validator.h"],
deps = [
":sparse_vector_validator",
"//ortools/base",
"//ortools/base:map_util",
"//ortools/base:status_macros",
"//ortools/math_opt/core:model_summary",
"//ortools/math_opt/core:sparse_vector_view",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "model_validator",
srcs = ["model_validator.cc"],
hdrs = ["model_validator.h"],
deps = [
":ids_validator",
":name_validator",
":scalar_validator",
":sparse_matrix_validator",
":sparse_vector_validator",

View File

@@ -30,89 +30,7 @@
#include "ortools/base/status_macros.h"
#include "ortools/math_opt/core/model_summary.h"
namespace operations_research {
namespace math_opt {
namespace {
absl::Status CheckSortedIdsSubsetWithIndexOffset(
const absl::Span<const int64_t> ids,
const absl::Span<const int64_t> universe, const int64_t offset) {
int id_index = 0;
int universe_index = 0;
// NOTE(user): in the common case where ids and/or universe is consecutive,
// we can avoid iterating though the list and do interval based checks.
while (id_index < ids.size() && universe_index < universe.size()) {
if (universe[universe_index] < ids[id_index]) {
++universe_index;
} else if (universe[universe_index] == ids[id_index]) {
++id_index;
} else {
break;
}
}
if (id_index < ids.size()) {
return util::InvalidArgumentErrorBuilder()
<< "Bad id: " << ids[id_index] << " (at index: " << id_index + offset
<< ") found";
}
return absl::OkStatus();
}
// Given a sorted, strictly increasing set of ids, provides `contains()` to
// check if another id is in the set in O(1) time.
//
// Implementation note: when ids are consecutive, they are stored as a single
// interval [lb, ub), otherwise they are stored as a hash table of integers.
class FastIdCheck {
public:
// ids must be sorted with unique strictly increasing entries.
explicit FastIdCheck(const absl::Span<const int64_t> ids) {
if (ids.empty()) {
interval_mode_ = true;
} else if (ids.size() == ids.back() + 1 - ids.front()) {
interval_mode_ = true;
interval_lb_ = ids.front();
interval_ub_ = ids.back() + 1;
} else {
ids_ = absl::flat_hash_set<int64_t>(ids.begin(), ids.end());
}
}
bool contains(int64_t id) const {
if (interval_mode_) {
return id >= interval_lb_ && id < interval_ub_;
} else {
return ids_.contains(id);
}
}
private:
bool interval_mode_ = false;
int64_t interval_lb_ = 0;
int64_t interval_ub_ = 0;
absl::flat_hash_set<int64_t> ids_;
};
// Checks that the elements of ids and bad_list have no overlap.
//
// Assumed: ids and bad_list are sorted in increasing order, repeats allowed.
absl::Status CheckSortedIdsNotBad(const absl::Span<const int64_t> ids,
const absl::Span<const int64_t> bad_list) {
int id_index = 0;
int bad_index = 0;
while (id_index < ids.size() && bad_index < bad_list.size()) {
if (bad_list[bad_index] < ids[id_index]) {
++bad_index;
} else if (bad_list[bad_index] > ids[id_index]) {
++id_index;
} else {
return util::InvalidArgumentErrorBuilder()
<< "Bad id: " << ids[id_index] << " (at index: " << id_index
<< ") found";
}
}
return absl::OkStatus();
}
} // namespace
namespace operations_research::math_opt {
absl::Status CheckIdsRangeAndStrictlyIncreasing(absl::Span<const int64_t> ids) {
int64_t previous{-1};
@@ -133,95 +51,17 @@ absl::Status CheckIdsRangeAndStrictlyIncreasing(absl::Span<const int64_t> ids) {
return absl::OkStatus();
}
absl::Status CheckSortedIdsSubset(const absl::Span<const int64_t> ids,
const absl::Span<const int64_t> universe) {
RETURN_IF_ERROR(CheckSortedIdsSubsetWithIndexOffset(ids, universe, 0));
return absl::OkStatus();
}
absl::Status CheckUnsortedIdsSubset(const absl::Span<const int64_t> ids,
const absl::Span<const int64_t> universe) {
if (ids.empty()) {
return absl::OkStatus();
}
const FastIdCheck id_check(universe);
for (int i = 0; i < ids.size(); ++i) {
if (!id_check.contains(ids[i])) {
absl::Status CheckIdsSubset(absl::Span<const int64_t> ids,
const IdNameBiMap& universe,
std::optional<int64_t> upper_bound) {
for (const int64_t id : ids) {
if (upper_bound.has_value() && id >= *upper_bound) {
return util::InvalidArgumentErrorBuilder()
<< "Bad id: " << ids[i] << " (at index: " << i << ") not found";
<< "id " << id
<< " should be less than upper bound: " << *upper_bound;
}
}
return absl::OkStatus();
}
absl::Status IdUpdateValidator::IsValid() const {
for (int i = 0; i < deleted_ids_.size(); ++i) {
const int64_t deleted_id = deleted_ids_[i];
if (!old_ids_.HasId(deleted_id)) {
return util::InvalidArgumentErrorBuilder()
<< "Tried to delete id: " << deleted_id << " (at index: " << i
<< ") but it was not present";
}
}
if (!new_ids_.empty() && new_ids_.front() < old_ids_.next_free_id()) {
return util::InvalidArgumentErrorBuilder()
<< "All new ids should be greater or equal to the first unused id: "
<< old_ids_.next_free_id()
<< " but the first new id was: " << new_ids_.front();
}
return absl::OkStatus();
}
absl::Status IdUpdateValidator::CheckSortedIdsSubsetOfNotDeleted(
const absl::Span<const int64_t> ids) const {
RETURN_IF_ERROR(CheckSortedIdsNotBad(ids, deleted_ids_)) << " was deleted";
for (int i = 0; i < ids.size(); ++i) {
if (!old_ids_.HasId(ids[i])) {
return util::InvalidArgumentErrorBuilder()
<< "Bad id: " << ids[i] << " (at index: " << i << ") not found";
}
}
return absl::OkStatus();
}
absl::Status IdUpdateValidator::CheckSortedIdsSubsetOfFinal(
const absl::Span<const int64_t> ids) const {
// Implementation:
// * Partition ids into "old" and "new"
// * Check that the old ids are in old_ids_ but not deleted_ids_.
// * Check that the new ids are in new_ids_.
size_t split_point = ids.size();
if (!new_ids_.empty()) {
split_point = std::distance(
ids.begin(), std::lower_bound(ids.begin(), ids.end(), new_ids_[0]));
}
RETURN_IF_ERROR(
CheckSortedIdsSubsetOfNotDeleted(ids.subspan(0, split_point)));
RETURN_IF_ERROR(CheckSortedIdsSubsetWithIndexOffset(ids.subspan(split_point),
new_ids_, split_point));
return absl::OkStatus();
}
absl::Status IdUpdateValidator::CheckIdsSubsetOfFinal(
const absl::Span<const int64_t> ids) const {
if (ids.empty()) {
return absl::OkStatus();
}
const FastIdCheck deleted_fast(deleted_ids_);
const FastIdCheck new_fast(new_ids_);
for (int i = 0; i < ids.size(); ++i) {
const int64_t id = ids[i];
if (!new_ids_.empty() && id >= new_ids_[0]) {
if (!new_fast.contains(id)) {
return util::InvalidArgumentErrorBuilder()
<< "Bad id: " << id << " (at index: " << i << ") not found";
}
} else if (!old_ids_.HasId(id)) {
return util::InvalidArgumentErrorBuilder()
<< "Bad id: " << id << " (at index: " << i << ") not found";
} else if (deleted_fast.contains(id)) {
return util::InvalidArgumentErrorBuilder()
<< "Bad id: " << id << " (at index: " << i << ") was deleted";
if (!universe.HasId(id)) {
return util::InvalidArgumentErrorBuilder() << "id " << id << " not found";
}
}
return absl::OkStatus();
@@ -256,5 +96,4 @@ absl::Status CheckIdsIdentical(absl::Span<const int64_t> first_ids,
return absl::OkStatus();
}
} // namespace math_opt
} // namespace operations_research
} // namespace operations_research::math_opt

View File

@@ -24,22 +24,19 @@
namespace operations_research {
namespace math_opt {
// Checks that the input ids are in [0, max(int64_t)) range and that their are
// Checks that the input ids are in [0, max(int64_t)) range and that they are
// strictly increasing.
absl::Status CheckIdsRangeAndStrictlyIncreasing(absl::Span<const int64_t> ids);
// Checks that the elements of ids are a subset of universe.
// Checks that the elements of ids are a subset of universe. Elements of ids
// do not need to be sorted or distinct. If upper_bound is set, elements must be
// strictly less than upper_bound.
//
// Assumed: ids and universe are sorted in increasing order, repeats allowed.
absl::Status CheckSortedIdsSubset(const absl::Span<const int64_t> ids,
const absl::Span<const int64_t> universe);
// Checks that the elements of ids are a subset of universe.
//
// Assumed: universe are sorted in strictly increasing order (no repeats). No
// assumptions on ids.
absl::Status CheckUnsortedIdsSubset(const absl::Span<const int64_t> ids,
const absl::Span<const int64_t> universe);
// TODO(b/232526223): try merge this with the CheckIdsSubset overload below, or
// at least have one call the other.
absl::Status CheckIdsSubset(absl::Span<const int64_t> ids,
const IdNameBiMap& universe,
std::optional<int64_t> upper_bound = std::nullopt);
// Checks that the elements of ids are a subset of universe. Elements of ids
// do not need to be sorted or distinct.
@@ -54,50 +51,6 @@ absl::Status CheckIdsIdentical(absl::Span<const int64_t> first_ids,
absl::string_view first_description,
absl::string_view second_description);
// Provides a unified view of the id sets:
// * NOT_DELETED = old - deleted
// * FINAL = old - deleted + new
// so users can validate if a list of ids (sorted or unsorted) is a subset of
// either of the sets above.
//
// Implementation note: this class does not allocate by default, but some
// functions will allocate at most O(#deleted + #new).
class IdUpdateValidator {
public:
// deleted_ids and new_ids must be sorted with unique strictly increasing
// entries.
IdUpdateValidator(const IdNameBiMap& old_ids,
const absl::Span<const int64_t> deleted_ids,
const absl::Span<const int64_t> new_ids)
: old_ids_(old_ids), deleted_ids_(deleted_ids), new_ids_(new_ids) {}
// Returns true if the sets of ids passed to the constructor are valid.
absl::Status IsValid() const;
// Checks that ids is a subset of NOT_DELETED = old_ids_ - deleted_ids_.
//
// ids must be sorted in increasing order (repeats are allowed).
absl::Status CheckSortedIdsSubsetOfNotDeleted(
const absl::Span<const int64_t> ids) const;
// Checks that ids is a subset of FINAL = old_ids_ - deleted_ids_ + new_ids_.
//
// ids must be sorted in increasing order (repeats are allowed).
absl::Status CheckSortedIdsSubsetOfFinal(
const absl::Span<const int64_t> ids) const;
// Checks that ids is a subset of FINAL = old_ids_ - deleted_ids_ + new_ids_.
//
// If ids is sorted, prefer CheckSortedIdsSubsetOfFinal.
absl::Status CheckIdsSubsetOfFinal(const absl::Span<const int64_t> ids) const;
private:
// NOT OWNED
const IdNameBiMap& old_ids_;
const absl::Span<const int64_t> deleted_ids_;
const absl::Span<const int64_t> new_ids_;
};
} // namespace math_opt
} // namespace operations_research

View File

@@ -21,7 +21,6 @@
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/status_macros.h"
#include "ortools/math_opt/core/model_summary.h"
#include "ortools/math_opt/core/sparse_vector_view.h"
@@ -29,7 +28,6 @@
#include "ortools/math_opt/model_update.pb.h"
#include "ortools/math_opt/sparse_containers.pb.h"
#include "ortools/math_opt/validators/ids_validator.h"
#include "ortools/math_opt/validators/name_validator.h"
#include "ortools/math_opt/validators/scalar_validator.h"
#include "ortools/math_opt/validators/sparse_matrix_validator.h"
#include "ortools/math_opt/validators/sparse_vector_validator.h"
@@ -42,8 +40,7 @@ namespace {
// Submessages
////////////////////////////////////////////////////////////////////////////////
absl::Status VariablesValid(const VariablesProto& variables,
const bool check_names) {
absl::Status VariablesValid(const VariablesProto& variables) {
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(variables.ids()))
<< "Bad variable ids";
RETURN_IF_ERROR(
@@ -54,13 +51,12 @@ absl::Status VariablesValid(const VariablesProto& variables,
{.allow_negative_infinity = false}, "upper_bounds"));
RETURN_IF_ERROR(
CheckValues(MakeView(variables.ids(), variables.integers()), "integers"));
RETURN_IF_ERROR(CheckNameVector(MakeView(variables.ids(), variables.names()),
check_names));
return absl::OkStatus();
}
absl::Status VariableUpdatesValid(
const VariableUpdatesProto& variable_updates) {
absl::Status VariableUpdatesValid(const VariableUpdatesProto& variable_updates,
const IdNameBiMap& variable_ids,
const int64_t old_var_id_ub) {
RETURN_IF_ERROR(CheckIdsAndValues(MakeView(variable_updates.lower_bounds()),
{.allow_positive_infinity = false}))
<< "Bad lower bounds";
@@ -69,26 +65,20 @@ absl::Status VariableUpdatesValid(
<< "Bad upper bounds";
RETURN_IF_ERROR(CheckIdsAndValues(MakeView(variable_updates.integers())))
<< "Bad integers";
return absl::OkStatus();
}
absl::Status VariableUpdatesValidForState(
const VariableUpdatesProto& variable_updates,
const IdUpdateValidator& id_validator) {
RETURN_IF_ERROR(id_validator.CheckSortedIdsSubsetOfNotDeleted(
variable_updates.lower_bounds().ids()))
RETURN_IF_ERROR(CheckIdsSubset(variable_updates.lower_bounds().ids(),
variable_ids, old_var_id_ub))
<< "lower bound update on invalid variable id";
RETURN_IF_ERROR(id_validator.CheckSortedIdsSubsetOfNotDeleted(
variable_updates.upper_bounds().ids()))
RETURN_IF_ERROR(CheckIdsSubset(variable_updates.upper_bounds().ids(),
variable_ids, old_var_id_ub))
<< "upper bound update on invalid variable id";
RETURN_IF_ERROR(id_validator.CheckSortedIdsSubsetOfNotDeleted(
variable_updates.integers().ids()))
RETURN_IF_ERROR(CheckIdsSubset(variable_updates.integers().ids(),
variable_ids, old_var_id_ub))
<< "integer update on invalid variable id";
return absl::OkStatus();
}
absl::Status ObjectiveValid(const ObjectiveProto& objective,
absl::Span<const int64_t> variable_ids) {
const IdNameBiMap& variable_ids) {
// 1. Validate offset
RETURN_IF_ERROR(CheckScalarNoNanNoInf(objective.offset()))
<< "Objective offset invalid";
@@ -98,7 +88,7 @@ absl::Status ObjectiveValid(const ObjectiveProto& objective,
linear_coefficients,
{.allow_positive_infinity = false, .allow_negative_infinity = false}))
<< "Linear objective coefficients bad";
RETURN_IF_ERROR(CheckSortedIdsSubset(linear_coefficients.ids(), variable_ids))
RETURN_IF_ERROR(CheckIdsSubset(linear_coefficients.ids(), variable_ids))
<< "Objective.linear_coefficients.ids not found in Variables.ids";
// 3. Validate quadratic terms
RETURN_IF_ERROR(SparseMatrixValid(objective.quadratic_coefficients(),
@@ -112,7 +102,8 @@ absl::Status ObjectiveValid(const ObjectiveProto& objective,
// NOTE: This method does not check requirements on the IDs
absl::Status ObjectiveUpdatesValid(
const ObjectiveUpdatesProto& objective_updates) {
const ObjectiveUpdatesProto& objective_updates,
const IdNameBiMap& variable_ids) {
// 1. Validate offset
RETURN_IF_ERROR(CheckScalarNoNanNoInf(objective_updates.offset_update()))
<< "Offset update invalid";
@@ -120,31 +111,22 @@ absl::Status ObjectiveUpdatesValid(
RETURN_IF_ERROR(CheckIdsAndValues(
MakeView(objective_updates.linear_coefficients()),
{.allow_positive_infinity = false, .allow_negative_infinity = false}))
<< "Linear objective coefficients bad";
<< "Linear objective coefficients invalid";
// 3. Validate quadratic terms
RETURN_IF_ERROR(SparseMatrixValid(objective_updates.quadratic_coefficients(),
/*enforce_upper_triangular=*/true))
<< "Objective.quadratic_coefficients invalid";
return absl::OkStatus();
}
absl::Status ObjectiveUpdatesValidForModel(
const ObjectiveUpdatesProto& objective_updates,
const IdUpdateValidator& id_validator) {
RETURN_IF_ERROR(id_validator.CheckSortedIdsSubsetOfFinal(
objective_updates.linear_coefficients().ids()))
RETURN_IF_ERROR(CheckIdsSubset(objective_updates.linear_coefficients().ids(),
variable_ids))
<< "Linear coefficients ids not found in variable ids";
RETURN_IF_ERROR(id_validator.CheckSortedIdsSubsetOfFinal(
objective_updates.quadratic_coefficients().row_ids()))
<< "Quadratic coefficient ids bad";
RETURN_IF_ERROR(id_validator.CheckIdsSubsetOfFinal(
objective_updates.quadratic_coefficients().column_ids()))
<< "Quadratic coefficient ids bad";
RETURN_IF_ERROR(SparseMatrixIdsAreKnown(
objective_updates.quadratic_coefficients(), variable_ids, variable_ids))
<< "quadratic_coefficients invalid";
return absl::OkStatus();
}
absl::Status LinearConstraintsValid(
const LinearConstraintsProto& linear_constraints, const bool check_names) {
const LinearConstraintsProto& linear_constraints) {
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(linear_constraints.ids()))
<< "Bad linear constraint ids";
RETURN_IF_ERROR(CheckValues(
@@ -153,14 +135,12 @@ absl::Status LinearConstraintsValid(
RETURN_IF_ERROR(CheckValues(
MakeView(linear_constraints.ids(), linear_constraints.upper_bounds()),
{.allow_negative_infinity = false}, "upper_bounds"));
RETURN_IF_ERROR(CheckNameVector(
MakeView(linear_constraints.ids(), linear_constraints.names()),
check_names));
return absl::OkStatus();
}
absl::Status LinearConstraintUpdatesValid(
const LinearConstraintUpdatesProto& linear_constraint_updates) {
const LinearConstraintUpdatesProto& linear_constraint_updates,
const IdNameBiMap& linear_constraint_ids, const int64_t old_lin_con_id_ub) {
RETURN_IF_ERROR(
CheckIdsAndValues(MakeView(linear_constraint_updates.lower_bounds()),
{.allow_positive_infinity = false}))
@@ -169,30 +149,21 @@ absl::Status LinearConstraintUpdatesValid(
CheckIdsAndValues(MakeView(linear_constraint_updates.upper_bounds()),
{.allow_negative_infinity = false}))
<< "Bad upper bounds";
return absl::OkStatus();
}
absl::Status LinearConstraintUpdatesValidForState(
const LinearConstraintUpdatesProto& linear_constraint_updates,
const IdUpdateValidator& id_validator) {
RETURN_IF_ERROR(id_validator.CheckSortedIdsSubsetOfNotDeleted(
linear_constraint_updates.lower_bounds().ids()))
RETURN_IF_ERROR(CheckIdsSubset(linear_constraint_updates.lower_bounds().ids(),
linear_constraint_ids, old_lin_con_id_ub))
<< "lower bound update on invalid linear constraint id";
RETURN_IF_ERROR(id_validator.CheckSortedIdsSubsetOfNotDeleted(
linear_constraint_updates.upper_bounds().ids()))
RETURN_IF_ERROR(CheckIdsSubset(linear_constraint_updates.upper_bounds().ids(),
linear_constraint_ids, old_lin_con_id_ub))
<< "upper bound update on invalid linear constraint id";
return absl::OkStatus();
}
absl::Status LinearConstraintMatrixIdsValidForUpdate(
const SparseDoubleMatrixProto& matrix,
const IdUpdateValidator& linear_constraint_id_validator,
const IdUpdateValidator& variable_id_validator) {
RETURN_IF_ERROR(linear_constraint_id_validator.CheckSortedIdsSubsetOfFinal(
matrix.row_ids()))
const IdNameBiMap& linear_constraint_ids, const IdNameBiMap& variable_ids) {
RETURN_IF_ERROR(CheckIdsSubset(matrix.row_ids(), linear_constraint_ids))
<< "Unknown linear_constraint_id";
RETURN_IF_ERROR(
variable_id_validator.CheckIdsSubsetOfFinal(matrix.column_ids()))
RETURN_IF_ERROR(CheckIdsSubset(matrix.column_ids(), variable_ids))
<< "Unknown variable_id";
return absl::OkStatus();
}
@@ -203,21 +174,23 @@ absl::Status LinearConstraintMatrixIdsValidForUpdate(
// Model
// /////////////////////////////////////////////////////////////////////////////
absl::Status ValidateModel(const ModelProto& model, const bool check_names) {
RETURN_IF_ERROR(VariablesValid(model.variables(), check_names))
absl::StatusOr<ModelSummary> ValidateModel(const ModelProto& model,
const bool check_names) {
ASSIGN_OR_RETURN(const auto model_summary,
ModelSummary::Create(model, check_names));
RETURN_IF_ERROR(VariablesValid(model.variables()))
<< "Model.variables are invalid.";
RETURN_IF_ERROR(ObjectiveValid(model.objective(), model.variables().ids()))
RETURN_IF_ERROR(ObjectiveValid(model.objective(), model_summary.variables))
<< "Model.objective is invalid";
RETURN_IF_ERROR(
LinearConstraintsValid(model.linear_constraints(), check_names))
RETURN_IF_ERROR(LinearConstraintsValid(model.linear_constraints()))
<< "Model.linear_constraints are invalid";
RETURN_IF_ERROR(SparseMatrixValid(model.linear_constraint_matrix()))
<< "Model.linear_constraint_matrix invalid";
RETURN_IF_ERROR(SparseMatrixIdsAreKnown(model.linear_constraint_matrix(),
model.linear_constraints().ids(),
model.variables().ids()))
model_summary.linear_constraints,
model_summary.variables))
<< "Model.linear_constraint_matrix ids are inconsistent";
return absl::OkStatus();
return model_summary;
}
////////////////////////////////////////////////////////////////////////////////
@@ -225,77 +198,38 @@ absl::Status ValidateModel(const ModelProto& model, const bool check_names) {
////////////////////////////////////////////////////////////////////////////////
absl::Status ValidateModelUpdate(const ModelUpdateProto& model_update,
const bool check_names) {
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(
model_update.deleted_linear_constraint_ids()))
<< "ModelUpdateProto.deleted_linear_constraint_ids invalid";
RETURN_IF_ERROR(
CheckIdsRangeAndStrictlyIncreasing(model_update.deleted_variable_ids()))
<< "ModelUpdateProto.deleted_variable_ids invalid";
RETURN_IF_ERROR(VariableUpdatesValid(model_update.variable_updates()))
ModelSummary& model_summary) {
RETURN_IF_ERROR(model_summary.Update(model_update));
const int64_t old_var_id_ub = model_update.new_variables().ids_size() > 0
? model_update.new_variables().ids(0)
: model_summary.variables.next_free_id();
const int64_t old_lin_con_id_ub =
model_update.new_linear_constraints().ids_size() > 0
? model_update.new_linear_constraints().ids(0)
: model_summary.linear_constraints.next_free_id();
RETURN_IF_ERROR(VariableUpdatesValid(model_update.variable_updates(),
model_summary.variables, old_var_id_ub))
<< "ModelUpdateProto.variable_updates invalid";
RETURN_IF_ERROR(
LinearConstraintUpdatesValid(model_update.linear_constraint_updates()))
RETURN_IF_ERROR(LinearConstraintUpdatesValid(
model_update.linear_constraint_updates(),
model_summary.linear_constraints, old_lin_con_id_ub))
<< "ModelUpdateProto.linear_constraint_updates invalid";
RETURN_IF_ERROR(VariablesValid(model_update.new_variables(), check_names))
RETURN_IF_ERROR(VariablesValid(model_update.new_variables()))
<< "ModelUpdateProto.new_variables invalid";
RETURN_IF_ERROR(LinearConstraintsValid(model_update.new_linear_constraints(),
check_names))
RETURN_IF_ERROR(LinearConstraintsValid(model_update.new_linear_constraints()))
<< "ModelUpdateProto.new_linear_constraints invalid";
RETURN_IF_ERROR(ObjectiveUpdatesValid(model_update.objective_updates()))
RETURN_IF_ERROR(ObjectiveUpdatesValid(model_update.objective_updates(),
model_summary.variables))
<< "ModelUpdateProto.objective_update invalid";
RETURN_IF_ERROR(
SparseMatrixValid(model_update.linear_constraint_matrix_updates()))
<< "Model.linear_constraint_matrix_updates invalid";
return absl::OkStatus();
}
absl::Status ValidateModelUpdateAndSummary(const ModelUpdateProto& model_update,
const ModelSummary& model_summary,
const bool check_names) {
RETURN_IF_ERROR(ValidateModelUpdate(model_update));
const IdUpdateValidator variable_id_validator(
model_summary.variables, model_update.deleted_variable_ids(),
model_update.new_variables().ids());
RETURN_IF_ERROR(variable_id_validator.IsValid())
<< "Invalid new or deleted variable id";
const IdUpdateValidator linear_constraint_id_validator(
model_summary.linear_constraints,
model_update.deleted_linear_constraint_ids(),
model_update.new_linear_constraints().ids());
RETURN_IF_ERROR(linear_constraint_id_validator.IsValid())
<< "Invalid new or deleted linear constraint id";
RETURN_IF_ERROR(VariableUpdatesValidForState(model_update.variable_updates(),
variable_id_validator))
<< "Invalid variable update";
RETURN_IF_ERROR(LinearConstraintUpdatesValidForState(
model_update.linear_constraint_updates(), linear_constraint_id_validator))
<< "Invalid linear constraint update";
RETURN_IF_ERROR(ObjectiveUpdatesValidForModel(
model_update.objective_updates(), variable_id_validator))
<< "Invalid objective update";
RETURN_IF_ERROR(LinearConstraintMatrixIdsValidForUpdate(
model_update.linear_constraint_matrix_updates(),
linear_constraint_id_validator, variable_id_validator))
<< "Invalid linear constraint matrix update";
if (check_names && !model_update.new_variables().names().empty()) {
RETURN_IF_ERROR(
CheckNewNames(model_summary.variables,
MakeView(model_update.new_variables().ids(),
model_update.new_variables().names())))
<< "Bad new variable names";
}
model_summary.linear_constraints, model_summary.variables))
<< "invalid linear constraint matrix update";
if (check_names && !model_update.new_linear_constraints().names().empty()) {
RETURN_IF_ERROR(
CheckNewNames(model_summary.linear_constraints,
MakeView(model_update.new_linear_constraints().ids(),
model_update.new_linear_constraints().names())))
<< "Bad new linear constraint names";
}
return absl::OkStatus();
}

View File

@@ -24,33 +24,19 @@ namespace math_opt {
// Runs in O(size of model) and allocates O(#variables + #linear constraints)
// memory.
absl::Status ValidateModel(const ModelProto& model, bool check_names = true);
absl::StatusOr<ModelSummary> ValidateModel(const ModelProto& model,
bool check_names = true);
// Validates the update as-is; without any knowledge of the model or previous
// updates. Some tests of the validity of ids are also not done.
//
// Performance: runs in O(size of update).
//
// See ValidateModelUpdateAndSummary() for a version that does a full
// validation taking into account the model and previous updates.
absl::Status ValidateModelUpdate(const ModelUpdateProto& model_update,
bool check_names = true);
// Validates the update taking into account the model and previous updates (via
// the provided summary).
//
// Note that this function uses model_summary.(variables|linear_constraints)'s
// next_free_id() to test that new variables/constraints ids are valid.
// Validates the update is consistent both internally and with current model (as
// given by model_summary) and updates the model_summary.
//
// Performance: runs in O(size of update), allocates at most
// O(#new or deleted variables + #new or deleted linear constraints).
//
// It internally calls ValidateModelUpdate() which validates all predicates that
// can be validated without knowledge of the initial model and the previous
// updates.
absl::Status ValidateModelUpdateAndSummary(const ModelUpdateProto& model_update,
const ModelSummary& model_summary,
bool check_names = true);
// If the function returns an error, no guarantees are made on the state of
// model_summary.
absl::Status ValidateModelUpdate(const ModelUpdateProto& model_update,
ModelSummary& model_summary);
} // namespace math_opt
} // namespace operations_research

View File

@@ -1,76 +0,0 @@
// Copyright 2010-2021 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/validators/name_validator.h"
#include <cstdint>
#include <string>
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/map_util.h"
#include "ortools/base/status_macros.h"
#include "ortools/math_opt/core/model_summary.h"
#include "ortools/math_opt/core/sparse_vector_view.h"
#include "ortools/math_opt/validators/sparse_vector_validator.h"
namespace operations_research {
namespace math_opt {
absl::Status CheckNameVector(
const SparseVectorView<const std::string*>& name_vector,
const bool check_unique) {
if (name_vector.values().empty()) {
// Names are optional.
return absl::OkStatus();
}
RETURN_IF_ERROR(CheckIdsAndValuesSize(name_vector, "names"));
absl::flat_hash_map<absl::string_view, int64_t> used_variable_names;
if (check_unique) {
for (const auto [id, name_pointer] : name_vector) {
const std::string& name = *name_pointer;
if (!name.empty()) {
if (!gtl::InsertIfNotPresent(&used_variable_names, {name, id})) {
return absl::InvalidArgumentError(
absl::StrCat("Found name: ", name, " twice, for ids ", id,
" and ", used_variable_names.at(name)));
}
}
}
}
return absl::OkStatus();
}
absl::Status CheckNewNames(
const IdNameBiMap& old_names,
const SparseVectorView<const std::string*>& new_names) {
if (old_names.Empty()) {
return absl::OkStatus();
}
for (const auto [id, name_pointer] : new_names) {
const std::string& new_name = *name_pointer;
if (!new_name.empty() && old_names.HasName(new_name)) {
return absl::InvalidArgumentError(
absl::StrCat("Found name: ", new_name, " twice, for ids ", id,
" and ", old_names.nonempty_name_to_id().at(new_name)));
}
}
return absl::OkStatus();
}
} // namespace math_opt
} // namespace operations_research

View File

@@ -1,42 +0,0 @@
// Copyright 2010-2021 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_VALIDATORS_NAME_VALIDATOR_H_
#define OR_TOOLS_MATH_OPT_VALIDATORS_NAME_VALIDATOR_H_
#include <string>
#include "absl/status/status.h"
#include "ortools/math_opt/core/model_summary.h"
#include "ortools/math_opt/core/sparse_vector_view.h"
namespace operations_research {
namespace math_opt {
// Checks basic validity of name_vector view: i.e. ids_size() = values_size().
// In addition, if check_unique is set to true, the function checks that every
// name that is not "" is distinct.
absl::Status CheckNameVector(
const SparseVectorView<const std::string*>& name_vector, bool check_unique);
// Checks new_names are compatible with old_names: i.e. new_names does not
// duplicate names in old_names. Assumes basic validity of new_names view and
// does not check for duplicates within old_names or new_names.
absl::Status CheckNewNames(
const IdNameBiMap& old_names,
const SparseVectorView<const std::string*>& new_names);
} // namespace math_opt
} // namespace operations_research
#endif // OR_TOOLS_MATH_OPT_VALIDATORS_NAME_VALIDATOR_H_

View File

@@ -31,33 +31,6 @@ namespace operations_research {
namespace math_opt {
namespace {
absl::Status ValidateTermination(const TerminationProto& termination) {
if (termination.reason() == TERMINATION_REASON_UNSPECIFIED) {
return absl::InvalidArgumentError("termination reason must be specified");
}
if (termination.reason() == TERMINATION_REASON_FEASIBLE ||
termination.reason() == TERMINATION_REASON_NO_SOLUTION_FOUND) {
if (termination.limit() == LIMIT_UNSPECIFIED) {
return absl::InvalidArgumentError(
absl::StrCat("for reason ", ProtoEnumToString(termination.reason()),
", limit must be specified"));
}
if (termination.limit() == LIMIT_CUTOFF &&
termination.reason() == TERMINATION_REASON_FEASIBLE) {
return absl::InvalidArgumentError(
"For LIMIT_CUTOFF expected no solutions");
}
} else {
if (termination.limit() != LIMIT_UNSPECIFIED) {
return absl::InvalidArgumentError(
absl::StrCat("for reason:", ProtoEnumToString(termination.reason()),
", limit should be unspecified, but was set to: ",
ProtoEnumToString(termination.limit())));
}
}
return absl::OkStatus();
}
bool HasPrimalFeasibleSolution(const SolutionProto& solution) {
return solution.has_primal_solution() &&
solution.primal_solution().feasibility_status() ==
@@ -146,6 +119,33 @@ absl::Status RequireNoDualFeasibleSolution(const SolveResultProto& result) {
}
} // namespace
absl::Status ValidateTermination(const TerminationProto& termination) {
if (termination.reason() == TERMINATION_REASON_UNSPECIFIED) {
return absl::InvalidArgumentError("termination reason must be specified");
}
if (termination.reason() == TERMINATION_REASON_FEASIBLE ||
termination.reason() == TERMINATION_REASON_NO_SOLUTION_FOUND) {
if (termination.limit() == LIMIT_UNSPECIFIED) {
return absl::InvalidArgumentError(
absl::StrCat("for reason ", ProtoEnumToString(termination.reason()),
", limit must be specified"));
}
if (termination.limit() == LIMIT_CUTOFF &&
termination.reason() == TERMINATION_REASON_FEASIBLE) {
return absl::InvalidArgumentError(
"For LIMIT_CUTOFF expected no solutions");
}
} else {
if (termination.limit() != LIMIT_UNSPECIFIED) {
return absl::InvalidArgumentError(
absl::StrCat("for reason:", ProtoEnumToString(termination.reason()),
", limit should be unspecified, but was set to: ",
ProtoEnumToString(termination.limit())));
}
}
return absl::OkStatus();
}
absl::Status CheckHasPrimalSolution(const SolveResultProto& result) {
if (!HasPrimalFeasibleSolution(result)) {
return absl::InvalidArgumentError(

View File

@@ -22,6 +22,13 @@
namespace operations_research {
namespace math_opt {
// Checks that:
// * termination.reason is not UNSPECIFIED,
// * termination.limit is set (not UNSPECIFIED) iff termination.reason is
// either FEASIBLE or NO_SOLUTION_FOUND,
// * termination.limit is not CUTOFF when termination.reason is FEASIBLE.
absl::Status ValidateTermination(const TerminationProto& termination);
// Validates the input result.
absl::Status ValidateResult(const SolveResultProto& result,
const ModelSolveParametersProto& parameters,

View File

@@ -88,13 +88,12 @@ absl::Status SparseMatrixValid(const SparseDoubleMatrixProto& matrix,
return absl::OkStatus();
}
absl::Status SparseMatrixIdsAreKnown(
const SparseDoubleMatrixProto& matrix,
const absl::Span<const int64_t> row_ids,
const absl::Span<const int64_t> column_ids) {
RETURN_IF_ERROR(CheckSortedIdsSubset(matrix.row_ids(), row_ids))
absl::Status SparseMatrixIdsAreKnown(const SparseDoubleMatrixProto& matrix,
const IdNameBiMap& row_ids,
const IdNameBiMap& column_ids) {
RETURN_IF_ERROR(CheckIdsSubset(matrix.row_ids(), row_ids))
<< "Unknown row_id";
RETURN_IF_ERROR(CheckUnsortedIdsSubset(matrix.column_ids(), column_ids))
RETURN_IF_ERROR(CheckIdsSubset(matrix.column_ids(), column_ids))
<< "Unknown column_id";
return absl::OkStatus();
}

View File

@@ -18,6 +18,7 @@
#include "absl/status/status.h"
#include "absl/types/span.h"
#include "ortools/math_opt/core/model_summary.h"
#include "ortools/math_opt/model.pb.h"
namespace operations_research::math_opt {
@@ -36,8 +37,8 @@ absl::Status SparseMatrixValid(const SparseDoubleMatrixProto& matrix,
// 1. matrix.row_ids is a subset of row_ids.
// 2. matrix.column_ids is a subset of column_ids.
absl::Status SparseMatrixIdsAreKnown(const SparseDoubleMatrixProto& matrix,
absl::Span<const int64_t> row_ids,
absl::Span<const int64_t> column_ids);
const IdNameBiMap& row_ids,
const IdNameBiMap& column_ids);
} // namespace operations_research::math_opt