From 0f16563548c32030e6874a29e35b43d435b86c45 Mon Sep 17 00:00:00 2001 From: Corentin Le Molgat Date: Wed, 22 Jun 2022 17:49:58 +0200 Subject: [PATCH] math_opt: update --- .../constraints/quadratic/validator.cc | 2 +- ortools/math_opt/cpp/BUILD.bazel | 7 ++ ortools/math_opt/cpp/formatters.h | 88 +++++++++++++++++++ ortools/math_opt/cpp/model.cc | 49 ++++++++++- ortools/math_opt/cpp/model.h | 5 ++ .../math_opt/cpp/variable_and_expressions.cc | 68 +------------- ortools/math_opt/solvers/glpk/rays.cc | 4 +- ortools/math_opt/solvers/gurobi/g_gurobi.cc | 4 +- ortools/math_opt/solvers/gurobi/g_gurobi.h | 3 +- ortools/math_opt/tools/mathopt_solve_main.cc | 25 +----- 10 files changed, 157 insertions(+), 98 deletions(-) create mode 100644 ortools/math_opt/cpp/formatters.h diff --git a/ortools/math_opt/constraints/quadratic/validator.cc b/ortools/math_opt/constraints/quadratic/validator.cc index 5ec007021a..8420ab7fb7 100644 --- a/ortools/math_opt/constraints/quadratic/validator.cc +++ b/ortools/math_opt/constraints/quadratic/validator.cc @@ -16,6 +16,7 @@ #include #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/model.pb.h" @@ -24,7 +25,6 @@ #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" -#include "ortools/base/status_macros.h" namespace operations_research::math_opt { diff --git a/ortools/math_opt/cpp/BUILD.bazel b/ortools/math_opt/cpp/BUILD.bazel index 61330f6213..6d0e5236b6 100644 --- a/ortools/math_opt/cpp/BUILD.bazel +++ b/ortools/math_opt/cpp/BUILD.bazel @@ -66,6 +66,7 @@ cc_library( srcs = ["model.cc"], hdrs = ["model.h"], deps = [ + ":formatters", ":key_types", ":linear_constraint", ":update_tracker", @@ -104,6 +105,7 @@ cc_library( srcs = ["variable_and_expressions.cc"], hdrs = ["variable_and_expressions.h"], deps = [ + ":formatters", ":id_map", ":key_types", "//ortools/base", @@ -390,3 +392,8 @@ cc_library( "//ortools/math_opt/storage:model_storage", ], ) + +cc_library( + name = "formatters", + hdrs = ["formatters.h"], +) diff --git a/ortools/math_opt/cpp/formatters.h b/ortools/math_opt/cpp/formatters.h new file mode 100644 index 0000000000..7d3a3a5747 --- /dev/null +++ b/ortools/math_opt/cpp/formatters.h @@ -0,0 +1,88 @@ +// Copyright 2010-2022 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_FORMATTERS_H_ +#define OR_TOOLS_MATH_OPT_CPP_FORMATTERS_H_ + +#include +#include + +namespace operations_research::math_opt { + +// Streaming formatter for a coefficient of a linear/quadratic term, along with +// any leading "+"/"-"'s to connect it with preceding terms in a sum, and +// potentially a "*" postfix. The `is_first` parameter specifies if the term is +// the first appearing in the sum, in which case the handling of the +/- +// connectors is different. +struct LeadingCoefficientFormatter { + LeadingCoefficientFormatter(const double coeff, const bool is_first) + : coeff(coeff), is_first(is_first) {} + const double coeff; + const bool is_first; +}; + +inline std::ostream& operator<<(std::ostream& out, + const LeadingCoefficientFormatter formatter) { + const double coeff = formatter.coeff; + if (formatter.is_first) { + if (coeff == 1.0) { + // Do nothing. + } else if (coeff == -1.0) { + out << "-"; + } else { + out << coeff << "*"; + } + } else { + if (coeff == 1.0) { + out << " + "; + } else if (coeff == -1.0) { + out << " - "; + } else if (std::isnan(coeff)) { + out << " + nan*"; + } else if (coeff >= 0) { + out << " + " << coeff << "*"; + } else { + out << " - " << -coeff << "*"; + } + } + return out; +} + +// Streaming formatter for a constant in a linear/quadratic expression. +struct ConstantFormatter { + ConstantFormatter(const double constant, const bool is_first) + : constant(constant), is_first(is_first) {} + const double constant; + const bool is_first; +}; + +inline std::ostream& operator<<(std::ostream& out, + const ConstantFormatter formatter) { + const double constant = formatter.constant; + if (formatter.is_first) { + out << constant; + } else if (constant == 0) { + // Do nothing. + } else if (std::isnan(constant)) { + out << " + nan"; + } else if (constant > 0) { + out << " + " << constant; + } else { + out << " - " << -constant; + } + return out; +} + +} // namespace operations_research::math_opt + +#endif // OR_TOOLS_MATH_OPT_CPP_FORMATTERS_H_ diff --git a/ortools/math_opt/cpp/model.cc b/ortools/math_opt/cpp/model.cc index 5e344adcbb..77bf94053d 100644 --- a/ortools/math_opt/cpp/model.cc +++ b/ortools/math_opt/cpp/model.cc @@ -14,23 +14,28 @@ #include "ortools/math_opt/cpp/model.h" #include +#include #include +#include +#include #include #include #include "absl/container/flat_hash_map.h" -#include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "ortools/base/logging.h" #include "ortools/base/status_macros.h" #include "ortools/base/strong_int.h" +#include "ortools/math_opt/cpp/linear_constraint.h" #include "ortools/math_opt/storage/model_storage.h" namespace operations_research { namespace math_opt { +constexpr double kInf = std::numeric_limits::infinity(); + absl::StatusOr> Model::FromModelProto( const ModelProto& model_proto) { ASSIGN_OR_RETURN(std::unique_ptr storage, @@ -220,5 +225,47 @@ absl::Status Model::ApplyUpdateProto(const ModelUpdateProto& update_proto) { return storage()->ApplyUpdateProto(update_proto); } +std::ostream& operator<<(std::ostream& ostr, const Model& model) { + ostr << "Model"; + if (!model.name().empty()) ostr << " " << model.name(); + ostr << ":\n"; + + ostr << " Objective:\n" + << (model.is_maximize() ? " maximize " : " minimize ") + << model.ObjectiveAsQuadraticExpression() << "\n"; + + ostr << " Linear constraints:\n"; + for (const LinearConstraint constraint : model.SortedLinearConstraints()) { + ostr << " " << constraint << ": " + << model.AsBoundedLinearExpression(constraint) << "\n"; + } + + ostr << " Variables:\n"; + for (const Variable v : model.SortedVariables()) { + ostr << " " << v; + if (v.is_integer()) { + if (v.lower_bound() == 0 && v.upper_bound() == 1) { + ostr << " (binary)\n"; + continue; + } + ostr << " (integer)"; + } + ostr << " in "; + if (v.lower_bound() == -kInf) { + ostr << "(-∞"; + } else { + ostr << "[" << v.lower_bound(); + } + ostr << ", "; + if (v.upper_bound() == kInf) { + ostr << "+∞)"; + } else { + ostr << v.upper_bound() << "]"; + } + ostr << "\n"; + } + return ostr; +} + } // namespace math_opt } // namespace operations_research diff --git a/ortools/math_opt/cpp/model.h b/ortools/math_opt/cpp/model.h index 4e56e7b510..943509090d 100644 --- a/ortools/math_opt/cpp/model.h +++ b/ortools/math_opt/cpp/model.h @@ -455,6 +455,11 @@ class Model { // it. ModelStorage* storage() { return storage_.get(); } + // Prints the objective, the constraints and the variables of the model over + // several lines in a human-readable way. Includes a new line at the end of + // the model. + friend std::ostream& operator<<(std::ostream& ostr, const Model& model); + private: // Asserts (with CHECK) that the input pointer is either nullptr or that it // points to the same model as storage_. diff --git a/ortools/math_opt/cpp/variable_and_expressions.cc b/ortools/math_opt/cpp/variable_and_expressions.cc index e8b3b47211..212c9e47fb 100644 --- a/ortools/math_opt/cpp/variable_and_expressions.cc +++ b/ortools/math_opt/cpp/variable_and_expressions.cc @@ -22,6 +22,7 @@ #include "ortools/base/logging.h" #include "ortools/base/map_util.h" #include "ortools/base/strong_int.h" +#include "ortools/math_opt/cpp/formatters.h" // IWYU pragma: export namespace operations_research { namespace math_opt { @@ -92,73 +93,6 @@ double LinearExpression::EvaluateWithDefaultZero( return result; } -namespace { - -// Streaming formatter for a coefficient of a linear/quadratic term, along with -// any leading "+"/"-"'s to connect it with preceding terms in a sum, and -// potentially a "*" postfix. The `is_first` parameter specifies if the term is -// the first appearing in the sum, in which case the handling of the +/- -// connectors is different. -struct LeadingCoefficientFormatter { - LeadingCoefficientFormatter(const double coeff, const bool is_first) - : coeff(coeff), is_first(is_first) {} - const double coeff; - const bool is_first; -}; - -std::ostream& operator<<(std::ostream& out, - const LeadingCoefficientFormatter formatter) { - const double coeff = formatter.coeff; - if (formatter.is_first) { - if (coeff == 1.0) { - // Do nothing. - } else if (coeff == -1.0) { - out << "-"; - } else { - out << coeff << "*"; - } - } else { - if (coeff == 1.0) { - out << " + "; - } else if (coeff == -1.0) { - out << " - "; - } else if (std::isnan(coeff)) { - out << " + nan*"; - } else if (coeff >= 0) { - out << " + " << coeff << "*"; - } else { - out << " - " << -coeff << "*"; - } - } - return out; -} - -// Streaming formatter for a constant in a linear/quadratic expression. -struct ConstantFormatter { - ConstantFormatter(const double constant, const bool is_first) - : constant(constant), is_first(is_first) {} - const double constant; - const bool is_first; -}; - -std::ostream& operator<<(std::ostream& out, const ConstantFormatter formatter) { - const double constant = formatter.constant; - if (formatter.is_first) { - out << constant; - } else if (constant == 0) { - // Do nothing. - } else if (std::isnan(constant)) { - out << " + nan"; - } else if (constant > 0) { - out << " + " << constant; - } else { - out << " - " << -constant; - } - return out; -} - -} // namespace - std::ostream& operator<<(std::ostream& ostr, const LinearExpression& expression) { // TODO(b/169415597): improve linear expression format: diff --git a/ortools/math_opt/solvers/glpk/rays.cc b/ortools/math_opt/solvers/glpk/rays.cc index cad200350a..c050e3c719 100644 --- a/ortools/math_opt/solvers/glpk/rays.cc +++ b/ortools/math_opt/solvers/glpk/rays.cc @@ -18,13 +18,13 @@ #include #include -#include "ortools/base/logging.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" +#include "ortools/base/logging.h" +#include "ortools/base/status_macros.h" #include "ortools/glpk/glpk_computational_form.h" #include "ortools/glpk/glpk_formatters.h" -#include "ortools/base/status_macros.h" namespace operations_research::math_opt { namespace { diff --git a/ortools/math_opt/solvers/gurobi/g_gurobi.cc b/ortools/math_opt/solvers/gurobi/g_gurobi.cc index f184882f87..4390baef8b 100644 --- a/ortools/math_opt/solvers/gurobi/g_gurobi.cc +++ b/ortools/math_opt/solvers/gurobi/g_gurobi.cc @@ -17,11 +17,11 @@ #include #include -#include "ortools/base/logging.h" -#include "ortools/base/cleanup.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_format.h" +#include "ortools/base/cleanup.h" +#include "ortools/base/logging.h" #include "ortools/base/source_location.h" #include "ortools/base/status_builder.h" #include "ortools/base/status_macros.h" diff --git a/ortools/math_opt/solvers/gurobi/g_gurobi.h b/ortools/math_opt/solvers/gurobi/g_gurobi.h index 3ad8266ddb..733848c778 100644 --- a/ortools/math_opt/solvers/gurobi/g_gurobi.h +++ b/ortools/math_opt/solvers/gurobi/g_gurobi.h @@ -40,9 +40,8 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "ortools/base/source_location.h" #include "absl/types/span.h" - +#include "ortools/base/source_location.h" #include "ortools/gurobi/environment.h" namespace operations_research::math_opt { diff --git a/ortools/math_opt/tools/mathopt_solve_main.cc b/ortools/math_opt/tools/mathopt_solve_main.cc index f4838b060a..75206d87c6 100644 --- a/ortools/math_opt/tools/mathopt_solve_main.cc +++ b/ortools/math_opt/tools/mathopt_solve_main.cc @@ -197,28 +197,6 @@ absl::StatusOr ReadModelUpdate( absl::StrCat("invalid format in ReadModelUpdate(): ", format)); } -// Prints the model. -void PrintModel(const Model& model) { - if (model.is_maximize()) { - std::cout << "max"; - } else { - std::cout << "min"; - } - std::cout << ' ' << model.ObjectiveAsQuadraticExpression() << std::endl; - - std::cout << "variables:" << std::endl; - for (const Variable v : model.SortedVariables()) { - std::cout << ' ' << v.lower_bound() << " ≤ " << v << " ≤ " - << v.upper_bound() << std::endl; - } - - std::cout << "constraints:" << std::endl; - for (const LinearConstraint c : model.SortedLinearConstraints()) { - std::cout << ' ' << c << ": " << model.AsBoundedLinearExpression(c) - << std::endl; - } -} - // Prints the summary of the solve result. absl::Status PrintSummary(const SolveResult& result) { std::cout << "Solve finished:\n" @@ -306,7 +284,8 @@ absl::Status RunSolver() { // Optionally prints the problem. if (absl::GetFlag(FLAGS_print_model)) { - PrintModel(*model); + std::cout << model; + std::cout.flush(); } // Solve the problem.