math_opt: sync from google3

This commit is contained in:
Corentin Le Molgat
2022-05-25 17:24:29 +02:00
parent 710f01a783
commit aba4790b18
47 changed files with 1203 additions and 500 deletions

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//ortools/math_opt:__subpackages__"])
exports_files(
[
"BUILD.bazel",
] + glob([
"*.cc",
"*.h",
"*.proto",
]),
visibility = [
"//ortools/open_source:__subpackages__",
],
)
cc_library(
name = "validator",
srcs = ["validator.cc"],
hdrs = ["validator.h"],
deps = [
"@com_google_absl//absl/status",
"//ortools/math_opt:model_cc_proto",
"//ortools/math_opt:sparse_containers_cc_proto",
"//ortools/math_opt/core:model_summary",
"//ortools/math_opt/validators:linear_expression_validator",
"//ortools/math_opt/validators:scalar_validator",
],
)

View File

@@ -0,0 +1,45 @@
// 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/constraints/sos/validator.h"
#include "absl/status/status.h"
#include "ortools/math_opt/core/model_summary.h"
#include "ortools/math_opt/model.pb.h"
#include "ortools/math_opt/sparse_containers.pb.h"
#include "ortools/math_opt/validators/linear_expression_validator.h"
#include "ortools/math_opt/validators/scalar_validator.h"
#include "ortools/base/status_macros.h"
namespace operations_research::math_opt {
absl::Status ValidateConstraint(const SosConstraintProto& constraint,
const IdNameBiMap& variable_universe) {
if (!constraint.weights().empty() &&
constraint.weights_size() != constraint.expressions_size()) {
return util::InvalidArgumentErrorBuilder()
<< "Length mismatch between weights and expressions: "
<< constraint.weights_size() << " vs. "
<< constraint.expressions_size();
}
for (const LinearExpressionProto& expression : constraint.expressions()) {
RETURN_IF_ERROR(ValidateLinearExpression(expression, variable_universe))
<< "Invalid SOS expression";
}
for (const double weight : constraint.weights()) {
RETURN_IF_ERROR(CheckScalarNoNanNoInf(weight)) << "Invalid SOS weight";
}
return absl::OkStatus();
}
} // namespace operations_research::math_opt

View File

@@ -0,0 +1,28 @@
// 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_CONSTRAINTS_SOS_VALIDATOR_H_
#define OR_TOOLS_MATH_OPT_CONSTRAINTS_SOS_VALIDATOR_H_
#include "absl/status/status.h"
#include "ortools/math_opt/core/model_summary.h"
#include "ortools/math_opt/model.pb.h"
namespace operations_research::math_opt {
absl::Status ValidateConstraint(const SosConstraintProto& constraint,
const IdNameBiMap& variable_universe);
} // namespace operations_research::math_opt
#endif // OR_TOOLS_MATH_OPT_CONSTRAINTS_SOS_VALIDATOR_H_

View File

@@ -49,34 +49,6 @@ cc_library(
],
)
cc_library(
name = "model_storage",
srcs = ["model_storage.cc"],
hdrs = ["model_storage.h"],
deps = [
":model_update_merge",
":sparse_vector_view",
"//ortools/base",
"//ortools/base:intops",
"//ortools/base:map_util",
"//ortools/math_opt:model_cc_proto",
"//ortools/math_opt:model_update_cc_proto",
"//ortools/math_opt:result_cc_proto",
"//ortools/math_opt:solution_cc_proto",
"//ortools/math_opt:sparse_containers_cc_proto",
"//ortools/math_opt/validators:model_validator",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/meta:type_traits",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "solver_interface",
srcs = ["solver_interface.cc"],
@@ -133,21 +105,6 @@ cc_library(
],
)
cc_library(
name = "model_update_merge",
srcs = ["model_update_merge.cc"],
hdrs = ["model_update_merge.h"],
deps = [
":sparse_vector_view",
"//ortools/base",
"//ortools/base:protobuf_util",
"//ortools/math_opt:model_cc_proto",
"//ortools/math_opt:model_update_cc_proto",
"//ortools/math_opt:sparse_containers_cc_proto",
"@com_google_absl//absl/container:flat_hash_set",
],
)
cc_library(
name = "solve_interrupter",
srcs = ["solve_interrupter.cc"],

View File

@@ -15,18 +15,27 @@
#include <cstdint>
#include <initializer_list>
#include <limits>
#include <optional>
#include <string>
#include <utility>
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "ortools/base/linked_hash_map.h"
#include "ortools/base/logging.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 {
namespace {
// TODO(b/232526223): this is an exact copy of
// CheckIdsRangeAndStrictlyIncreasing from ids_validator.h, find a way to share
// the code.
namespace internal {
absl::Status CheckIdsRangeAndStrictlyIncreasing2(
absl::Span<const int64_t> ids) {
int64_t previous{-1};
@@ -47,7 +56,7 @@ absl::Status CheckIdsRangeAndStrictlyIncreasing2(
return absl::OkStatus();
}
} // namespace
} // namespace internal
IdNameBiMap::IdNameBiMap(
std::initializer_list<std::pair<int64_t, absl::string_view>> ids)
@@ -85,9 +94,9 @@ IdNameBiMap& IdNameBiMap::operator=(const IdNameBiMap& other) {
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))
RETURN_IF_ERROR(internal::CheckIdsRangeAndStrictlyIncreasing2(deleted_ids))
<< "invalid deleted ids";
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing2(new_ids))
RETURN_IF_ERROR(internal::CheckIdsRangeAndStrictlyIncreasing2(new_ids))
<< "invalid new ids";
if (!names.empty() && names.size() != new_ids.size()) {
return util::InvalidArgumentErrorBuilder()
@@ -107,7 +116,10 @@ absl::Status IdNameBiMap::BulkUpdate(
}
ModelSummary::ModelSummary(const bool check_names)
: variables(check_names), linear_constraints(check_names) {}
: variables(check_names),
linear_constraints(check_names),
sos1_constraints(check_names),
sos2_constraints(check_names) {}
absl::StatusOr<ModelSummary> ModelSummary::Create(const ModelProto& model,
const bool check_names) {
@@ -118,6 +130,12 @@ absl::StatusOr<ModelSummary> ModelSummary::Create(const ModelProto& model,
RETURN_IF_ERROR(summary.linear_constraints.BulkUpdate(
{}, model.linear_constraints().ids(), model.linear_constraints().names()))
<< "Model.linear_constraints are invalid";
RETURN_IF_ERROR(internal::UpdateBiMapFromMappedConstraints(
{}, model.sos1_constraints(), summary.sos1_constraints))
<< "Model.sos1_constraints are invalid";
RETURN_IF_ERROR(internal::UpdateBiMapFromMappedConstraints(
{}, model.sos2_constraints(), summary.sos2_constraints))
<< "Model.sos2_constraints are invalid";
return summary;
}
@@ -131,6 +149,16 @@ absl::Status ModelSummary::Update(const ModelUpdateProto& model_update) {
model_update.new_linear_constraints().ids(),
model_update.new_linear_constraints().names()))
<< "invalid linear constraints";
RETURN_IF_ERROR(internal::UpdateBiMapFromMappedConstraints(
model_update.sos1_constraint_updates().deleted_constraint_ids(),
model_update.sos1_constraint_updates().new_constraints(),
sos1_constraints))
<< "invalid sos1 constraints";
RETURN_IF_ERROR(internal::UpdateBiMapFromMappedConstraints(
model_update.sos2_constraint_updates().deleted_constraint_ids(),
model_update.sos2_constraint_updates().new_constraints(),
sos2_constraints))
<< "invalid sos2 constraints";
return absl::OkStatus();
}

View File

@@ -16,17 +16,21 @@
#include <cstdint>
#include <initializer_list>
#include <limits>
#include <list>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.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"
@@ -131,6 +135,8 @@ struct ModelSummary {
IdNameBiMap variables;
IdNameBiMap linear_constraints;
IdNameBiMap sos1_constraints;
IdNameBiMap sos2_constraints;
};
////////////////////////////////////////////////////////////////////////////////
@@ -219,6 +225,40 @@ absl::Status IdNameBiMap::SetNextFreeId(const int64_t new_next_free_id) {
return absl::OkStatus();
}
namespace internal {
// TODO(b/232526223): this is an exact copy of
// CheckIdsRangeAndStrictlyIncreasing from ids_validator.h, find a way to share
// the code.
// NOTE: This function is only exposed in the header because we need
// UpdateBiMapFromMappedConstraints here for testing purposes.
absl::Status CheckIdsRangeAndStrictlyIncreasing2(absl::Span<const int64_t> ids);
// NOTE: This is only exposed in the header for testing purposes.
template <typename ConstraintDataProto>
absl::Status UpdateBiMapFromMappedConstraints(
const absl::Span<const int64_t> deleted_ids,
const google::protobuf::Map<int64_t, ConstraintDataProto>& proto_map,
IdNameBiMap& bimap) {
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing2(deleted_ids))
<< "invalid deleted ids";
for (const int64_t id : deleted_ids) {
RETURN_IF_ERROR(bimap.Erase(id));
}
std::vector<int64_t> new_ids;
new_ids.reserve(proto_map.size());
for (const auto& [id, value] : proto_map) {
new_ids.push_back(id);
}
absl::c_sort(new_ids);
for (const int64_t id : new_ids) {
RETURN_IF_ERROR(bimap.Insert(id, proto_map.at(id).name()));
}
return absl::OkStatus();
}
} // namespace internal
} // namespace math_opt
} // namespace operations_research

View File

