big sync with internal code; mostly code reformating; a few fixes for CP-SAT; more work on bandit based concatenators for the routing library

This commit is contained in:
Laurent Perron
2020-11-02 18:48:31 +01:00
parent d2147306e2
commit a2dc7e9a8e
23 changed files with 85 additions and 178 deletions

View File

@@ -478,7 +478,7 @@ class FilteredHeuristicExpensiveChainLNSOperator
int last_route_;
const int num_arcs_to_consider_;
std::vector<std::pair<int64, int> > most_expensive_arc_starts_and_ranks_;
std::vector<std::pair<int64, int>> most_expensive_arc_starts_and_ranks_;
/// Indices in most_expensive_arc_starts_and_ranks_ corresponding to the first
/// and second arcs currently being considered for removal.
std::pair</*first_arc_index*/ int, /*second_arc_index*/ int>
@@ -531,14 +531,14 @@ class FilteredHeuristicCloseNodesLNSOperator
std::vector<int64> GetActiveSiblings(int64 node) const;
const std::vector<std::pair<std::vector<int64>, std::vector<int64> > >&
const std::vector<std::pair<std::vector<int64>, std::vector<int64>>>&
pickup_delivery_pairs_;
int current_node_;
int last_node_;
bool just_started_;
std::vector<std::vector<int64> > close_nodes_;
std::vector<std::vector<int64>> close_nodes_;
/// Keep track of changes when making a neighbor.
std::vector<int64> new_nexts_;
SparseBitset<> changed_nexts_;
@@ -578,7 +578,7 @@ class RelocateExpensiveChain : public PathOperator {
int num_arcs_to_consider_;
int current_path_;
std::vector<std::pair<int64, int> > most_expensive_arc_starts_and_ranks_;
std::vector<std::pair<int64, int>> most_expensive_arc_starts_and_ranks_;
/// Indices in most_expensive_arc_starts_and_ranks_ corresponding to the first
/// and second arcs currently being considered for removal.
std::pair</*first_arc_index*/ int, /*second_arc_index*/ int>

View File

@@ -1115,6 +1115,13 @@ using CallMap = absl::flat_hash_map<
std::string, std::function<bool(const Constraint& ct,
std::function<int64(IntegerVariable*)>)>>;
// Creates a map between flatzinc predicates and CP-SAT builders.
//
// Predicates starting with fzn_ are predicates with the same name in flatzinc
// and in minizinc. The fzn_ prefix is added to differentiate them.
//
// Predicates starting with ortools_ are predicates defined only in or-tools.
// They are created at compilation time when using the or-tools mzn library.
CallMap CreateCallMap() {
CallMap m;
m["fzn_all_different_int"] = CheckAllDifferentInt;

View File

@@ -109,6 +109,7 @@ std::vector<char*> FixAndParseParameters(int* argc, char*** argv) {
}
const char kUsage[] =
"Usage: see flags.\nThis program parses and solve a flatzinc problem.";
absl::SetProgramUsageMessage(kUsage);
const std::vector<char*> residual_flags =
absl::ParseCommandLine(*argc, *argv);

View File

@@ -7,4 +7,3 @@ predicate fzn_diffn(array[int] of var int: x,
array[int] of var int: y,
array[int] of var int: dx,
array[int] of var int: dy);

View File

@@ -4,5 +4,5 @@ predicate ortools_network_flow(array [int] of int: arc,
predicate fzn_network_flow(array [int,int] of int: arc,
array [int] of int: balance,
array [int] of var int: flow) =
array [int] of var int: flow) =
ortools_network_flow(array1d(arc),balance,flow);

View File

@@ -17,6 +17,7 @@
#include <string>
#include "absl/flags/flag.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/timer.h"
#include "ortools/flatzinc/logging.h"

View File

@@ -71,12 +71,14 @@ class ChristofidesPathSolver {
void Solve();
// Safe addition operator to avoid overflows when possible.
// template <typename T>
// T SafeAdd(T a, T b) {
// return a + b;
// }
// template <>
int64 SafeAdd(int64 a, int64 b) { return CapAdd(a, b); }
template <typename T>
T SafeAdd(T a, T b) {
return a + b;
}
template <>
int64 SafeAdd(int64 a, int64 b) {
return CapAdd(a, b);
}
// Matching algorithm to use.
MatchingAlgorithm matching_;

View File

@@ -21,9 +21,9 @@
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/inlined_vector.h"
#include "ortools/base/hash.h"
#include "ortools/base/map_util.h"

View File

@@ -75,7 +75,6 @@ class BopInterface : public MPSolverInterface {
// ------ Query statistics on the solution and the solve ------
int64 iterations() const override;
int64 nodes() const override;
double best_objective_bound() const override;
MPSolver::BasisStatus row_status(int constraint_index) const override;
MPSolver::BasisStatus column_status(int variable_index) const override;
@@ -110,7 +109,6 @@ class BopInterface : public MPSolverInterface {
std::vector<MPSolver::BasisStatus> column_status_;
std::vector<MPSolver::BasisStatus> row_status_;
bop::BopParameters parameters_;
double best_objective_bound_;
std::atomic<bool> interrupt_solver_;
};
@@ -262,13 +260,6 @@ int64 BopInterface::nodes() const {
return kUnknownNumberOfNodes;
}
double BopInterface::best_objective_bound() const {
if (!CheckSolutionIsSynchronized() || !CheckBestObjectiveBoundExists()) {
return trivial_worst_objective_bound();
}
return best_objective_bound_;
}
MPSolver::BasisStatus BopInterface::row_status(int constraint_index) const {
return row_status_[constraint_index];
}

View File

@@ -108,8 +108,6 @@ class CBCInterface : public MPSolverInterface {
int64 iterations() const override;
// Number of branch-and-bound nodes. Only available for discrete problems.
int64 nodes() const override;
// Best objective bound. Only available for discrete problems.
double best_objective_bound() const override;
// Returns the basis status of a row.
MPSolver::BasisStatus row_status(int constraint_index) const override {
@@ -152,7 +150,6 @@ class CBCInterface : public MPSolverInterface {
// TODO(user): remove and query number of iterations directly from CbcModel
int64 iterations_;
int64 nodes_;
double best_objective_bound_;
// Special way to handle the relative MIP gap parameter.
double relative_mip_gap_;
int num_threads_ = 1;
@@ -165,7 +162,6 @@ CBCInterface::CBCInterface(MPSolver* const solver)
: MPSolverInterface(solver),
iterations_(0),
nodes_(0),
best_objective_bound_(-std::numeric_limits<double>::infinity()),
relative_mip_gap_(MPSolverParameters::kDefaultRelativeMipGap) {
osi_.setStrParam(OsiProbName, solver_->name_);
osi_.setObjSense(1);
@@ -481,13 +477,6 @@ int64 CBCInterface::nodes() const {
return nodes_;
}
double CBCInterface::best_objective_bound() const {
if (!CheckSolutionIsSynchronized() || !CheckBestObjectiveBoundExists()) {
return trivial_worst_objective_bound();
}
return best_objective_bound_;
}
// ----- Parameters -----
// The support for parameters in CBC is intentionally sparse. There is

View File

@@ -85,8 +85,6 @@ class CLPInterface : public MPSolverInterface {
int64 iterations() const override;
// Number of branch-and-bound nodes. Only available for discrete problems.
int64 nodes() const override;
// Best objective bound. Only available for discrete problems.
double best_objective_bound() const override;
// Returns the basis status of a row.
MPSolver::BasisStatus row_status(int constraint_index) const override;
@@ -544,11 +542,6 @@ int64 CLPInterface::nodes() const {
return kUnknownNumberOfNodes;
}
double CLPInterface::best_objective_bound() const {
LOG(DFATAL) << "Best objective bound only available for discrete problems";
return trivial_worst_objective_bound();
}
MPSolver::BasisStatus CLPInterface::row_status(int constraint_index) const {
DCHECK_LE(0, constraint_index);
DCHECK_GT(last_constraint_index_, constraint_index);

View File

@@ -60,7 +60,6 @@ class GLOPInterface : public MPSolverInterface {
// ------ Query statistics on the solution and the solve ------
int64 iterations() const override;
int64 nodes() const override;
double best_objective_bound() const override;
MPSolver::BasisStatus row_status(int constraint_index) const override;
MPSolver::BasisStatus column_status(int variable_index) const override;
@@ -245,11 +244,6 @@ int64 GLOPInterface::nodes() const {
return kUnknownNumberOfNodes;
}
double GLOPInterface::best_objective_bound() const {
// TODO(user): report a better bound when we can.
return trivial_worst_objective_bound();
}
MPSolver::BasisStatus GLOPInterface::row_status(int constraint_index) const {
return row_status_[constraint_index];
}

View File

@@ -136,8 +136,6 @@ class GLPKInterface : public MPSolverInterface {
int64 iterations() const override;
// Number of branch-and-bound nodes. Only available for discrete problems.
int64 nodes() const override;
// Best objective bound. Only available for discrete problems.
double best_objective_bound() const override;
// Returns the basis status of a row.
MPSolver::BasisStatus row_status(int constraint_index) const override;
@@ -146,8 +144,6 @@ class GLPKInterface : public MPSolverInterface {
// Checks whether a feasible solution exists.
bool CheckSolutionExists() const override;
// Checks whether information on the best objective bound exists.
bool CheckBestObjectiveBoundExists() const override;
// ----- Misc -----
// Query problem type.
@@ -567,10 +563,12 @@ MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) {
// Get the results.
if (mip_) {
objective_value_ = glp_mip_obj_val(lp_);
best_objective_bound_ = mip_callback_info_->best_objective_bound_;
} else {
objective_value_ = glp_get_obj_val(lp_);
}
VLOG(1) << "objective=" << objective_value_;
VLOG(1) << "objective=" << objective_value_
<< ", bound=" << best_objective_bound_;
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
double val;
@@ -693,23 +691,6 @@ int64 GLPKInterface::nodes() const {
}
}
double GLPKInterface::best_objective_bound() const {
if (mip_) {
if (!CheckSolutionIsSynchronized() || !CheckBestObjectiveBoundExists()) {
return trivial_worst_objective_bound();
}
if (solver_->variables_.empty() && solver_->constraints_.empty()) {
// Special case for empty model.
return solver_->Objective().offset();
} else {
return mip_callback_info_->best_objective_bound_;
}
} else {
LOG(DFATAL) << "Best objective bound only available for discrete problems";
return trivial_worst_objective_bound();
}
}
MPSolver::BasisStatus GLPKInterface::row_status(int constraint_index) const {
DCHECK_GE(constraint_index, 0);
DCHECK_LT(constraint_index, last_constraint_index_);
@@ -737,18 +718,6 @@ bool GLPKInterface::CheckSolutionExists() const {
}
}
bool GLPKInterface::CheckBestObjectiveBoundExists() const {
if (result_status_ == MPSolver::ABNORMAL) {
LOG(WARNING) << "Ignoring ABNORMAL status from GLPK: This status may or may"
<< " not indicate that information is available on the best"
<< " objective bound.";
return true;
} else {
// Call default implementation
return MPSolverInterface::CheckBestObjectiveBoundExists();
}
}
double GLPKInterface::ComputeExactConditionNumber() const {
if (!IsContinuous()) {
// TODO(user): support MIP.

View File

@@ -114,7 +114,6 @@ class GurobiInterface : public MPSolverInterface {
void SetObjectiveOffset(double value) override;
// Clears the objective from all its terms.
void ClearObjective() override;
bool CheckBestObjectiveBoundExists() const override;
void BranchingPriorityChangedForVariable(int var_index) override;
// ------ Query statistics on the solution and the solve ------
@@ -122,8 +121,6 @@ class GurobiInterface : public MPSolverInterface {
int64 iterations() const override;
// Number of branch-and-bound nodes. Only available for discrete problems.
int64 nodes() const override;
// Best objective bound. Only available for discrete problems.
double best_objective_bound() const override;
// Returns the basis status of a row.
MPSolver::BasisStatus row_status(int constraint_index) const override;
@@ -788,38 +785,6 @@ int64 GurobiInterface::nodes() const {
}
}
bool GurobiInterface::CheckBestObjectiveBoundExists() const {
double value;
const int error = GRBgetdblattr(model_, GRB_DBL_ATTR_OBJBOUND, &value);
return error == 0;
}
// Returns the best objective bound. Only available for discrete problems.
double GurobiInterface::best_objective_bound() const {
if (mip_) {
if (!CheckSolutionIsSynchronized() || !CheckBestObjectiveBoundExists()) {
return trivial_worst_objective_bound();
}
if (solver_->variables_.empty() && solver_->constraints_.empty()) {
// Special case for empty model.
return solver_->Objective().offset();
}
double value;
const int error = GRBgetdblattr(model_, GRB_DBL_ATTR_OBJBOUND, &value);
if (result_status_ == MPSolver::OPTIMAL &&
error == GRB_ERROR_DATA_NOT_AVAILABLE) {
// Special case for when presolve removes all the variables so the model
// becomes empty after the presolve phase.
return objective_value_;
}
CheckedGurobiCall(error);
return value;
} else {
LOG(DFATAL) << "Best objective bound only available for discrete problems.";
return trivial_worst_objective_bound();
}
}
MPSolver::BasisStatus GurobiInterface::TransformGRBVarBasisStatus(
int gurobi_basis_status) const {
switch (gurobi_basis_status) {
@@ -1272,6 +1237,16 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
}
}
if (IsMIP() && (result_status_ != MPSolver::UNBOUNDED &&
result_status_ != MPSolver::INFEASIBLE)) {
const int error =
GRBgetdblattr(model_, GRB_DBL_ATTR_OBJBOUND, &best_objective_bound_);
LOG_IF(WARNING, error != 0)
<< "Best objective bound is not available, error=" << error
<< ", message=" << GRBgeterrormsg(env_);
VLOG(1) << "best bound = " << best_objective_bound_;
}
if (solution_count > 0 && (result_status_ == MPSolver::FEASIBLE ||
result_status_ == MPSolver::OPTIMAL)) {
current_solution_index_ = 0;

View File

@@ -1037,6 +1037,7 @@ absl::Status MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response,
"'"));
}
}
// TODO(user): Load the reduced costs too, if available.
for (int i = 0; i < response.variable_value_size(); ++i) {
variables_[i]->set_solution_value(response.variable_value(i));
}
@@ -1045,6 +1046,9 @@ absl::Status MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response,
if (response.has_objective_value()) {
interface_->objective_value_ = response.objective_value();
}
if (response.has_best_objective_bound()) {
interface_->best_objective_bound_ = response.best_objective_bound();
}
// Mark the status as SOLUTION_SYNCHRONIZED, so that users may inspect the
// solution normally.
interface_->sync_status_ = MPSolverInterface::SOLUTION_SYNCHRONIZED;
@@ -1611,6 +1615,8 @@ bool MPSolverResponseStatusIsRpcError(MPSolverResponseStatus status) {
const int MPSolverInterface::kDummyVariableIndex = 0;
// TODO(user): Initialize objective value and bound to +/- inf (depending on
// optimization direction).
MPSolverInterface::MPSolverInterface(MPSolver* const solver)
: solver_(solver),
sync_status_(MODEL_SYNCHRONIZED),
@@ -1619,6 +1625,7 @@ MPSolverInterface::MPSolverInterface(MPSolver* const solver)
last_constraint_index_(0),
last_variable_index_(0),
objective_value_(0.0),
best_objective_bound_(0.0),
quiet_(true) {}
MPSolverInterface::~MPSolverInterface() {}
@@ -1685,28 +1692,29 @@ bool MPSolverInterface::CheckSolutionExists() const {
return true;
}
// Default version that can be overwritten by a solver-specific
// version to accommodate for the quirks of each solver.
bool MPSolverInterface::CheckBestObjectiveBoundExists() const {
if (result_status_ != MPSolver::OPTIMAL &&
result_status_ != MPSolver::FEASIBLE) {
LOG(DFATAL) << "No information is available for the best objective bound."
<< " MPSolverInterface::result_status_ = " << result_status_;
return false;
}
return true;
}
double MPSolverInterface::trivial_worst_objective_bound() const {
return maximize_ ? -std::numeric_limits<double>::infinity()
: std::numeric_limits<double>::infinity();
}
double MPSolverInterface::objective_value() const {
if (!CheckSolutionIsSynchronizedAndExists()) return 0;
return objective_value_;
}
double MPSolverInterface::best_objective_bound() const {
const double trivial_worst_bound =
maximize_ ? -std::numeric_limits<double>::infinity()
: std::numeric_limits<double>::infinity();
if (!IsMIP()) {
LOG(DFATAL) << "Best objective bound only available for discrete problems.";
return trivial_worst_bound;
}
if (!CheckSolutionIsSynchronized()) {
return trivial_worst_bound;
}
// Special case for empty model.
if (solver_->variables_.empty() && solver_->constraints_.empty()) {
return solver_->Objective().offset();
}
return best_objective_bound_;
}
void MPSolverInterface::InvalidateSolutionSynchronization() {
if (sync_status_ == SOLUTION_SYNCHRONIZED) {
sync_status_ = MODEL_SYNCHRONIZED;

View File

@@ -1611,11 +1611,8 @@ class MPSolverInterface {
// otherwise it crashes, or returns kUnknownNumberOfNodes in NDEBUG mode.
virtual int64 nodes() const = 0;
// Returns the best objective bound. The problem must be discrete, otherwise
// it crashes, or returns trivial_worst_objective_bound() in NDEBUG mode.
virtual double best_objective_bound() const = 0;
// A trivial objective bound: the worst possible value of the objective,
// which will be +infinity if minimizing and -infinity if maximing.
double trivial_worst_objective_bound() const;
// it crashes, or returns trivial bound (+/- inf) in NDEBUG mode.
double best_objective_bound() const;
// Returns the objective value of the best solution found so far.
double objective_value() const;
@@ -1635,9 +1632,6 @@ class MPSolverInterface {
bool CheckSolutionIsSynchronizedAndExists() const {
return CheckSolutionIsSynchronized() && CheckSolutionExists();
}
// Checks whether information on the best objective bound exists. The behavior
// is similar to CheckSolutionIsSynchronized() above.
virtual bool CheckBestObjectiveBoundExists() const;
// ----- Misc -----
// Queries problem type. For simplicity, the distinction between
@@ -1732,6 +1726,9 @@ class MPSolverInterface {
// The value of the objective function.
double objective_value_;
// The value of the best objective bound. Used only for MIP solvers.
double best_objective_bound_;
// Boolean indicator for the verbosity of the solver output.
bool quiet_;

View File

@@ -69,7 +69,6 @@ class SatInterface : public MPSolverInterface {
// ------ Query statistics on the solution and the solve ------
int64 iterations() const override;
int64 nodes() const override;
double best_objective_bound() const override;
MPSolver::BasisStatus row_status(int constraint_index) const override;
MPSolver::BasisStatus column_status(int variable_index) const override;
@@ -102,7 +101,6 @@ class SatInterface : public MPSolverInterface {
std::atomic<bool> interrupt_solve_;
sat::SatParameters parameters_;
int num_threads_ = 8;
double best_objective_bound_ = 0.0;
};
SatInterface::SatInterface(MPSolver* const solver)
@@ -246,13 +244,6 @@ int64 SatInterface::iterations() const {
int64 SatInterface::nodes() const { return 0; }
double SatInterface::best_objective_bound() const {
if (!CheckSolutionIsSynchronized() || !CheckBestObjectiveBoundExists()) {
return trivial_worst_objective_bound();
}
return best_objective_bound_;
}
MPSolver::BasisStatus SatInterface::row_status(int constraint_index) const {
return MPSolver::BasisStatus::FREE; // FIXME
}

View File

@@ -84,7 +84,6 @@ class SCIPInterface : public MPSolverInterface {
int64 iterations() const override;
int64 nodes() const override;
double best_objective_bound() const override;
MPSolver::BasisStatus row_status(int constraint_index) const override {
LOG(DFATAL) << "Basis status only available for continuous problems";
return MPSolver::FREE;
@@ -656,6 +655,7 @@ MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) {
sync_status_ = SOLUTION_SYNCHRONIZED;
result_status_ = MPSolver::OPTIMAL;
objective_value_ = solver_->Objective().offset();
best_objective_bound_ = solver_->Objective().offset();
return result_status_;
}
@@ -797,7 +797,9 @@ MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) {
void SCIPInterface::SetSolution(SCIP_SOL* solution) {
objective_value_ = SCIPgetSolOrigObj(scip_, solution);
VLOG(1) << "objective=" << objective_value_;
best_objective_bound_ = SCIPgetDualbound(scip_);
VLOG(1) << "objective=" << objective_value_
<< ", bound=" << best_objective_bound_;
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
const int var_index = var->index();
@@ -860,19 +862,6 @@ int64 SCIPInterface::nodes() const {
return SCIPgetNTotalNodes(scip_);
}
double SCIPInterface::best_objective_bound() const {
// NOTE(user): Same story as iterations(): it's OK to crash here.
if (!CheckSolutionIsSynchronized() || !CheckBestObjectiveBoundExists()) {
return trivial_worst_objective_bound();
}
if (solver_->variables_.empty() && solver_->constraints_.empty()) {
// Special case for empty model.
return solver_->Objective().offset();
} else {
return SCIPgetDualbound(scip_);
}
}
void SCIPInterface::SetParameters(const MPSolverParameters& param) {
SetCommonParameters(param);
SetMIPParameters(param);

View File

@@ -2723,9 +2723,11 @@ void SolveCpModelParallel(const CpModelProto& model_proto,
NeighborhoodGeneratorHelper* helper = unique_helper.get();
subsolvers.push_back(std::move(unique_helper));
const int num_lns_strategies = parameters.diversify_lns_params() ? 6 : 1;
const std::vector<SatParameters>& lns_params =
GetDiverseSetOfParameters(parameters, model_proto, num_lns_strategies);
std::vector<SatParameters> lns_params = {parameters};
if (parameters.diversify_lns_params()) {
std::vector<SatParameters> lns_params =
GetDiverseSetOfParameters(parameters, model_proto, 6);
}
for (const SatParameters& local_params : lns_params) {
// Only register following LNS SubSolver if there is an objective.
if (model_proto.has_objective()) {
@@ -2865,15 +2867,14 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
CHECK_OK(file::SetTextProto(file, model_proto, file::Defaults()));
}
absl::Cleanup<std::function<void()>> dump_response_cleanup;
if (absl::GetFlag(FLAGS_cp_model_dump_response)) {
dump_response_cleanup = absl::MakeCleanup([&final_response] {
auto dump_response_cleanup = absl::MakeCleanup([&final_response] {
if (absl::GetFlag(FLAGS_cp_model_dump_response)) {
const std::string file = absl::StrCat(
absl::GetFlag(FLAGS_cp_model_dump_prefix), "response.pbtxt");
LOG(INFO) << "Dumping response proto to '" << file << "'.";
CHECK_OK(file::SetTextProto(file, final_response, file::Defaults()));
});
}
}
});
// Override parameters?
if (!absl::GetFlag(FLAGS_cp_model_params).empty()) {

View File

@@ -1635,7 +1635,7 @@ void LinearProgrammingConstraint::PreventOverflow(LinearConstraint* constraint,
sum_min += std::min(0.0, std::min(prod1, prod2));
sum_max += std::max(0.0, std::max(prod1, prod2));
}
const double max_value = std::max(sum_max, -sum_min);
const double max_value = std::max({sum_max, -sum_min, sum_max - sum_min});
const IntegerValue divisor(std::ceil(std::ldexp(max_value, -max_pow)));
if (divisor <= 1) return;

View File

@@ -282,12 +282,12 @@ ABSL_MUST_USE_RESULT bool PresolveContext::SetLiteralToTrue(int lit) {
return SetLiteralToFalse(NegatedRef(lit));
}
void PresolveContext::UpdateRuleStats(const std::string& name) {
void PresolveContext::UpdateRuleStats(const std::string& name, int num_times) {
if (enable_stats) {
VLOG(1) << num_presolve_operations << " : " << name;
stats_by_rule_name[name]++;
stats_by_rule_name[name] += num_times;
}
num_presolve_operations++;
num_presolve_operations += num_times;
}
void PresolveContext::UpdateLinear1Usage(const ConstraintProto& ct, int c) {

View File

@@ -151,7 +151,7 @@ class PresolveContext {
// Stores a description of a rule that was just applied to have a summary of
// what the presolve did at the end.
void UpdateRuleStats(const std::string& name);
void UpdateRuleStats(const std::string& name, int num_times = 1);
// Updates the constraints <-> variables graph. This needs to be called each
// time a constraint is modified.

View File

@@ -1115,7 +1115,7 @@ bool ExploitDominanceRelations(const VarDomination& var_domination,
if (num_added > 0) {
VLOG(1) << "Added " << num_added << " domination implications.";
context->UpdateNewConstraintsVariableUsage();
context->UpdateRuleStats("domination: added implications.");
context->UpdateRuleStats("domination: added implications", num_added);
}
return true;