diff --git a/examples/cpp/linear_programming.cc b/examples/cpp/linear_programming.cc index c48e9d4bda..00b5a455bb 100644 --- a/examples/cpp/linear_programming.cc +++ b/examples/cpp/linear_programming.cc @@ -82,12 +82,13 @@ void RunLinearProgrammingExample( LOG(INFO) << "x1: reduced cost = " << x1->reduced_cost(); LOG(INFO) << "x2: reduced cost = " << x2->reduced_cost(); LOG(INFO) << "x3: reduced cost = " << x3->reduced_cost(); + const std::vector activities = solver.ComputeConstraintActivities(); LOG(INFO) << "c0: dual value = " << c0->dual_value() - << " activity = " << c0->activity(); + << " activity = " << activities[c0->index()]; LOG(INFO) << "c1: dual value = " << c1->dual_value() - << " activity = " << c1->activity(); + << " activity = " << activities[c1->index()]; LOG(INFO) << "c2: dual value = " << c2->dual_value() - << " activity = " << c2->activity(); + << " activity = " << activities[c2->index()]; } void RunAllExamples() { diff --git a/src/linear_solver/bop_interface.cc b/src/linear_solver/bop_interface.cc index 607c9d1a15..a6d764f0a0 100644 --- a/src/linear_solver/bop_interface.cc +++ b/src/linear_solver/bop_interface.cc @@ -184,13 +184,9 @@ MPSolver::ResultStatus BopInterface::Solve(const MPSolverParameters& param) { var->set_solution_value(static_cast(solution_value)); } - // TODO(user): Implement the row activity and row status. + // TODO(user): Implement the row status. const size_t num_constraints = solver_->constraints_.size(); row_status_.resize(num_constraints, MPSolver::FREE); - for (int ct_id = 0; ct_id < num_constraints; ++ct_id) { - MPConstraint* const ct = solver_->constraints_[ct_id]; - ct->set_activity(static_cast(0)); - } } return result_status_; diff --git a/src/linear_solver/cbc_interface.cc b/src/linear_solver/cbc_interface.cc index 569d7382b1..fe10d00e00 100644 --- a/src/linear_solver/cbc_interface.cc +++ b/src/linear_solver/cbc_interface.cc @@ -429,17 +429,6 @@ MPSolver::ResultStatus CBCInterface::Solve(const MPSolverParameters& param) { } else { VLOG(1) << "No feasible solution found."; } - - const double* const row_activities = model.getRowActivity(); - if (row_activities != NULL) { - for (int i = 0; i < solver_->constraints_.size(); ++i) { - MPConstraint* const ct = solver_->constraints_[i]; - const int constraint_index = ct->index(); - const double row_activity = row_activities[constraint_index]; - ct->set_activity(row_activity); - VLOG(4) << "row " << ct->index() << ": activity = " << row_activity; - } - } } iterations_ = model.getIterationCount(); diff --git a/src/linear_solver/clp_interface.cc b/src/linear_solver/clp_interface.cc index 4bef4449b7..04c4c9de2f 100644 --- a/src/linear_solver/clp_interface.cc +++ b/src/linear_solver/clp_interface.cc @@ -493,16 +493,12 @@ MPSolver::ResultStatus CLPInterface::Solve(const MPSolverParameters& param) { VLOG(4) << var->name() << ": reduced cost = " << reduced_cost; } const double* const dual_values = clp_->getRowPrice(); - const double* const row_activities = clp_->getRowActivity(); for (int i = 0; i < solver_->constraints_.size(); ++i) { MPConstraint* const ct = solver_->constraints_[i]; const int constraint_index = ct->index(); - const double row_activity = row_activities[constraint_index]; - ct->set_activity(row_activity); const double dual_value = dual_values[constraint_index]; ct->set_dual_value(dual_value); - VLOG(4) << "row " << ct->index() << ": activity = " << row_activity - << " dual value = " << dual_value; + VLOG(4) << "row " << ct->index() << " dual value = " << dual_value; } } diff --git a/src/linear_solver/csharp/linear_solver.swig b/src/linear_solver/csharp/linear_solver.swig index 5a1310f2e3..969313d88f 100644 --- a/src/linear_solver/csharp/linear_solver.swig +++ b/src/linear_solver/csharp/linear_solver.swig @@ -101,6 +101,7 @@ %unignore operations_research::MPSolver::LookupVariableOrNull; // Expose very advanced parts of the MPSolver API. For expert users only. +%unignore operations_research::MPSolver::ComputeConstraintActivities; %unignore operations_research::MPSolver::ComputeExactConditionNumber; %rename (Nodes) operations_research::MPSolver::nodes; %rename (Iterations) operations_research::MPSolver::iterations; @@ -156,7 +157,6 @@ %rename (Lb) operations_research::MPConstraint::lb; %rename (Ub) operations_research::MPConstraint::ub; %rename (Name) operations_research::MPConstraint::name; -%rename (Activity) operations_research::MPConstraint::activity; %rename (BasisStatus) operations_research::MPConstraint::basis_status; %rename (DualValue) operations_research::MPConstraint::dual_value; // expert %rename (IsLazy) operations_research::MPConstraint::is_lazy; // expert @@ -179,7 +179,6 @@ %unignore operations_research::MPObjective::BestBound; // MPSolverParameters API. For expert users only. -%unignore operations_research::MPSolverParameters::MPSolverParameters; %unignore operations_research::MPSolverParameters::DoubleParam; %unignore operations_research::MPSolverParameters::RELATIVE_MIP_GAP; %unignore operations_research::MPSolverParameters::GetDoubleParam; diff --git a/src/linear_solver/glop_interface.cc b/src/linear_solver/glop_interface.cc index 45134eee04..2709872108 100644 --- a/src/linear_solver/glop_interface.cc +++ b/src/linear_solver/glop_interface.cc @@ -239,10 +239,6 @@ MPSolver::ResultStatus GLOPInterface::Solve(const MPSolverParameters& param) { lp_solver_.dual_values()[lp_solver_ct_id]; ct->set_dual_value(static_cast(dual_value)); - const glop::Fractional row_activity = - lp_solver_.constraint_activities()[lp_solver_ct_id]; - ct->set_activity(static_cast(row_activity)); - const glop::ConstraintStatus constraint_status = lp_solver_.constraint_statuses()[lp_solver_ct_id]; row_status_.at(ct_id) = TranslateConstraintStatus(constraint_status); diff --git a/src/linear_solver/glpk_interface.cc b/src/linear_solver/glpk_interface.cc index 81beddfb3d..456e5adaf4 100644 --- a/src/linear_solver/glpk_interface.cc +++ b/src/linear_solver/glpk_interface.cc @@ -591,21 +591,11 @@ MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) { } for (int i = 0; i < solver_->constraints_.size(); ++i) { MPConstraint* const ct = solver_->constraints_[i]; - if (mip_) { - const double row_activity = - glp_mip_row_val(lp_, MPSolverIndexToGlpkIndex(i)); - ct->set_activity(row_activity); - VLOG(4) << "row " << MPSolverIndexToGlpkIndex(i) - << ": activity = " << row_activity; - } else { - const double row_activity = - glp_get_row_prim(lp_, MPSolverIndexToGlpkIndex(i)); - ct->set_activity(row_activity); + if (!mip_) { const double dual_value = glp_get_row_dual(lp_, MPSolverIndexToGlpkIndex(i)); ct->set_dual_value(dual_value); VLOG(4) << "row " << MPSolverIndexToGlpkIndex(i) - << ": activity = " << row_activity << ": dual value = " << dual_value; } } diff --git a/src/linear_solver/gurobi_interface.cc b/src/linear_solver/gurobi_interface.cc index 885ec85400..3f62ca857c 100644 --- a/src/linear_solver/gurobi_interface.cc +++ b/src/linear_solver/gurobi_interface.cc @@ -682,18 +682,12 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) { std::unique_ptr values(new double[total_num_cols]); std::unique_ptr dual_values(new double[total_num_rows]); - std::unique_ptr slacks(new double[total_num_rows]); - std::unique_ptr rhs(new double[total_num_rows]); std::unique_ptr reduced_costs(new double[total_num_cols]); CHECKED_GUROBI_CALL( GRBgetdblattr(model_, GRB_DBL_ATTR_OBJVAL, &objective_value_)); CHECKED_GUROBI_CALL(GRBgetdblattrarray(model_, GRB_DBL_ATTR_X, 0, total_num_cols, values.get())); - CHECKED_GUROBI_CALL(GRBgetdblattrarray(model_, GRB_DBL_ATTR_SLACK, 0, - total_num_rows, slacks.get())); - CHECKED_GUROBI_CALL(GRBgetdblattrarray(model_, GRB_DBL_ATTR_RHS, 0, - total_num_rows, rhs.get())); if (!mip_) { CHECKED_GUROBI_CALL(GRBgetdblattrarray( model_, GRB_DBL_ATTR_RC, 0, total_num_cols, reduced_costs.get())); @@ -712,16 +706,11 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) { } } - for (int i = 0; i < solver_->constraints_.size(); ++i) { - MPConstraint* const ct = solver_->constraints_[i]; - ct->set_activity(rhs[i] - slacks[i]); - if (mip_) { - VLOG(4) << "row " << ct->index() << ", slack = " << slacks[i] - << ", rhs = " << rhs[i]; - } else { + if (!mip_) { + for (int i = 0; i < solver_->constraints_.size(); ++i) { + MPConstraint* const ct = solver_->constraints_[i]; ct->set_dual_value(dual_values[i]); - VLOG(4) << "row " << ct->index() << ", slack = " << slacks[i] - << ", rhs = " << rhs[i] << ", dual value = " << dual_values[i]; + VLOG(4) << "row " << ct->index() << ", dual value = " << dual_values[i]; } } } diff --git a/src/linear_solver/java/linear_solver.swig b/src/linear_solver/java/linear_solver.swig index 1643117ec5..0caeb896d8 100644 --- a/src/linear_solver/java/linear_solver.swig +++ b/src/linear_solver/java/linear_solver.swig @@ -165,9 +165,9 @@ import java.lang.reflect.*; %rename (suppressOutput) operations_research::MPSolver::SuppressOutput; // no test %rename (lookupConstraintOrNull) operations_research::MPSolver::LookupConstraintOrNull; // no test %rename (lookupVariableOrNull) operations_research::MPSolver::LookupVariableOrNull; // no test -%rename (solverVersion) operations_research::MPSolver::SolverVersion; // no test // Expose very advanced parts of the MPSolver API. For expert users only. +%rename (computeConstraintActivities) operations_research::MPSolver::ComputeConstraintActivities; %rename (computeExactConditionNumber) operations_research::MPSolver::ComputeExactConditionNumber; %rename (nodes) operations_research::MPSolver::nodes; %rename (iterations) operations_research::MPSolver::iterations; @@ -204,10 +204,10 @@ import java.lang.reflect.*; %rename (lb) operations_research::MPConstraint::lb; // no test %rename (ub) operations_research::MPConstraint::ub; // no test %rename (name) operations_research::MPConstraint::name; -%rename (activity) operations_research::MPConstraint::activity; %rename (basisStatus) operations_research::MPConstraint::basis_status; %rename (dualValue) operations_research::MPConstraint::dual_value; // For experts only. %rename (isLazy) operations_research::MPConstraint::is_lazy; // For experts only. +%rename (index) operations_research::MPConstraint::index; // MPObjective: writer API. %rename (setCoefficient) operations_research::MPObjective::SetCoefficient; @@ -228,7 +228,6 @@ import java.lang.reflect.*; // MPSolverParameters API. For expert users only. // TODO(user): unit test all of it. -%unignore operations_research::MPSolverParameters::MPSolverParameters; // no test %unignore operations_research::MPSolverParameters::DoubleParam; // no test %unignore operations_research::MPSolverParameters::RELATIVE_MIP_GAP; // no test %rename (getDoubleParam) operations_research::MPSolverParameters::GetDoubleParam; // no test diff --git a/src/linear_solver/linear_solver.cc b/src/linear_solver/linear_solver.cc index 2d7d8d7903..7df4187fd2 100644 --- a/src/linear_solver/linear_solver.cc +++ b/src/linear_solver/linear_solver.cc @@ -152,11 +152,6 @@ MPSolver::BasisStatus MPConstraint::basis_status() const { return interface_->row_status(index_); } -double MPConstraint::activity() const { - if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0; - return activity_; -} - bool MPConstraint::ContainsNewVariables() { const int last_variable_index = interface_->last_variable_index(); for (CoeffEntry entry : coefficients_) { @@ -979,6 +974,21 @@ std::string PrettyPrintConstraint(const MPConstraint& constraint) { } } // namespace +std::vector MPSolver::ComputeConstraintActivities() const { + // TODO(user): test this failure case. + if (!interface_->CheckSolutionIsSynchronizedAndExists()) return {}; + std::vector activities(constraints_.size(), 0.0); + for (int i = 0; i < constraints_.size(); ++i) { + const MPConstraint& constraint = *constraints_[i]; + AccurateSum sum; + for (CoeffEntry entry : constraint.coefficients_) { + sum.Add(entry.first->solution_value() * entry.second); + } + activities[i] = sum.Value(); + } + return activities; +} + // TODO(user): split. bool MPSolver::VerifySolution(double tolerance, bool log_errors) const { double max_observed_error = 0; @@ -1027,17 +1037,15 @@ bool MPSolver::VerifySolution(double tolerance, bool log_errors) const { } // Verify constraints. + const std::vector activities = ComputeConstraintActivities(); for (int i = 0; i < constraints_.size(); ++i) { const MPConstraint& constraint = *constraints_[i]; - // Re-compute the actual activity with a safe summing algorithm. - AccurateSum activity_sum; - double inaccurate_activity = 0; + const double activity = activities[i]; + // Re-compute the activity with a inaccurate summing algorithm. + double inaccurate_activity = 0.0; for (CoeffEntry entry : constraint.coefficients_) { - const double term = entry.first->solution_value() * entry.second; - activity_sum.Add(term); - inaccurate_activity += term; + inaccurate_activity += entry.first->solution_value() * entry.second; } - const double activity = activity_sum.Value(); // Catch NaNs. if (isnan(activity) || isnan(inaccurate_activity)) { ++num_errors; diff --git a/src/linear_solver/linear_solver.h b/src/linear_solver/linear_solver.h index 4a31a6aa06..14c6375497 100644 --- a/src/linear_solver/linear_solver.h +++ b/src/linear_solver/linear_solver.h @@ -323,6 +323,12 @@ class MPSolver { // Solves the problem using the specified parameter values. ResultStatus Solve(const MPSolverParameters& param); + // Advanced usage: compute the "activities" of all constraints, which are the + // sums of their linear terms. The activities are returned in the same order + // as constraints(), which is the order in which constraints were added; but + // you can also use MPConstraint::index() to get a constraint's index. + std::vector ComputeConstraintActivities() const; + // Advanced usage: // Verifies the *correctness* of the solution: all variables must be within // their domains, all constraints must be satisfied, and the reported @@ -817,10 +823,6 @@ class MPConstraint { // For more info see: http://tinyurl.com/lazy-constraints. void set_is_lazy(bool laziness) { is_lazy_ = laziness; } - // Returns the constraint's activity in the current solution: - // sum over all terms of (coefficient * variable value) - double activity() const; - // Returns the index of the constraint in the MPSolver::constraints_. int index() const { return index_; } @@ -858,10 +860,8 @@ class MPConstraint { name_(name), is_lazy_(false), dual_value_(0.0), - activity_(0.0), interface_(interface) {} - void set_activity(double activity) { activity_ = activity; } void set_dual_value(double dual_value) { dual_value_ = dual_value; } private: @@ -888,7 +888,6 @@ class MPConstraint { // By default this parameter is 'false'. bool is_lazy_; double dual_value_; - double activity_; MPSolverInterface* const interface_; DISALLOW_COPY_AND_ASSIGN(MPConstraint); }; diff --git a/src/linear_solver/python/linear_solver.swig b/src/linear_solver/python/linear_solver.swig index 84030c2f4a..2e5ae9562a 100644 --- a/src/linear_solver/python/linear_solver.swig +++ b/src/linear_solver/python/linear_solver.swig @@ -146,24 +146,32 @@ from ortools.linear_solver.linear_solver_natural_api import LinearConstraint } // %pythoncode } -// See ../python/linear_programming.py for example on how to use the nice -// extended python API provided below. %extend MPSolver { + // Change a (bool, std::string*) outputs to a python std::string (empty if bool=false). std::string ExportModelAsLpFormat(bool obfuscated) { std::string output; - if (!self->ExportModelAsLpFormat(obfuscated, &output)) return ""; + if (!$self->ExportModelAsLpFormat(obfuscated, &output)) return ""; return output; } - std::string ExportModelAsMpsFormat(bool fixed_format, bool obfuscated) { std::string output; - if (!self->ExportModelAsMpsFormat(fixed_format, obfuscated, &output)) { + if (!$self->ExportModelAsMpsFormat(fixed_format, obfuscated, &output)) { return ""; } return output; } + // Change the API of LoadModelFromProto() to simply return the error message: + // it will always be empty iff the model was valid. + std::string LoadModelFromProto(const MPModelProto& input_model) { + std::string error_message; + $self->LoadModelFromProto(input_model, &error_message); + return error_message; + } + %pythoncode { + # See ../python/linear_programming.py for example on how to use the nice + # extended python API provided below. def Add(self, constraint, name=''): if isinstance(constraint, bool): if constraint: @@ -177,6 +185,7 @@ from ortools.linear_solver.linear_solver_natural_api import LinearConstraint result = SumArray(expr_array) return result + # Compatibility def RowConstraint(self, *args): return self.Constraint(*args) @@ -252,6 +261,7 @@ from ortools.linear_solver.linear_solver_natural_api import LinearConstraint %unignore operations_research::MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING; %unignore operations_research::MPSolver::CBC_MIXED_INTEGER_PROGRAMMING; %unignore operations_research::MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING; +%unignore operations_research::MPSolver::BOP_INTEGER_PROGRAMMING; // These aren't unit tested, as they only run on machines with a Gurobi license. %unignore operations_research::MPSolver::GUROBI_LINEAR_PROGRAMMING; %unignore operations_research::MPSolver::GUROBI_MIXED_INTEGER_PROGRAMMING; @@ -292,8 +302,10 @@ from ortools.linear_solver.linear_solver_natural_api import LinearConstraint %rename (LookupConstraint) operations_research::MPSolver::LookupConstraintOrNull; %rename (LookupVariable) operations_research::MPSolver::LookupVariableOrNull; +%unignore operations_research::MPSolver::SetSolverSpecificParametersAsString; // Expose very advanced parts of the MPSolver API. For expert users only. +%unignore operations_research::MPSolver::ComputeConstraintActivities; %unignore operations_research::MPSolver::ComputeExactConditionNumber; %unignore operations_research::MPSolver::nodes; %unignore operations_research::MPSolver::iterations; // No unit test @@ -308,7 +320,9 @@ from ortools.linear_solver.linear_solver_natural_api import LinearConstraint %unignore operations_research::MPVariable::solution_value; %unignore operations_research::MPVariable::lb; // No unit test %unignore operations_research::MPVariable::ub; // No unit test +%unignore operations_research::MPVariable::integer; // No unit test %unignore operations_research::MPVariable::name; // No unit test +%unignore operations_research::MPVariable::index; // No unit test %unignore operations_research::MPVariable::basis_status; %unignore operations_research::MPVariable::reduced_cost; // For experts only. @@ -324,7 +338,7 @@ from ortools.linear_solver.linear_solver_natural_api import LinearConstraint %unignore operations_research::MPConstraint::lb; %unignore operations_research::MPConstraint::ub; %unignore operations_research::MPConstraint::name; -%unignore operations_research::MPConstraint::activity; +%unignore operations_research::MPConstraint::index; %unignore operations_research::MPConstraint::basis_status; %unignore operations_research::MPConstraint::dual_value; // For experts only. diff --git a/src/linear_solver/scip_interface.cc b/src/linear_solver/scip_interface.cc index dd9fad968a..57740250e4 100644 --- a/src/linear_solver/scip_interface.cc +++ b/src/linear_solver/scip_interface.cc @@ -561,14 +561,6 @@ MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) { var->set_solution_value(val); VLOG(3) << var->name() << "=" << val; } - for (int i = 0; i < solver_->constraints_.size(); ++i) { - MPConstraint* const ct = solver_->constraints_[i]; - const int constraint_index = ct->index(); - const double row_activity = SCIPgetActivityLinear( - scip_, scip_constraints_[constraint_index], solution); - ct->set_activity(row_activity); - VLOG(4) << "row " << ct->index() << ": activity = " << row_activity; - } } else { VLOG(1) << "No feasible solution found."; }