@@ -36,8 +36,8 @@ cc_library(
"//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/storage:model_storage",
"//ortools/math_opt/validators:ids_validator",
"//ortools/math_opt/validators:sparse_vector_validator",
"//ortools/util:status_macros",
@@ -62,7 +62,7 @@ cc_library(
"//ortools/base:status_macros",
"//ortools/math_opt:model_cc_proto",
"//ortools/math_opt:model_update_cc_proto",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/storage:model_storage",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
@@ -79,7 +79,7 @@ cc_library(
"//ortools/base",
"//ortools/base:intops",
"//ortools/math_opt/core:arrow_operator_proxy",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/storage:model_storage",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/types:span",
@@ -96,7 +96,7 @@ cc_library(
"//ortools/base",
"//ortools/base:intops",
"//ortools/base:map_util",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/storage:model_storage",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
],
@@ -111,7 +111,7 @@ cc_library(
":variable_and_expressions",
"//ortools/base",
"//ortools/base:intops",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/storage:model_storage",
],
)
@@ -129,8 +129,8 @@ cc_library(
"//ortools/base:intops",
"//ortools/math_opt:result_cc_proto",
"//ortools/math_opt:solution_cc_proto",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/core:sparse_vector_view",
"//ortools/math_opt/storage:model_storage",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:optional",
@@ -150,11 +150,11 @@ 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/math_opt/storage:model_storage",
"//ortools/port:proto_utils",
"//ortools/util:status_macros",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
@@ -171,7 +171,7 @@ cc_library(
":id_set",
"//ortools/base:intops",
"//ortools/math_opt:sparse_containers_cc_proto",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/storage:model_storage",
],
)
@@ -192,8 +192,8 @@ cc_library(
"//ortools/math_opt:callback_cc_proto",
"//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/storage:model_storage",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
@@ -207,7 +207,7 @@ cc_library(
hdrs = ["key_types.h"],
deps = [
"//ortools/base",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/storage:model_storage",
"@com_google_absl//absl/strings",
],
)
@@ -219,7 +219,7 @@ cc_library(
":key_types",
"//ortools/base",
"//ortools/math_opt/core:arrow_operator_proxy",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/storage:model_storage",
"@com_google_absl//absl/container:flat_hash_set",
],
)
@@ -237,7 +237,7 @@ cc_library(
"//ortools/math_opt:model_parameters_cc_proto",
"//ortools/math_opt:solution_cc_proto",
"//ortools/math_opt:sparse_containers_cc_proto",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/storage:model_storage",
"@com_google_protobuf//:protobuf",
],
)
@@ -250,7 +250,7 @@ cc_library(
"//ortools/base",
"//ortools/math_opt:model_cc_proto",
"//ortools/math_opt:model_update_cc_proto",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/storage:model_storage",
"@com_google_absl//absl/strings",
],
)
@@ -315,8 +315,8 @@ cc_library(
"//ortools/base:status_macros",
"//ortools/math_opt:callback_cc_proto",
"//ortools/math_opt:parameters_cc_proto",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/core:solver",
"//ortools/math_opt/storage:model_storage",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status:statusor",
@@ -374,6 +374,6 @@ cc_library(
hdrs = ["statistics.h"],
deps = [
":model",
"//ortools/math_opt/core:model_storage",
"//ortools/math_opt/storage:model_storage",
],
)

View File

@@ -28,12 +28,12 @@
#include "ortools/base/protoutil.h"
#include "ortools/base/strong_int.h"
#include "ortools/math_opt/callback.pb.h"
#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"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -75,10 +75,10 @@
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "ortools/math_opt/callback.pb.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/enums.h" // IWYU pragma: export
#include "ortools/math_opt/cpp/map_filter.h"
#include "ortools/math_opt/cpp/variable_and_expressions.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -26,8 +26,8 @@
#include "ortools/base/logging.h"
#include "ortools/base/strong_int.h"
#include "ortools/math_opt/core/arrow_operator_proxy.h" // IWYU pragma: export
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/key_types.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -21,8 +21,8 @@
#include "absl/container/flat_hash_set.h"
#include "ortools/base/logging.h"
#include "ortools/math_opt/core/arrow_operator_proxy.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/key_types.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -41,7 +41,7 @@
#include "absl/strings/string_view.h"
#include "ortools/base/logging.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -21,9 +21,9 @@
#include "ortools/base/logging.h"
#include "ortools/base/strong_int.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/id_map.h" // IWYU pragma: export
#include "ortools/math_opt/cpp/variable_and_expressions.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -19,9 +19,9 @@
#include <optional>
#include "ortools/base/strong_int.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/id_set.h"
#include "ortools/math_opt/sparse_containers.pb.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -26,7 +26,7 @@
#include "ortools/base/logging.h"
#include "ortools/base/status_macros.h"
#include "ortools/base/strong_int.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {
@@ -186,7 +186,7 @@ void Model::AddToObjective(const QuadraticExpression& objective_terms) {
}
LinearExpression Model::ObjectiveAsLinearExpression() const {
CHECK(storage()->quadratic_objective().empty())
CHECK_EQ(storage()->num_quadratic_objective_terms(), 0)
<< "The objective function contains quadratic terms and cannot be "
"represented as a LinearExpression";
LinearExpression result = storage()->objective_offset();
@@ -201,9 +201,9 @@ QuadraticExpression Model::ObjectiveAsQuadraticExpression() const {
for (const auto& [v, coef] : storage()->linear_objective()) {
result += Variable(storage(), v) * coef;
}
for (const auto& [vars, coef] : storage()->quadratic_objective()) {
result += QuadraticTerm(Variable(storage(), vars.first),
Variable(storage(), vars.second), coef);
for (const auto& [v1, v2, coef] : storage()->quadratic_objective_terms()) {
result +=
QuadraticTerm(Variable(storage(), v1), Variable(storage(), v2), coef);
}
return result;
}

View File

@@ -22,13 +22,13 @@
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "ortools/base/logging.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/key_types.h"
#include "ortools/math_opt/cpp/linear_constraint.h" // IWYU pragma: export
#include "ortools/math_opt/cpp/update_tracker.h" // IWYU pragma: export
#include "ortools/math_opt/cpp/variable_and_expressions.h" // IWYU pragma: export
#include "ortools/math_opt/model.pb.h" // IWYU pragma: export
#include "ortools/math_opt/model_update.pb.h" // IWYU pragma: export
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -20,13 +20,13 @@
#include <utility>
#include "google/protobuf/message.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/linear_constraint.h"
#include "ortools/math_opt/cpp/solution.h"
#include "ortools/math_opt/cpp/variable_and_expressions.h"
#include "ortools/math_opt/model_parameters.pb.h"
#include "ortools/math_opt/solution.pb.h"
#include "ortools/math_opt/sparse_containers.pb.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -20,12 +20,12 @@
#include <optional>
#include <vector>
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/linear_constraint.h"
#include "ortools/math_opt/cpp/map_filter.h" // IWYU pragma: export
#include "ortools/math_opt/cpp/solution.h"
#include "ortools/math_opt/cpp/variable_and_expressions.h"
#include "ortools/math_opt/model_parameters.pb.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -22,13 +22,13 @@
#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/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/storage/model_storage.h"
#include "ortools/math_opt/validators/ids_validator.h"
#include "ortools/math_opt/validators/sparse_vector_validator.h"
#include "ortools/util/status_macros.h"

View File

@@ -19,13 +19,13 @@
#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"
#include "ortools/math_opt/result.pb.h" // IWYU pragma: export
#include "ortools/math_opt/solution.pb.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -24,9 +24,9 @@
#include "ortools/base/logging.h"
#include "ortools/base/status_macros.h"
#include "ortools/math_opt/callback.pb.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/core/solver.h"
#include "ortools/math_opt/cpp/model.h"
#include "ortools/math_opt/storage/model_storage.h"
#include "ortools/util/status_macros.h"
namespace operations_research {

View File

@@ -25,7 +25,6 @@
#include <utility>
#include "absl/status/statusor.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/core/solver.h"
#include "ortools/math_opt/cpp/model.h"
#include "ortools/math_opt/cpp/parameters.h" // IWYU pragma: export
@@ -33,6 +32,7 @@
#include "ortools/math_opt/cpp/solve_result.h" // IWYU pragma: export
#include "ortools/math_opt/cpp/solver_init_arguments.h" // IWYU pragma: export
#include "ortools/math_opt/parameters.pb.h" // IWYU pragma: export
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -27,10 +27,10 @@
#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/math_opt/storage/model_storage.h"
#include "ortools/port/proto_utils.h"
#include "ortools/util/status_macros.h"

View File

@@ -24,12 +24,12 @@
#include "absl/types/span.h"
#include "ortools/base/logging.h"
#include "ortools/gscip/gscip.pb.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/enums.h" // IWYU pragma: export
#include "ortools/math_opt/cpp/linear_constraint.h"
#include "ortools/math_opt/cpp/solution.h" // IWYU pragma: export
#include "ortools/math_opt/cpp/variable_and_expressions.h"
#include "ortools/math_opt/result.pb.h" // IWYU pragma: export
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -20,13 +20,13 @@
#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/storage/model_storage.h"
#include "ortools/math_opt/validators/ids_validator.h"
#include "ortools/math_opt/validators/sparse_vector_validator.h"
#include "ortools/util/status_macros.h"

View File

@@ -20,8 +20,8 @@
#include <ostream>
#include <type_traits>
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/model.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research::math_opt {
namespace {

View File

@@ -19,8 +19,8 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "ortools/base/logging.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/model.pb.h"
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -20,9 +20,9 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/model.pb.h"
#include "ortools/math_opt/model_update.pb.h" // IWYU pragma: export
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -101,9 +101,9 @@
#include "absl/container/flat_hash_map.h"
#include "ortools/base/logging.h"
#include "ortools/base/strong_int.h"
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/cpp/id_map.h" // IWYU pragma: export
#include "ortools/math_opt/cpp/key_types.h" // IWYU pragma: export
#include "ortools/math_opt/storage/model_storage.h"
namespace operations_research {
namespace math_opt {

View File

@@ -88,23 +88,35 @@ message LinearConstraintsProto {
repeated string names = 4;
}
// An optimization problem of the form
// Data for representing a single SOS1 or SOS2 constraint.
message SosConstraintProto {
// The expressions over which to apply the SOS constraint:
// * SOS1: At most one element takes a nonzero value.
// * SOS2: At most two elements take nonzero values, and they must be
// adjacent in the repeated ordering.
repeated LinearExpressionProto expressions = 1;
// Either empty or of equal length to expressions. If empty, default weights
// are 1, 2, ...
repeated double weights = 2;
// Parent messages may have uniqueness requirements on this field; e.g., see
// ModelProto.sos1_constraints and SosConstraintUpdatesProto.new_constraints.
string name = 3;
}
// An optimization problem. For full details, see go/mathopt-model.
//
// min(/max) c * x
// s.t.
// cons_lb <= A * x <= cons_ub
// var_lb <= x <= var_ub
// x_i integer for i in I
// MathOpt supports:
// - Continuous and integer decision variables with optional finite bounds.
// - Linear and quadratic objectives, either minimized or maximized.
// - A number of constraints types, including:
// * Linear constraints
// * Logical constraints
// > SOS1 and SOS2 constraints
//
// where:
// * x is a vector of decision variables in R^n
// * c, var_lb, var_ub are vectors in R^n
// * cons_lb, cons_ub are vectors in R^m
// * A is a sparse matrix in R^{m by n}
// * potentially var_lb, cons_lb are -inf
// * potentially var_ub, cons_ub are +inf
//
// For more details see go/mathopt-model
// By default, constraints are represented in "id-to-data" maps. However, we
// represent linear constraints in a more efficient "struct-of-arrays" format.
message ModelProto {
string name = 1;
VariablesProto variables = 2;
@@ -119,4 +131,30 @@ message ModelProto {
// * Matrix entries not specified are zero.
// * linear_constraint_matrix.values must all be finite.
SparseDoubleMatrixProto linear_constraint_matrix = 5;
// Mapped constraints (i.e., stored in "constraint ID"-to-"constraint data"
// map). For each subsequent submessage, we require that:
// * Each key is in [0, max(int64)).
// * Each key is unique in its respective map (but not necessarily across
// constraint types)
// * Each value contains a name field, and each nonempty name must be
// distinct across all map entries (but not necessarily across constraint
// types).
// Reserved for quadratic constraints.
reserved 6;
// SOS1 constraints in the model, which constrain that at most one
// `expression` can be nonzero. The optional `weights` entries are an
// implementation detail used by the solver to (hopefully) converge more
// quickly. In more detail, solvers may (or may not) use these weights to
// select branching decisions that produce "balanced" children nodes.
map<int64, SosConstraintProto> sos1_constraints = 7;
// SOS2 constraints in the model, which constrain that at most two entries of
// `expression` can be nonzero, and they must be adjacent in their ordering.
// If no `weights` are provided, this ordering is their linear ordering in the
// `expressions` list; if `weights` are presented, the ordering is taken with
// respect to these values in increasing order.
map<int64, SosConstraintProto> sos2_constraints = 8;
}

View File

@@ -107,6 +107,23 @@ message LinearConstraintUpdatesProto {
SparseDoubleVectorProto upper_bounds = 2;
}
// Data for updates to SOS1 and SOS2 constraints; only addition and deletion, no
// support for in-place constraint updates.
message SosConstraintUpdatesProto {
// Removes SOS constraints from the model.
//
// Each value must be in [0, max(int64)). Values must be in strictly
// increasing order. Applies only to existing SOS constraint ids that have not
// yet been deleted.
repeated int64 deleted_constraint_ids = 1;
// Add new SOS constraints to the model. All keys must be in [0, max(int64)),
// and must be greater than any ids used in the initial model and previous
// updates. All nonempty names should be distinct from existing names and each
// other.
map<int64, SosConstraintProto> new_constraints = 2;
}
// Updates to a ModelProto.
message ModelUpdateProto {
// Removes variables from the model.
@@ -161,4 +178,11 @@ message ModelUpdateProto {
// * Zero values delete existing entries, and have no effect for new entries.
// * linear_constraint_matrix.values must all be finite.
SparseDoubleMatrixProto linear_constraint_matrix_updates = 8;
// Reserved for quadratic constraint updates.
reserved 9;
// Updates the general constraints (addition or deletion only).
SosConstraintUpdatesProto sos1_constraint_updates = 10;
SosConstraintUpdatesProto sos2_constraint_updates = 11;
}

View File

@@ -1860,7 +1860,7 @@ GurobiSolver::RegisterCallback(const CallbackRegistrationProto& registration,
// can not be performed safely.
RETURN_IF_ERROR(gurobi_->SetIntParam(GRB_INT_PAR_LAZYCONSTRAINTS, 1));
}
return absl::make_unique<GurobiCallbackData>(
return std::make_unique<GurobiCallbackData>(
GurobiCallbackInput{
.user_cb = cb,
.message_cb = message_cb,

View File

@@ -0,0 +1,84 @@
package(default_visibility = ["//ortools/math_opt:__subpackages__"])
cc_library(
name = "model_storage_types",
hdrs = ["model_storage_types.h"],
deps = ["//ortools/base:intops",],
)
cc_library(
name = "sparse_matrix",
srcs = ["sparse_matrix.cc"],
hdrs = ["sparse_matrix.h"],
deps = [
":model_storage_types",
"//ortools/base:intops",
"//ortools/base:map_util",
"//ortools/base:strong_vector",
"//ortools/math_opt:sparse_containers_cc_proto",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
],
)
cc_library(
name = "model_storage",
srcs = ["model_storage.cc"],
hdrs = ["model_storage.h"],
deps = [
":model_storage_types",
":model_update_merge",
":sparse_matrix",
"//ortools/base",
#"//ortools/base:logging",
"//ortools/base:map_util",
"//ortools/base:intops",
"//ortools/math_opt:model_cc_proto",
"//ortools/math_opt:model_update_cc_proto",
"//ortools/math_opt:solution_cc_proto",
"//ortools/math_opt:sparse_containers_cc_proto",
"//ortools/math_opt/core:sparse_vector_view",
"//ortools/math_opt/validators:model_validator",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/meta:type_traits",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "model_update_merge",
srcs = ["model_update_merge.cc"],
hdrs = ["model_update_merge.h"],
deps = [
":proto_merging_utils",
"//ortools/base",
#"//ortools/base:logging",
#"@com_google_absl//absl/log:check",
"//ortools/math_opt:model_cc_proto",
"//ortools/math_opt:model_update_cc_proto",
"//ortools/math_opt:sparse_containers_cc_proto",
"//ortools/math_opt/core:sparse_vector_view",
],
)
cc_library(
name = "proto_merging_utils",
srcs = ["proto_merging_utils.cc"],
hdrs = ["proto_merging_utils.h"],
deps = [
"//ortools/math_opt:sparse_containers_cc_proto",
"//ortools/math_opt/core:sparse_vector_view",
"//ortools/base:protobuf_util",
"//ortools/base",
#"//ortools/base:logging",
#"@com_google_absl//absl/log:check",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_protobuf//:protobuf",
],
)

View File

@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/math_opt/core/model_storage.h"
#include "ortools/math_opt/storage/model_storage.h"
#include <algorithm>
#include <cstdint>
@@ -33,12 +33,14 @@
#include "ortools/base/map_util.h"
#include "ortools/base/status_macros.h"
#include "ortools/base/strong_int.h"
#include "ortools/math_opt/core/model_update_merge.h"
#include "ortools/math_opt/core/sparse_vector_view.h"
#include "ortools/math_opt/model.pb.h"
#include "ortools/math_opt/model_update.pb.h"
#include "ortools/math_opt/solution.pb.h"
#include "ortools/math_opt/sparse_containers.pb.h"
#include "ortools/math_opt/storage/model_storage_types.h"
#include "ortools/math_opt/storage/model_update_merge.h"
#include "ortools/math_opt/storage/sparse_matrix.h"
#include "ortools/math_opt/validators/model_validator.h"
namespace operations_research {
@@ -110,18 +112,6 @@ void AppendFromMap(const absl::flat_hash_set<IdType>& dirty_keys,
}
}
template <typename T>
absl::flat_hash_map<T, BasisStatusProto> SparseBasisVectorToMap(
const SparseBasisStatusVector& sparse_vector) {
absl::flat_hash_map<T, BasisStatusProto> result;
CHECK_EQ(sparse_vector.ids_size(), sparse_vector.values_size());
result.reserve(sparse_vector.ids_size());
for (const auto [id, value] : MakeView(sparse_vector)) {
gtl::InsertOrDie(&result, T(id), static_cast<BasisStatusProto>(value));
}
return result;
}
// If an element in keys is not found in coefficients, it is set to 0.0 in
// matrix. Keys must be in lexicographic ordering (i.e. sorted).
// NOTE: This signature can be updated to take a Span instead of a vector if
@@ -256,9 +246,6 @@ void ModelStorage::AddVariableInternal(const VariableId id,
if (!lazy_matrix_columns_.empty()) {
gtl::InsertOrDie(&lazy_matrix_columns_, id, {});
}
if (!lazy_quadratic_objective_by_variable_.empty()) {
gtl::InsertOrDie(&lazy_quadratic_objective_by_variable_, id, {});
}
}
void ModelStorage::AddVariables(const VariablesProto& variables) {
@@ -284,33 +271,16 @@ void ModelStorage::DeleteVariable(const VariableId id) {
dirty_variable_upper_bounds_.erase(id);
dirty_variable_is_integer_.erase(id);
dirty_linear_objective_coefficients_.erase(id);
}
// If we do not have any quadratic updates to delete, we would like to avoid
// initializing the lazy data structures. The updates might tracked in:
// 1. dirty_quadratic_objective_coefficients_ (both variables old)
// 2. quadratic_objective_ (at least one new variable)
// If both maps are empty, we can skip the update and initializiation. Note
// that we could be a bit more clever here based on whether the deleted
// variable is new or old, but that makes the logic more complex.
if (!quadratic_objective_.empty() ||
!dirty_quadratic_objective_coefficients_.empty()) {
EnsureLazyQuadraticObjective();
const auto related_variables =
lazy_quadratic_objective_by_variable_.extract(id);
for (const VariableId other_id : related_variables.mapped()) {
// Due to the extract above, the at lookup will fail if other_id == id.
if (id != other_id) {
CHECK_GT(lazy_quadratic_objective_by_variable_.at(other_id).erase(id),
0);
}
const auto ordered_pair = internal::MakeOrderedPair(id, other_id);
quadratic_objective_.erase(ordered_pair);
for (const VariableId related : quadratic_objective_.RelatedVariables(id)) {
// We can only have a dirty update to wipe clean if both variables are old
if (id < variables_checkpoint_ && other_id < variables_checkpoint_) {
dirty_quadratic_objective_coefficients_.erase(ordered_pair);
if (related < variables_checkpoint_) {
dirty_quadratic_objective_coefficients_.erase(
internal::MakeOrderedPair(id, related));
}
}
}
quadratic_objective_.Delete(id);
for (const LinearConstraintId related_constraint :
lazy_matrix_columns_.at(id)) {
CHECK_GT(lazy_matrix_rows_.at(related_constraint).erase(id), 0);
@@ -440,7 +410,7 @@ ModelProto ModelStorage::ExportModel() const {
SortedMapKeys(linear_objective_), linear_objective_,
*result.mutable_objective()->mutable_linear_coefficients());
*result.mutable_objective()->mutable_quadratic_coefficients() =
ExportMatrix(quadratic_objective_, SortedMapKeys(quadratic_objective_));
quadratic_objective_.Proto();
// Pull out the linear constraints.
for (const LinearConstraintId con : SortedMapKeys(linear_constraints_)) {
@@ -535,42 +505,11 @@ std::optional<ModelUpdateProto> ModelStorage::ExportSharedModelUpdate() {
obj_updates->mutable_linear_coefficients()->add_values(*double_value);
}
}
// If we do not have any quadratic updates to push, we would like to avoid
// initializing the lazy data structures. The updates might tracked in:
// 1. dirty_quadratic_objective_coefficients_ (both variables old)
// 2. quadratic_objective_ (at least one new variable)
// If both maps are empty, we can skip the update and initializiation.
if (!quadratic_objective_.empty() ||
!dirty_quadratic_objective_coefficients_.empty()) {
EnsureLazyQuadraticObjective();
// NOTE: dirty_quadratic_objective_coefficients_ only tracks terms where
// both variables are "old".
std::vector<std::pair<VariableId, VariableId>> quadratic_objective_updates(
dirty_quadratic_objective_coefficients_.begin(),
dirty_quadratic_objective_coefficients_.end());
// Now, we loop through the "new" variables and track updates involving
// them. We need to look out for two things:
// * The "other" variable in the term can either be new or old.
// * We cannot doubly insert terms when both variables are new.
// Note that this traversal is doing at most twice as much work as
// necessary.
for (VariableId new_var = variables_checkpoint_;
new_var < next_variable_id_; ++new_var) {
if (variables_.contains(new_var)) {
for (const VariableId other_var :
lazy_quadratic_objective_by_variable_.at(new_var)) {
if (other_var <= new_var) {
quadratic_objective_updates.push_back(
internal::MakeOrderedPair(new_var, other_var));
}
}
}
}
std::sort(quadratic_objective_updates.begin(),
quadratic_objective_updates.end());
*result.mutable_objective_updates()->mutable_quadratic_coefficients() =
ExportMatrix(quadratic_objective_, quadratic_objective_updates);
}
*result.mutable_objective_updates()->mutable_quadratic_coefficients() =
quadratic_objective_.Update(variables_, variables_checkpoint_,
next_variable_id_,
dirty_quadratic_objective_coefficients_);
// Update the linear constraints
auto lin_con_updates = result.mutable_linear_constraint_updates();
@@ -646,22 +585,6 @@ void ModelStorage::EnsureLazyMatrixRows() {
}
}
void ModelStorage::EnsureLazyQuadraticObjective() {
if (lazy_quadratic_objective_by_variable_.empty()) {
for (const auto& [var, data] : variables_) {
lazy_quadratic_objective_by_variable_.insert({var, {}});
}
for (const auto& [vars, coeff] : quadratic_objective_) {
lazy_quadratic_objective_by_variable_.at(vars.first).insert(vars.second);
lazy_quadratic_objective_by_variable_.at(vars.second).insert(vars.first);
}
for (const auto& vars : dirty_quadratic_objective_coefficients_) {
lazy_quadratic_objective_by_variable_.at(vars.first).insert(vars.second);
lazy_quadratic_objective_by_variable_.at(vars.second).insert(vars.first);
}
}
}
void ModelStorage::SharedCheckpoint() {
variables_checkpoint_ = next_variable_id_;
linear_constraints_checkpoint_ = next_linear_constraint_id_;
@@ -803,6 +726,7 @@ void ModelStorage::CheckpointLocked(const UpdateTrackerId update_tracker) {
}
}
SharedCheckpoint();
data->updates.clear();
}

View File

@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef OR_TOOLS_MATH_OPT_CORE_MODEL_STORAGE_H_
#define OR_TOOLS_MATH_OPT_CORE_MODEL_STORAGE_H_
#ifndef OR_TOOLS_MATH_OPT_STORAGE_MODEL_STORAGE_H_
#define OR_TOOLS_MATH_OPT_STORAGE_MODEL_STORAGE_H_
#include <cstdint>
#include <limits>
@@ -33,14 +33,12 @@
#include "ortools/base/strong_int.h"
#include "ortools/math_opt/model.pb.h"
#include "ortools/math_opt/model_update.pb.h"
#include "ortools/math_opt/storage/model_storage_types.h"
#include "ortools/math_opt/storage/sparse_matrix.h"
namespace operations_research {
namespace math_opt {
DEFINE_STRONG_INT_TYPE(VariableId, int64_t);
DEFINE_STRONG_INT_TYPE(LinearConstraintId, int64_t);
DEFINE_STRONG_INT_TYPE(UpdateTrackerId, int64_t);
// An index based C++ API for building & storing optimization problems.
//
// Note that this API should usually not be used by C++ users that should prefer
@@ -376,10 +374,14 @@ class ModelStorage {
inline const absl::flat_hash_map<VariableId, double>& linear_objective()
const;
inline int64_t num_quadratic_objective_terms() const;
// The variable pairs with nonzero quadratic objective coefficients. The keys
// are ordered such that .first <= .second.
inline const absl::flat_hash_map<std::pair<VariableId, VariableId>, double>&
quadratic_objective() const;
// are ordered such that .first <= .second. All values are nonempty.
//
// TODO(b/233630053) do no allocate the result, expose an iterator API.
inline std::vector<std::tuple<VariableId, VariableId, double>>
quadratic_objective_terms() const;
// Returns a sorted vector of all variables in the model with nonzero linear
// objective coefficients.
@@ -577,10 +579,6 @@ class ModelStorage {
// the model.
void EnsureLazyMatrixRows();
// Initializes lazy_quadratic_objective_by_variable_ if it is still empty and
// there is at least one variable in the model.
void EnsureLazyQuadraticObjective();
// Export a single variable to proto.
void AppendVariable(VariableId id, VariablesProto& variables_proto) const;
@@ -616,28 +614,20 @@ class ModelStorage {
absl::flat_hash_map<VariableId, VariableData> variables_;
absl::flat_hash_map<LinearConstraintId, LinearConstraintData>
linear_constraints_;
// The values of the map must never include zero.
absl::flat_hash_map<VariableId, double> linear_objective_;
// The values of the map must never include zero. The keys must be upper
// triangular, i.e. .first <= .second.
absl::flat_hash_map<std::pair<VariableId, VariableId>, double>
quadratic_objective_;
SparseSymmetricMatrix quadratic_objective_;
// The values of the map must never include zero.
absl::flat_hash_map<std::pair<LinearConstraintId, VariableId>, double>
linear_constraint_matrix_;
absl::flat_hash_map<VariableId, absl::flat_hash_set<LinearConstraintId>>
lazy_matrix_columns_;
absl::flat_hash_map<LinearConstraintId, absl::flat_hash_set<VariableId>>
lazy_matrix_rows_;
// To handle deletions we need to have an efficient way to look up which
// quadratic objective terms involve a given variable. This map stores this
// information where the key corresponds to a variable and the value is the
// set of all variables appearing in a quadratic objective term with the key.
// This data structure is only initialized after a call to
// EnsureLazyQuadraticObjective. As of 11/17/2021, this will have occurred if
// a nonzero quadratic objective term has ever been added to the model.
absl::flat_hash_map<VariableId, absl::flat_hash_set<VariableId>>
lazy_quadratic_objective_by_variable_;
// Update information
//
@@ -918,9 +908,7 @@ double ModelStorage::linear_objective_coefficient(VariableId variable) const {
double ModelStorage::quadratic_objective_coefficient(
const VariableId first_variable, const VariableId second_variable) const {
return gtl::FindWithDefault(
quadratic_objective_,
internal::MakeOrderedPair(first_variable, second_variable));
return quadratic_objective_.get(first_variable, second_variable);
}
bool ModelStorage::is_linear_objective_coefficient_nonzero(
@@ -930,8 +918,7 @@ bool ModelStorage::is_linear_objective_coefficient_nonzero(
bool ModelStorage::is_quadratic_objective_coefficient_nonzero(
const VariableId first_variable, const VariableId second_variable) const {
return quadratic_objective_.contains(
internal::MakeOrderedPair(first_variable, second_variable));
return quadratic_objective_.get(first_variable, second_variable) != 0.0;
}
void ModelStorage::set_is_maximize(bool is_maximize) {
@@ -977,33 +964,12 @@ void ModelStorage::set_linear_objective_coefficient(VariableId variable,
void ModelStorage::set_quadratic_objective_coefficient(
const VariableId first_variable, const VariableId second_variable,
double value) {
const bool updated =
quadratic_objective_.set(first_variable, second_variable, value);
const std::pair<VariableId, VariableId> key =
internal::MakeOrderedPair(first_variable, second_variable);
bool was_updated = false;
if (value == 0.0) {
if (quadratic_objective_.erase(key) > 0) {
was_updated = true;
}
} else {
const auto [iterator, inserted] =
quadratic_objective_.try_emplace(key, value);
if (inserted) {
was_updated = true;
} else if (iterator->second != value) {
iterator->second = value;
was_updated = true;
}
}
if (was_updated) {
if (!lazy_quadratic_objective_by_variable_.empty()) {
lazy_quadratic_objective_by_variable_.at(first_variable)
.insert(second_variable);
lazy_quadratic_objective_by_variable_.at(second_variable)
.insert(first_variable);
}
if (key.second < variables_checkpoint_) {
dirty_quadratic_objective_coefficients_.insert(key);
}
if (updated && key.second < variables_checkpoint_) {
dirty_quadratic_objective_coefficients_.insert(key);
}
}
@@ -1012,11 +978,12 @@ void ModelStorage::clear_objective() {
while (!linear_objective_.empty()) {
set_linear_objective_coefficient(linear_objective_.begin()->first, 0.0);
}
while (!quadratic_objective_.empty()) {
set_quadratic_objective_coefficient(
quadratic_objective_.begin()->first.first,
quadratic_objective_.begin()->first.second, 0.0);
for (const auto [var_pair, value] : quadratic_objective_.values()) {
if (var_pair.second < variables_checkpoint_ && value != 0.0) {
dirty_quadratic_objective_coefficients_.insert(var_pair);
}
}
quadratic_objective_.Clear();
}
const absl::flat_hash_map<VariableId, double>& ModelStorage::linear_objective()
@@ -1024,12 +991,16 @@ const absl::flat_hash_map<VariableId, double>& ModelStorage::linear_objective()
return linear_objective_;
}
const absl::flat_hash_map<std::pair<VariableId, VariableId>, double>&
ModelStorage::quadratic_objective() const {
return quadratic_objective_;
int64_t ModelStorage::num_quadratic_objective_terms() const {
return quadratic_objective_.nonzeros();
}
std::vector<std::tuple<VariableId, VariableId, double>>
ModelStorage::quadratic_objective_terms() const {
return quadratic_objective_.Terms();
}
} // namespace math_opt
} // namespace operations_research
#endif // OR_TOOLS_MATH_OPT_CORE_MODEL_STORAGE_H_
#endif // OR_TOOLS_MATH_OPT_STORAGE_MODEL_STORAGE_H_

View File

@@ -0,0 +1,29 @@
// 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_STORAGE_MODEL_STORAGE_TYPES_H_
#define OR_TOOLS_MATH_OPT_STORAGE_MODEL_STORAGE_TYPES_H_
#include <cstdint>
#include "ortools/base/strong_int.h"
namespace operations_research::math_opt {
DEFINE_STRONG_INT_TYPE(VariableId, int64_t);
DEFINE_STRONG_INT_TYPE(LinearConstraintId, int64_t);
DEFINE_STRONG_INT_TYPE(UpdateTrackerId, int64_t);
} // namespace operations_research::math_opt
#endif // OR_TOOLS_MATH_OPT_STORAGE_MODEL_STORAGE_TYPES_H_

View File

@@ -11,24 +11,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/math_opt/core/model_update_merge.h"
#include "ortools/math_opt/storage/model_update_merge.h"
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <string>
#include <utility>
#include "absl/container/flat_hash_set.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/math_opt/core/sparse_vector_view.h"
#include "ortools/math_opt/model.pb.h"
#include "ortools/math_opt/model_update.pb.h"
#include "ortools/math_opt/sparse_containers.pb.h"
#include "ortools/math_opt/storage/proto_merging_utils.h"
namespace operations_research {
namespace math_opt {
namespace operations_research::math_opt {
void MergeIntoUpdate(const ModelUpdateProto& from_new,
ModelUpdateProto& into_old) {
@@ -36,13 +32,12 @@ void MergeIntoUpdate(const ModelUpdateProto& from_new,
// variables that were created in `into_old`. Below we will simply remove
// those variables from the list of new variables in the merge; thus making
// the update as if those variables never existed.
internal::MergeIntoSortedIds(from_new.deleted_variable_ids(),
*into_old.mutable_deleted_variable_ids(),
/*deleted=*/into_old.new_variables().ids());
internal::MergeIntoSortedIds(
from_new.deleted_linear_constraint_ids(),
*into_old.mutable_deleted_linear_constraint_ids(),
/*deleted=*/into_old.new_linear_constraints().ids());
MergeIntoSortedIds(from_new.deleted_variable_ids(),
*into_old.mutable_deleted_variable_ids(),
/*deleted=*/into_old.new_variables().ids());
MergeIntoSortedIds(from_new.deleted_linear_constraint_ids(),
*into_old.mutable_deleted_linear_constraint_ids(),
/*deleted=*/into_old.new_linear_constraints().ids());
// For variables and linear constraints updates, we want to ignore updates of:
//
@@ -67,25 +62,25 @@ void MergeIntoUpdate(const ModelUpdateProto& from_new,
into_old.new_linear_constraints().ids());
// Merge updates of variable properties.
internal::MergeIntoSparseVector(
MergeIntoSparseVector(
from_new.variable_updates().lower_bounds(),
*into_old.mutable_variable_updates()->mutable_lower_bounds(),
from_deleted_and_into_new_variable_ids);
internal::MergeIntoSparseVector(
MergeIntoSparseVector(
from_new.variable_updates().upper_bounds(),
*into_old.mutable_variable_updates()->mutable_upper_bounds(),
from_deleted_and_into_new_variable_ids);
internal::MergeIntoSparseVector(
MergeIntoSparseVector(
from_new.variable_updates().integers(),
*into_old.mutable_variable_updates()->mutable_integers(),
from_deleted_and_into_new_variable_ids);
// Merge updates of linear constraints properties.
internal::MergeIntoSparseVector(
MergeIntoSparseVector(
from_new.linear_constraint_updates().lower_bounds(),
*into_old.mutable_linear_constraint_updates()->mutable_lower_bounds(),
from_deleted_and_into_new_linear_constraint_ids);
internal::MergeIntoSparseVector(
MergeIntoSparseVector(
from_new.linear_constraint_updates().upper_bounds(),
*into_old.mutable_linear_constraint_updates()->mutable_upper_bounds(),
from_deleted_and_into_new_linear_constraint_ids);
@@ -110,28 +105,28 @@ void MergeIntoUpdate(const ModelUpdateProto& from_new,
CHECK_GT(*from_new.new_variables().ids().begin(),
*into_old.new_variables().ids().rbegin());
}
internal::UpdateNewElementProperty(
UpdateNewElementProperty(
/*ids=*/into_old.new_variables().ids(),
/*values=*/*into_old.mutable_new_variables()->mutable_lower_bounds(),
/*deleted=*/from_new.deleted_variable_ids(),
/*updates=*/from_new.variable_updates().lower_bounds());
internal::UpdateNewElementProperty(
UpdateNewElementProperty(
/*ids=*/into_old.new_variables().ids(),
/*values=*/*into_old.mutable_new_variables()->mutable_upper_bounds(),
/*deleted=*/from_new.deleted_variable_ids(),
/*updates=*/from_new.variable_updates().upper_bounds());
internal::UpdateNewElementProperty(
UpdateNewElementProperty(
/*ids=*/into_old.new_variables().ids(),
/*values=*/*into_old.mutable_new_variables()->mutable_integers(),
/*deleted=*/from_new.deleted_variable_ids(),
/*updates=*/from_new.variable_updates().integers());
internal::UpdateNewElementProperty(
UpdateNewElementProperty(
/*ids=*/into_old.new_variables().ids(),
/*values=*/*into_old.mutable_new_variables()->mutable_names(),
/*deleted=*/from_new.deleted_variable_ids(),
// We use an empty view here since names can't be updated.
/*updates=*/SparseVectorView<std::string>());
internal::RemoveDeletedIds(
RemoveDeletedIds(
/*ids=*/*into_old.mutable_new_variables()->mutable_ids(),
/*deleted=*/from_new.deleted_variable_ids());
into_old.mutable_new_variables()->MergeFrom(from_new.new_variables());
@@ -143,25 +138,25 @@ void MergeIntoUpdate(const ModelUpdateProto& from_new,
CHECK_GT(*from_new.new_linear_constraints().ids().begin(),
*into_old.new_linear_constraints().ids().rbegin());
}
internal::UpdateNewElementProperty(
UpdateNewElementProperty(
/*ids=*/into_old.new_linear_constraints().ids(),
/*values=*/
*into_old.mutable_new_linear_constraints()->mutable_lower_bounds(),
/*deleted=*/from_new.deleted_linear_constraint_ids(),
/*updates=*/from_new.linear_constraint_updates().lower_bounds());
internal::UpdateNewElementProperty(
UpdateNewElementProperty(
/*ids=*/into_old.new_linear_constraints().ids(),
/*values=*/
*into_old.mutable_new_linear_constraints()->mutable_upper_bounds(),
/*deleted=*/from_new.deleted_linear_constraint_ids(),
/*updates=*/from_new.linear_constraint_updates().upper_bounds());
internal::UpdateNewElementProperty(
UpdateNewElementProperty(
/*ids=*/into_old.new_linear_constraints().ids(),
/*values=*/*into_old.mutable_new_linear_constraints()->mutable_names(),
/*deleted=*/from_new.deleted_linear_constraint_ids(),
// We use an empty view here since names can't be updated.
/*updates=*/SparseVectorView<std::string>());
internal::RemoveDeletedIds(
RemoveDeletedIds(
/*ids=*/*into_old.mutable_new_linear_constraints()->mutable_ids(),
/*deleted=*/from_new.deleted_linear_constraint_ids());
into_old.mutable_new_linear_constraints()->MergeFrom(
@@ -176,184 +171,22 @@ void MergeIntoUpdate(const ModelUpdateProto& from_new,
into_old.mutable_objective_updates()->set_offset_update(
from_new.objective_updates().offset_update());
}
internal::MergeIntoSparseVector(
MergeIntoSparseVector(
from_new.objective_updates().linear_coefficients(),
*into_old.mutable_objective_updates()->mutable_linear_coefficients(),
from_new.deleted_variable_ids());
internal::MergeIntoSparseDoubleMatrix(
MergeIntoSparseDoubleMatrix(
from_new.objective_updates().quadratic_coefficients(),
*into_old.mutable_objective_updates()->mutable_quadratic_coefficients(),
/*deleted_rows=*/from_new.deleted_variable_ids(),
/*deleted_columns=*/from_new.deleted_variable_ids());
// Merge the linear constraints coefficients.
internal::MergeIntoSparseDoubleMatrix(
MergeIntoSparseDoubleMatrix(
from_new.linear_constraint_matrix_updates(),
*into_old.mutable_linear_constraint_matrix_updates(),
/*deleted_rows=*/from_new.deleted_linear_constraint_ids(),
/*deleted_columns=*/from_new.deleted_variable_ids());
}
namespace internal {
void RemoveDeletedIds(google::protobuf::RepeatedField<int64_t>& ids,
const google::protobuf::RepeatedField<int64_t>& deleted) {
int next_insertion_point = 0;
int deleted_i = 0;
for (const int64_t id : ids) {
while (deleted_i < deleted.size() && deleted[deleted_i] < id) {
++deleted_i;
}
if (deleted_i < deleted.size() && deleted[deleted_i] == id) {
continue;
}
ids[next_insertion_point] = id;
++next_insertion_point;
}
ids.Truncate(next_insertion_point);
}
void MergeIntoSortedIds(
const google::protobuf::RepeatedField<int64_t>& from_new,
google::protobuf::RepeatedField<int64_t>& into_old,
const google::protobuf::RepeatedField<int64_t>& deleted) {
google::protobuf::RepeatedField<int64_t> result;
int from_new_i = 0;
int into_old_i = 0;
int deleted_i = 0;
// Functions that adds the input id to the result if it is not in deleted. It
// updates deleted_i as a side effect too.
const auto add_if_not_deleted = [&](const int64_t id) {
while (deleted_i < deleted.size() && deleted[deleted_i] < id) {
++deleted_i;
}
if (deleted_i == deleted.size() || deleted[deleted_i] != id) {
result.Add(id);
}
};
while (from_new_i < from_new.size() && into_old_i < into_old.size()) {
if (from_new[from_new_i] < into_old[into_old_i]) {
add_if_not_deleted(from_new[from_new_i]);
++from_new_i;
} else if (from_new[from_new_i] > into_old[into_old_i]) {
add_if_not_deleted(into_old[into_old_i]);
++into_old_i;
} else { // from_new[from_new_i] == into_old[into_old_i]
add_if_not_deleted(from_new[from_new_i]);
++from_new_i;
++into_old_i;
}
}
// At this point either from_new_i == from_new.size() or to_i == to.size() or
// both. And the one that is not empty, if it exists, has elements greater
// than all other elements already inserted.
for (; from_new_i < from_new.size(); ++from_new_i) {
add_if_not_deleted(from_new[from_new_i]);
}
for (; into_old_i < into_old.size(); ++into_old_i) {
add_if_not_deleted(into_old[into_old_i]);
}
into_old.Swap(&result);
}
void MergeIntoSparseDoubleMatrix(
const SparseDoubleMatrixProto& from_new, SparseDoubleMatrixProto& into_old,
const google::protobuf::RepeatedField<int64_t>& deleted_rows,
const google::protobuf::RepeatedField<int64_t>& deleted_columns) {
SparseDoubleMatrixProto result;
auto& result_row_ids = *result.mutable_row_ids();
auto& result_column_ids = *result.mutable_column_ids();
auto& result_coefficients = *result.mutable_coefficients();
// Contrary to rows that are traversed in order (the matrix is using row-major
// order), columns are not. Thus we would have to start the iteration on
// deleted_columns for each new row of the matrix if we wanted to use the same
// approach as with rows. This would be O(num_rows * num_deleted_columns).
//
// Here we use a hash-set to be O(num_matrix_elements +
// num_deleted_columns). The downside is that we consumed
// O(num_deleted_columns) additional memory.
//
// We could have used binary search that would be O(num_matrix_elements *
// lg(num_deleted_columns)) but without additional memory.
const absl::flat_hash_set<int64_t> deleted_columns_set(
deleted_columns.begin(), deleted_columns.end());
int from_new_i = 0;
int into_old_i = 0;
int deleted_rows_i = 0;
// Functions that adds the input tuple (row_id, col_id, coefficient) to the
// result if the input row_id and col_id are not in deleted_rows or
// deleted_columns. It updates deleted_rows_i and deleted_columns_i as a side
// effect too.
const auto add_if_not_deleted = [&](const int64_t row_id,
const int64_t col_id,
const double coefficient) {
while (deleted_rows_i < deleted_rows.size() &&
deleted_rows[deleted_rows_i] < row_id) {
++deleted_rows_i;
}
if ((deleted_rows_i != deleted_rows.size() &&
deleted_rows[deleted_rows_i] == row_id) ||
deleted_columns_set.contains(col_id)) {
return;
}
result_row_ids.Add(row_id);
result_column_ids.Add(col_id);
result_coefficients.Add(coefficient);
};
while (from_new_i < from_new.row_ids_size() &&
into_old_i < into_old.row_ids_size()) {
// Matrices are in row-major order and std::pair comparison is
// lexicographical, thus matrices are sorted in the natural order of pairs
// of coordinates (row, col).
const auto from_new_coordinates = std::make_pair(
from_new.row_ids(from_new_i), from_new.column_ids(from_new_i));
const auto into_old_coordinates = std::make_pair(
into_old.row_ids(into_old_i), into_old.column_ids(into_old_i));
if (from_new_coordinates < into_old_coordinates) {
add_if_not_deleted(from_new_coordinates.first,
from_new_coordinates.second,
from_new.coefficients(from_new_i));
++from_new_i;
} else if (from_new_coordinates > into_old_coordinates) {
add_if_not_deleted(into_old_coordinates.first,
into_old_coordinates.second,
into_old.coefficients(into_old_i));
++into_old_i;
} else { // from_new_coordinates == into_old_coordinates
add_if_not_deleted(from_new_coordinates.first,
from_new_coordinates.second,
from_new.coefficients(from_new_i));
++from_new_i;
++into_old_i;
}
}
// At this point either from_new_i == from_new.row_ids_size() or
// to_i == to.row_ids_size() (or both). And the one that is not empty, if it
// exists, has elements greater than all other elements already inserted.
for (; from_new_i < from_new.row_ids_size(); ++from_new_i) {
add_if_not_deleted(from_new.row_ids(from_new_i),
from_new.column_ids(from_new_i),
from_new.coefficients(from_new_i));
}
for (; into_old_i < into_old.row_ids_size(); ++into_old_i) {
add_if_not_deleted(into_old.row_ids(into_old_i),
into_old.column_ids(into_old_i),
into_old.coefficients(into_old_i));
}
into_old.Swap(&result);
}
} // namespace internal
} // namespace math_opt
} // namespace operations_research
} // namespace operations_research::math_opt

View File

@@ -0,0 +1,40 @@
// 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_STORAGE_MODEL_UPDATE_MERGE_H_
#define OR_TOOLS_MATH_OPT_STORAGE_MODEL_UPDATE_MERGE_H_
#include "ortools/math_opt/model_update.pb.h"
namespace operations_research::math_opt {
// Merges the `from_new` update into the `into_old` one.
//
// The `from_new` update must represent an update that happens after the
// `into_old` one is applied. Thus when the two updates have overlaps, the
// `from_new` one overrides the value of the `into_old` one (i.e. the `from_new`
// update is expected to be more recent).
//
// This function also CHECKs that the ids of new variables and constraints in
// `from_new` are greater than the ones in `into_old` (as expected if `from_new`
// happens after `into_old`).
//
// Note that the complexity is O(size(from_new) + size(into_old)) thus if you
// need to merge a long list of updates this may be not efficient enough. In
// that case an n-way merge would be needed to be implemented here.
void MergeIntoUpdate(const ModelUpdateProto& from_new,
ModelUpdateProto& into_old);
} // namespace operations_research::math_opt
#endif // OR_TOOLS_MATH_OPT_STORAGE_MODEL_UPDATE_MERGE_H_

View File

@@ -0,0 +1,182 @@
// 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/storage/proto_merging_utils.h"
#include <cstdint>
#include <utility>
#include "absl/container/flat_hash_set.h"
#include "ortools/math_opt/sparse_containers.pb.h"
namespace operations_research::math_opt {
void RemoveDeletedIds(google::protobuf::RepeatedField<int64_t>& ids,
const google::protobuf::RepeatedField<int64_t>& deleted) {
int next_insertion_point = 0;
int deleted_i = 0;
for (const int64_t id : ids) {
while (deleted_i < deleted.size() && deleted[deleted_i] < id) {
++deleted_i;
}
if (deleted_i < deleted.size() && deleted[deleted_i] == id) {
continue;
}
ids[next_insertion_point] = id;
++next_insertion_point;
}
ids.Truncate(next_insertion_point);
}
void MergeIntoSortedIds(
const google::protobuf::RepeatedField<int64_t>& from_new,
google::protobuf::RepeatedField<int64_t>& into_old,
const google::protobuf::RepeatedField<int64_t>& deleted) {
google::protobuf::RepeatedField<int64_t> result;
int from_new_i = 0;
int into_old_i = 0;
int deleted_i = 0;
// Functions that adds the input id to the result if it is not in deleted. It
// updates deleted_i as a side effect too.
const auto add_if_not_deleted = [&](const int64_t id) {
while (deleted_i < deleted.size() && deleted[deleted_i] < id) {
++deleted_i;
}
if (deleted_i == deleted.size() || deleted[deleted_i] != id) {
result.Add(id);
}
};
while (from_new_i < from_new.size() && into_old_i < into_old.size()) {
if (from_new[from_new_i] < into_old[into_old_i]) {
add_if_not_deleted(from_new[from_new_i]);
++from_new_i;
} else if (from_new[from_new_i] > into_old[into_old_i]) {
add_if_not_deleted(into_old[into_old_i]);
++into_old_i;
} else { // from_new[from_new_i] == into_old[into_old_i]
add_if_not_deleted(from_new[from_new_i]);
++from_new_i;
++into_old_i;
}
}
// At this point either from_new_i == from_new.size() or to_i == to.size() or
// both. And the one that is not empty, if it exists, has elements greater
// than all other elements already inserted.
for (; from_new_i < from_new.size(); ++from_new_i) {
add_if_not_deleted(from_new[from_new_i]);
}
for (; into_old_i < into_old.size(); ++into_old_i) {
add_if_not_deleted(into_old[into_old_i]);
}
into_old.Swap(&result);
}
void MergeIntoSparseDoubleMatrix(
const SparseDoubleMatrixProto& from_new, SparseDoubleMatrixProto& into_old,
const google::protobuf::RepeatedField<int64_t>& deleted_rows,
const google::protobuf::RepeatedField<int64_t>& deleted_columns) {
SparseDoubleMatrixProto result;
auto& result_row_ids = *result.mutable_row_ids();
auto& result_column_ids = *result.mutable_column_ids();
auto& result_coefficients = *result.mutable_coefficients();
// Contrary to rows that are traversed in order (the matrix is using row-major
// order), columns are not. Thus we would have to start the iteration on
// deleted_columns for each new row of the matrix if we wanted to use the same
// approach as with rows. This would be O(num_rows * num_deleted_columns).
//
// Here we use a hash-set to be O(num_matrix_elements +
// num_deleted_columns). The downside is that we consumed
// O(num_deleted_columns) additional memory.
//
// We could have used binary search that would be O(num_matrix_elements *
// lg(num_deleted_columns)) but without additional memory.
const absl::flat_hash_set<int64_t> deleted_columns_set(
deleted_columns.begin(), deleted_columns.end());
int from_new_i = 0;
int into_old_i = 0;
int deleted_rows_i = 0;
// Functions that adds the input tuple (row_id, col_id, coefficient) to the
// result if the input row_id and col_id are not in deleted_rows or
// deleted_columns. It updates deleted_rows_i and deleted_columns_i as a side
// effect too.
const auto add_if_not_deleted = [&](const int64_t row_id,
const int64_t col_id,
const double coefficient) {
while (deleted_rows_i < deleted_rows.size() &&
deleted_rows[deleted_rows_i] < row_id) {
++deleted_rows_i;
}
if ((deleted_rows_i != deleted_rows.size() &&
deleted_rows[deleted_rows_i] == row_id) ||
deleted_columns_set.contains(col_id)) {
return;
}
result_row_ids.Add(row_id);
result_column_ids.Add(col_id);
result_coefficients.Add(coefficient);
};
while (from_new_i < from_new.row_ids_size() &&
into_old_i < into_old.row_ids_size()) {
// Matrices are in row-major order and std::pair comparison is
// lexicographical, thus matrices are sorted in the natural order of pairs
// of coordinates (row, col).
const auto from_new_coordinates = std::make_pair(
from_new.row_ids(from_new_i), from_new.column_ids(from_new_i));
const auto into_old_coordinates = std::make_pair(
into_old.row_ids(into_old_i), into_old.column_ids(into_old_i));
if (from_new_coordinates < into_old_coordinates) {
add_if_not_deleted(from_new_coordinates.first,
from_new_coordinates.second,
from_new.coefficients(from_new_i));
++from_new_i;
} else if (from_new_coordinates > into_old_coordinates) {
add_if_not_deleted(into_old_coordinates.first,
into_old_coordinates.second,
into_old.coefficients(into_old_i));
++into_old_i;
} else { // from_new_coordinates == into_old_coordinates
add_if_not_deleted(from_new_coordinates.first,
from_new_coordinates.second,
from_new.coefficients(from_new_i));
++from_new_i;
++into_old_i;
}
}
// At this point either from_new_i == from_new.row_ids_size() or
// to_i == to.row_ids_size() (or both). And the one that is not empty, if it
// exists, has elements greater than all other elements already inserted.
for (; from_new_i < from_new.row_ids_size(); ++from_new_i) {
add_if_not_deleted(from_new.row_ids(from_new_i),
from_new.column_ids(from_new_i),
from_new.coefficients(from_new_i));
}
for (; into_old_i < into_old.row_ids_size(); ++into_old_i) {
add_if_not_deleted(into_old.row_ids(into_old_i),
into_old.column_ids(into_old_i),
into_old.coefficients(into_old_i));
}
into_old.Swap(&result);
}
} // namespace operations_research::math_opt

View File

@@ -11,39 +11,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef OR_TOOLS_MATH_OPT_CORE_MODEL_UPDATE_MERGE_H_
#define OR_TOOLS_MATH_OPT_CORE_MODEL_UPDATE_MERGE_H_
#ifndef OR_TOOLS_MATH_OPT_STORAGE_PROTO_MERGING_UTILS_H_
#define OR_TOOLS_MATH_OPT_STORAGE_PROTO_MERGING_UTILS_H_
#include <algorithm>
#include <cstdint>
#include "ortools/base/logging.h"
#include "ortools/base/protobuf_util.h"
#include "ortools/math_opt/core/sparse_vector_view.h"
#include "ortools/math_opt/model_update.pb.h"
#include "ortools/math_opt/sparse_containers.pb.h"
namespace operations_research {
namespace math_opt {
// Merges the `from_new` update into the `into_old` one.
//
// The `from_new` update must represent an update that happens after the
// `into_old` one is applied. Thus when the two updates have overlaps, the
// `from_new` one overrides the value of the `into_old` one (i.e. the `from_new`
// update is expected to be more recent).
//
// This function also CHECKs that the ids of new variables and constraints in
// `from_new` are greater than the ones in `into_old` (as expected if `from_new`
// happens after `into_old`).
//
// Note that the complexity is O(size(from_new) + size(into_old)) thus if you
// need to merge a long list of updates this may be not efficient enough. In
// that case an n-way merge would be needed to be implemented here.
void MergeIntoUpdate(const ModelUpdateProto& from_new,
ModelUpdateProto& into_old);
namespace internal {
namespace operations_research::math_opt {
// Removes from the sorted list `ids` all elements found in the sorted list
// `deleted`. The elements should be unique in each sorted list.
@@ -107,14 +85,10 @@ void UpdateNewElementProperty(
const google::protobuf::RepeatedField<int64_t>& deleted,
const SparseVector& updates);
} // namespace internal
////////////////////////////////////////////////////////////////////////////////
// Inline functions implementations.
////////////////////////////////////////////////////////////////////////////////
namespace internal {
template <typename SparseVector>
void MergeIntoSparseVector(
const SparseVector& from_new, SparseVector& into_old,
@@ -207,8 +181,6 @@ void UpdateNewElementProperty(
google::protobuf::util::Truncate(&values, next_insertion_point);
}
} // namespace internal
} // namespace math_opt
} // namespace operations_research
} // namespace operations_research::math_opt
#endif // OR_TOOLS_MATH_OPT_CORE_MODEL_UPDATE_MERGE_H_
#endif // OR_TOOLS_MATH_OPT_STORAGE_PROTO_MERGING_UTILS_H_

View File

@@ -0,0 +1,145 @@
// 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/storage/sparse_matrix.h"
#include "ortools/base/map_util.h"
namespace operations_research::math_opt {
namespace {
// When the fraction of entries in values_ with value 0.0 is larger than
// kZerosCleanup, we compact the data structure and remove all zero entries.
constexpr double kZerosCleanup = 1.0 / 3.0;
} // namespace
void SparseSymmetricMatrix::Delete(const VariableId variable) {
auto related_vars = related_variables_.find(variable);
if (related_vars == related_variables_.end()) {
return;
}
for (const VariableId related : related_vars->second) {
auto mat_value = values_.find(make_key(variable, related));
if (mat_value != values_.end() && mat_value->second != 0.0) {
nonzeros_--;
mat_value->second = 0.0;
}
}
CompactIfNeeded();
}
std::vector<VariableId> SparseSymmetricMatrix::RelatedVariables(
const VariableId variable) const {
std::vector<VariableId> result;
if (!related_variables_.contains(variable)) {
return result;
}
for (const VariableId second : related_variables_.at(variable)) {
if (get(variable, second) != 0) {
result.push_back(second);
}
}
return result;
}
std::vector<std::pair<VariableId, double>> SparseSymmetricMatrix::Terms(
const VariableId variable) const {
std::vector<std::pair<VariableId, double>> result;
if (!related_variables_.contains(variable)) {
return result;
}
for (const VariableId second : related_variables_.at(variable)) {
double val = get(variable, second);
if (val != 0) {
result.push_back({second, val});
}
}
return result;
}
std::vector<std::tuple<VariableId, VariableId, double>>
SparseSymmetricMatrix::Terms() const {
std::vector<std::tuple<VariableId, VariableId, double>> result;
result.reserve(nonzeros_);
for (const auto& [var_pair, value] : values_) {
if (value != 0.0) {
result.push_back({var_pair.first, var_pair.second, value});
}
}
return result;
}
void SparseSymmetricMatrix::CompactIfNeeded() {
const int64_t zeros = values_.size() - nonzeros_;
if (static_cast<double>(zeros) / values_.size() <= kZerosCleanup) {
return;
}
++compactions_;
for (auto related_var_it = related_variables_.begin();
related_var_it != related_variables_.end();) {
const VariableId v = related_var_it->first;
std::vector<VariableId>& related = related_var_it->second;
int64_t write = 0;
for (int read = 0; read < related.size(); ++read) {
auto val = values_.find(make_key(v, related[read]));
if (val != values_.end()) {
if (val->second != 0) {
related[write] = related[read];
++write;
} else {
values_.erase(val);
}
}
}
if (write == 0) {
related_variables_.erase(related_var_it++);
} else {
related.resize(write);
++related_var_it;
}
}
}
void SparseSymmetricMatrix::Clear() {
related_variables_.clear();
values_.clear();
nonzeros_ = 0;
}
SparseDoubleMatrixProto SparseSymmetricMatrix::Proto() const {
SparseDoubleMatrixProto result;
std::vector<VariableId> vars_in_order;
for (const auto& [v, _] : related_variables_) {
vars_in_order.push_back(v);
}
absl::c_sort(vars_in_order);
for (const VariableId v : vars_in_order) {
// TODO(b/233630053): reuse the allocation once an iterator API is
// supported.
std::vector<std::pair<VariableId, double>> related = Terms(v);
absl::c_sort(related);
for (const auto [other, coef] : related) {
if (v <= other) {
result.add_row_ids(v.value());
result.add_column_ids(other.value());
result.add_coefficients(coef);
}
}
}
return result;
}
} // namespace operations_research::math_opt

View File

@@ -0,0 +1,217 @@
// 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.
// Classes for modeling sparse matrices.
#ifndef OR_TOOLS_MATH_OPT_STORAGE_SPARSE_MATRIX_H_
#define OR_TOOLS_MATH_OPT_STORAGE_SPARSE_MATRIX_H_
#include <algorithm>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "ortools/base/map_util.h"
#include "ortools/base/strong_int.h"
#include "ortools/base/strong_vector.h"
#include "ortools/math_opt/sparse_containers.pb.h"
#include "ortools/math_opt/storage/model_storage_types.h"
namespace operations_research::math_opt {
// A sparse symmetric double valued matrix over VariableIds.
//
// Note that the matrix is sparse in both:
// * The IDs of the rows/columns (both VariableIds), stored as flat_hash_map.
// * The entries with nonzero value.
//
// Getting/setting/clearing entries are O(1) operations. Getting a row of the
// matrix runs in O(size of the row) if nothing has been deleted, and getting
// all the rows runs in O(number of nonzero entries), even with deletions
// (with deletions, accessing a particular row with many deletions may be slow).
//
// Implementation: The entries are stored in a
// flat_hash_map<pair<VariableId, VariableId>, double> `values_` where for each
// key, key.first <= key.second. Additionally, we maintain a
// flat_hash_map<VariableId, vector<VariableId>> `related_variables_` that says
// for each variable, which variables they have a nonzero term with. When a
// coefficient is set to zero or a variable is deleted, we do not immediately
// delete the data from values_ or related_variables_, we simply set the
// coefficient to zero in values_. We track how many zeros are in values_, and
// when more than some constant fraction of all entries are zero (see
// kZerosCleanup in cc file), we clean up related_variables_ and values_ to
// remove all the zeros. Iteration over the rows or total entries of the matrix
// must check for zeros in values_ and skip these terms.
//
// Memory use:
// * 3*8 bytes per nonzero plus hash capacity overhead for values_.
// * 2*8 bytes per nonzero plus vector capacity overhead for
// related_variables_.
// * ~5*8 bytes per variable participating in any quadratic term; one heap
// allocation per such variable.
class SparseSymmetricMatrix {
public:
// Setting `value` to zero removes the value from the matrix.
// Returns true if `value` is different from the existing value in the matrix.
inline bool set(VariableId first, VariableId second, double value);
// Zero is returned if the value is not present.
inline double get(VariableId first, VariableId second) const;
// Zeros out all coefficients for this variable.
void Delete(VariableId variable);
// Returns the variables that have nonzero entries with `variable`.
//
// The return order is deterministic but not defined.
// TODO(b/233630053): expose an iterator based API to avoid making a copy.
std::vector<VariableId> RelatedVariables(VariableId variable) const;
// Returns the variable value pairs (x, c) where `variable` and x have nonzero
// coefficient c.
//
// The return order is deterministic but not defined.
// TODO(b/233630053): expose an iterator based API to avoid making a copy.
std::vector<std::pair<VariableId, double>> Terms(VariableId variable) const;
// Returns (x, y, c) tuples where variables x and y have nonzero coefficient
// c, and x <= y.
//
// The return order is non-deterministic and not defined.
// TODO(b/233630053): expose an iterator based API to avoid making a copy.
std::vector<std::tuple<VariableId, VariableId, double>> Terms() const;
// Removes all terms from the matrix.
void Clear();
// The number of (var, var) keys with nonzero value. Note that (x, y) and
// (y, x) are the same key.
int64_t nonzeros() const { return nonzeros_; }
// Visible for testing, do not depend on this.
int64_t compactions() const { return compactions_; }
// TODO(b/233630053): do not expose values_ directly, instead offer a way to
// iterate over all the nonzero entries.
// Warning: this map will contain zeros.
const absl::flat_hash_map<std::pair<VariableId, VariableId>, double>& values()
const {
return values_;
}
SparseDoubleMatrixProto Proto() const;
template <typename VarContainer>
inline SparseDoubleMatrixProto Update(
const VarContainer& variables, VariableId checkpoint, VariableId next_var,
const absl::flat_hash_set<std::pair<VariableId, VariableId>>& dirty)
const;
private:
inline std::pair<VariableId, VariableId> make_key(VariableId first,
VariableId second) const;
void CompactIfNeeded();
// The keys of values_ have key.first <= key.second.
absl::flat_hash_map<std::pair<VariableId, VariableId>, double> values_;
absl::flat_hash_map<VariableId, std::vector<VariableId>> related_variables_;
// The number of nonzero elements in values_.
int64_t nonzeros_ = 0;
int64_t compactions_ = 0;
};
////////////////////////////////////////////////////////////////////////////////
// Inlined functions
////////////////////////////////////////////////////////////////////////////////
std::pair<VariableId, VariableId> SparseSymmetricMatrix::make_key(
const VariableId first, const VariableId second) const {
return {std::min(first, second), std::max(first, second)};
}
bool SparseSymmetricMatrix::set(const VariableId first, const VariableId second,
const double value) {
const std::pair<VariableId, VariableId> key = make_key(first, second);
auto map_iter = values_.find(key);
if (map_iter == values_.end()) {
if (value == 0.0) {
return false;
}
related_variables_[first].push_back(second);
if (first != second) {
related_variables_[second].push_back(first);
}
values_[key] = value;
nonzeros_++;
return true;
} else {
if (map_iter->second == value) {
return false;
}
const double old_value = map_iter->second;
map_iter->second = value;
if (value == 0.0) {
nonzeros_--;
CompactIfNeeded();
} else if (old_value == 0.0) {
nonzeros_++;
}
return true;
}
}
double SparseSymmetricMatrix::get(const VariableId first,
const VariableId second) const {
return gtl::FindWithDefault(values_, make_key(first, second));
}
template <typename VarContainer>
SparseDoubleMatrixProto SparseSymmetricMatrix::Update(
const VarContainer& variables, const VariableId checkpoint,
const VariableId next_var,
const absl::flat_hash_set<std::pair<VariableId, VariableId>>& dirty) const {
std::vector<std::pair<VariableId, VariableId>> updates;
for (const std::pair<VariableId, VariableId> pair : dirty) {
// If either variable has been deleted, don't add it.
if (variables.contains(pair.first) && variables.contains(pair.second)) {
updates.push_back(pair);
}
}
for (const VariableId v : MakeStrongIntRange(checkpoint, next_var)) {
if (related_variables_.contains(v)) {
// TODO(b/233630053): do not allocate here.
for (const VariableId other : RelatedVariables(v)) {
if (v <= other) {
updates.push_back({v, other});
} else if (other < checkpoint) {
updates.push_back({other, v});
}
}
}
}
absl::c_sort(updates);
SparseDoubleMatrixProto result;
for (const auto [row, col] : updates) {
result.add_row_ids(row.value());
result.add_column_ids(col.value());
result.add_coefficients(get(row, col));
}
return result;
}
} // namespace operations_research::math_opt
#endif // OR_TOOLS_MATH_OPT_STORAGE_SPARSE_MATRIX_H_

View File

@@ -67,6 +67,7 @@ cc_library(
"//ortools/math_opt:model_cc_proto",
"//ortools/math_opt:model_update_cc_proto",
"//ortools/math_opt:sparse_containers_cc_proto",
"//ortools/math_opt/constraints/sos:validator",
"//ortools/math_opt/core:model_summary",
"//ortools/math_opt/core:sparse_vector_view",
"@com_google_absl//absl/status",
@@ -189,3 +190,17 @@ cc_library(
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "linear_expression_validator",
srcs = ["linear_expression_validator.cc"],
hdrs = ["linear_expression_validator.h"],
deps = [
":scalar_validator",
":sparse_vector_validator",
"//ortools/math_opt:sparse_containers_cc_proto",
"//ortools/math_opt/core:model_summary",
"//ortools/math_opt/core:sparse_vector_view",
"@com_google_absl//absl/status",
],
)

View File

@@ -0,0 +1,45 @@
// 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/linear_expression_validator.h"
#include <cstdint>
#include "absl/status/status.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/sparse_containers.pb.h"
#include "ortools/math_opt/validators/scalar_validator.h"
#include "ortools/math_opt/validators/sparse_vector_validator.h"
namespace operations_research::math_opt {
absl::Status ValidateLinearExpression(const LinearExpressionProto& expression,
const IdNameBiMap& variable_universe) {
RETURN_IF_ERROR(CheckIdsAndValues(
MakeView(expression.ids(), expression.coefficients()),
{.allow_positive_infinity = false, .allow_negative_infinity = false}))
<< "invalid linear expression terms";
for (const int64_t var_id : expression.ids()) {
if (!variable_universe.HasId(var_id)) {
return util::InvalidArgumentErrorBuilder()
<< "invalid variable id: " << var_id;
}
}
RETURN_IF_ERROR(CheckScalarNoNanNoInf(expression.offset()))
<< "invalid linear expression offset";
return absl::OkStatus();
}
} // namespace operations_research::math_opt

View File

@@ -0,0 +1,28 @@
// 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_LINEAR_EXPRESSION_VALIDATOR_H_
#define OR_TOOLS_MATH_OPT_VALIDATORS_LINEAR_EXPRESSION_VALIDATOR_H_
#include "absl/status/status.h"
#include "ortools/math_opt/core/model_summary.h"
#include "ortools/math_opt/sparse_containers.pb.h"
namespace operations_research::math_opt {
absl::Status ValidateLinearExpression(const LinearExpressionProto& expression,
const IdNameBiMap& variable_universe);
} // namespace operations_research::math_opt
#endif // OR_TOOLS_MATH_OPT_VALIDATORS_LINEAR_EXPRESSION_VALIDATOR_H_

View File

@@ -13,15 +13,13 @@
#include "ortools/math_opt/validators/model_validator.h"
#include <cmath>
#include <cstdint>
#include <limits>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "absl/status/statusor.h"
#include "ortools/base/status_macros.h"
#include "ortools/math_opt/constraints/sos/validator.h"
#include "ortools/math_opt/core/model_summary.h"
#include "ortools/math_opt/core/sparse_vector_view.h"
#include "ortools/math_opt/model.pb.h"
@@ -168,6 +166,20 @@ absl::Status LinearConstraintMatrixIdsValidForUpdate(
return absl::OkStatus();
}
// To use this helper, you must implement an overload for:
// ValidateConstraint(const MyConstraintProto& constraint,
// const IdNameBiMap& variable_universe);
template <typename ConstraintType>
absl::Status ValidateConstraintMap(
const google::protobuf::Map<int64_t, ConstraintType>& constraints,
const IdNameBiMap& variable_universe) {
for (const auto& [id, constraint] : constraints) {
RETURN_IF_ERROR(ValidateConstraint(constraint, variable_universe))
<< "invalid constraint with id: " << id;
}
return absl::OkStatus();
}
} // namespace
// /////////////////////////////////////////////////////////////////////////////
@@ -190,6 +202,14 @@ absl::StatusOr<ModelSummary> ValidateModel(const ModelProto& model,
model_summary.linear_constraints,
model_summary.variables))
<< "Model.linear_constraint_matrix ids are inconsistent";
RETURN_IF_ERROR(
ValidateConstraintMap(model.sos1_constraints(), model_summary.variables))
<< "Model.sos1_constraints invalid";
RETURN_IF_ERROR(
ValidateConstraintMap(model.sos2_constraints(), model_summary.variables))
<< "Model.sos2_constraints invalid";
return model_summary;
}
@@ -230,6 +250,15 @@ absl::Status ValidateModelUpdate(const ModelUpdateProto& model_update,
model_summary.linear_constraints, model_summary.variables))
<< "invalid linear constraint matrix update";
RETURN_IF_ERROR(ValidateConstraintMap(
model_update.sos1_constraint_updates().new_constraints(),
model_summary.variables))
<< "ModelUpdateProto.sos1_constraint_updates.new_constraints invalid";
RETURN_IF_ERROR(ValidateConstraintMap(
model_update.sos2_constraint_updates().new_constraints(),
model_summary.variables))
<< "ModelUpdateProto.sos2_constraint_updates.new_constraints invalid";
return absl::OkStatus();
}

View File

@@ -15,6 +15,7 @@
#define OR_TOOLS_MATH_OPT_VALIDATORS_MODEL_VALIDATOR_H_
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "ortools/math_opt/core/model_summary.h"
#include "ortools/math_opt/model.pb.h"
#include "ortools/math_opt/model_update.pb.h"