You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Conception/drake-master/solvers/clp_solver.cc

375 lines
16 KiB

#include "drake/solvers/clp_solver.h"
#include <limits>
#include <unordered_map>
#include <utility>
#include <vector>
#include "ClpSimplex.hpp"
#include "drake/common/text_logging.h"
#include "drake/solvers/aggregate_costs_constraints.h"
namespace drake {
namespace solvers {
constexpr double kInf = std::numeric_limits<double>::infinity();
namespace {
void ConstructClpModel(
const std::vector<Eigen::Triplet<double>>& constraint_coeffs,
const std::vector<double>& constraint_lower,
const std::vector<double>& constraint_upper, const Eigen::VectorXd& xlow,
const Eigen::VectorXd& xupp,
const Eigen::SparseMatrix<double>& quadratic_matrix,
const Eigen::VectorXd& objective_coeff, double constant_cost,
ClpModel* model) {
Eigen::SparseMatrix<double> constraint_mat(constraint_lower.size(),
xlow.rows());
constraint_mat.setFromTriplets(constraint_coeffs.begin(),
constraint_coeffs.end());
// Now convert the sparse matrix to Compressed Column Storage format.
model->loadProblem(constraint_mat.cols(), constraint_mat.rows(),
constraint_mat.outerIndexPtr(),
constraint_mat.innerIndexPtr(), constraint_mat.valuePtr(),
xlow.data(), xupp.data(), objective_coeff.data(),
constraint_lower.data(), constraint_upper.data(),
nullptr /* rowObjective=nullptr */);
if (quadratic_matrix.nonZeros() > 0) {
static const logging::Warn log_once(
"Currently CLP solver has a memory issue when solving a QP. The user "
"should be aware of this risk.");
model->loadQuadraticObjective(
quadratic_matrix.cols(), quadratic_matrix.outerIndexPtr(),
quadratic_matrix.innerIndexPtr(), quadratic_matrix.valuePtr());
}
model->setObjectiveOffset(-constant_cost);
}
template <typename C>
void AddLinearConstraint(
const MathematicalProgram& prog, const Binding<C>& linear_constraint,
std::vector<Eigen::Triplet<double>>* constraint_coeffs,
std::vector<double>* constraint_lower,
std::vector<double>* constraint_upper, int* constraint_count,
std::unordered_map<Binding<Constraint>, int>* constraint_dual_start_index) {
const Binding<Constraint> binding_cast =
internal::BindingDynamicCast<Constraint>(linear_constraint);
constraint_dual_start_index->emplace(binding_cast, *constraint_count);
const Eigen::SparseMatrix<double>& A =
linear_constraint.evaluator()->get_sparse_A();
// First loop by column to set the coefficient since A is a column major
// matrix.
for (int j = 0; j < A.cols(); ++j) {
const int var_index =
prog.FindDecisionVariableIndex(linear_constraint.variables()(j));
for (Eigen::SparseMatrix<double>::InnerIterator it(A, j); it; ++it) {
const int row = it.row();
constraint_coeffs->emplace_back(row + *constraint_count, var_index,
it.value());
}
}
// Now loop by rows of the constraints to set the bounds.
for (int i = 0; i < linear_constraint.evaluator()->num_constraints(); ++i) {
(*constraint_lower)[*constraint_count + i] =
linear_constraint.evaluator()->lower_bound()(i);
(*constraint_upper)[*constraint_count + i] =
linear_constraint.evaluator()->upper_bound()(i);
}
*constraint_count += linear_constraint.evaluator()->num_constraints();
}
void AddLinearConstraints(
const MathematicalProgram& prog,
std::vector<Eigen::Triplet<double>>* constraint_coeffs,
std::vector<double>* constraint_lower,
std::vector<double>* constraint_upper,
std::unordered_map<Binding<Constraint>, int>* constraint_dual_start_index) {
int num_constraints = 0;
int num_nonzero_coeff_max = 0;
for (const auto& linear_constraint : prog.linear_constraints()) {
num_constraints += linear_constraint.evaluator()->num_constraints();
num_nonzero_coeff_max += linear_constraint.evaluator()->num_constraints() *
linear_constraint.variables().rows();
}
for (const auto& linear_eq_constraint : prog.linear_equality_constraints()) {
num_constraints += linear_eq_constraint.evaluator()->num_constraints();
num_nonzero_coeff_max +=
linear_eq_constraint.evaluator()->num_constraints() *
linear_eq_constraint.variables().rows();
}
constraint_lower->resize(num_constraints);
constraint_upper->resize(num_constraints);
constraint_coeffs->reserve(num_nonzero_coeff_max);
int constraint_count = 0;
for (const auto& linear_constraint : prog.linear_constraints()) {
AddLinearConstraint<LinearConstraint>(
prog, linear_constraint, constraint_coeffs, constraint_lower,
constraint_upper, &constraint_count, constraint_dual_start_index);
}
for (const auto& linear_equal_constraint :
prog.linear_equality_constraints()) {
AddLinearConstraint<LinearEqualityConstraint>(
prog, linear_equal_constraint, constraint_coeffs, constraint_lower,
constraint_upper, &constraint_count, constraint_dual_start_index);
}
}
// @param lambda The dual solution for the whole problem. This can be obtained
// through CLP by calling getRowPrice() or getReducedCost() function.
void SetConstraintDualSolution(
const Eigen::Map<const Eigen::VectorXd>& lambda,
const std::unordered_map<Binding<Constraint>, int>&
constraint_dual_start_index,
MathematicalProgramResult* result) {
for (const auto& [constraint, dual_start_index] :
constraint_dual_start_index) {
result->set_dual_solution(
constraint, lambda.segment(dual_start_index,
constraint.evaluator()->num_constraints()));
}
}
void SetBoundingBoxConstraintDualSolution(
const Eigen::Map<const Eigen::VectorXd>& column_dual_sol,
const std::unordered_map<Binding<BoundingBoxConstraint>,
std::pair<std::vector<int>, std::vector<int>>>&
bb_con_dual_variable_indices,
MathematicalProgramResult* result) {
for (const auto& [bb_con, dual_var_indices] : bb_con_dual_variable_indices) {
Eigen::VectorXd bb_con_dual_sol =
Eigen::VectorXd::Zero(bb_con.evaluator()->num_constraints());
const std::vector<int>& lower_dual_indices = dual_var_indices.first;
const std::vector<int>& upper_dual_indices = dual_var_indices.second;
for (int k = 0; k < bb_con.evaluator()->num_constraints(); ++k) {
// At most one of the bound is active (If both the lower and upper bounds
// are active, then these two bounds have to be equal, then the dual
// solution for both bounds are the same).
if (lower_dual_indices[k] != -1 &&
column_dual_sol(lower_dual_indices[k]) > 0) {
// If the dual solution is positive, then the lower bound is active
// (according to the definition of reduced cost).
bb_con_dual_sol[k] = column_dual_sol[lower_dual_indices[k]];
} else if (upper_dual_indices[k] != -1 &&
column_dual_sol(upper_dual_indices[k]) < 0) {
// If the dual solution is negative, then the upper bound is active
// (according to the definition of reduced cost).
bb_con_dual_sol[k] = column_dual_sol[upper_dual_indices[k]];
}
}
result->set_dual_solution(bb_con, bb_con_dual_sol);
}
}
void SetSolution(
const MathematicalProgram& prog, const ClpModel& model,
const std::unordered_map<Binding<Constraint>, int>&
constraint_dual_start_index,
const std::unordered_map<Binding<BoundingBoxConstraint>,
std::pair<std::vector<int>, std::vector<int>>>&
bb_con_dual_variable_indices,
MathematicalProgramResult* result) {
ClpSolverDetails& solver_details =
result->SetSolverDetailsType<ClpSolverDetails>();
result->set_x_val(Eigen::Map<const Eigen::VectorXd>(model.getColSolution(),
prog.num_vars()));
Eigen::Map<const Eigen::VectorXd> lambda(model.dualRowSolution(),
model.getNumRows());
SetConstraintDualSolution(lambda, constraint_dual_start_index, result);
Eigen::Map<const Eigen::VectorXd> column_dual_sol(model.dualColumnSolution(),
model.getNumCols());
SetBoundingBoxConstraintDualSolution(column_dual_sol,
bb_con_dual_variable_indices, result);
solver_details.status = model.status();
SolutionResult solution_result{SolutionResult::kUnknownError};
switch (solver_details.status) {
case -1: {
solution_result = SolutionResult::kUnknownError;
break;
}
case 0: {
solution_result = SolutionResult::kSolutionFound;
break;
}
case 1: {
solution_result = SolutionResult::kInfeasibleConstraints;
break;
}
case 2: {
solution_result = SolutionResult::kUnbounded;
break;
}
case 3: {
solution_result = SolutionResult::kIterationLimit;
break;
}
default: {
// Merging multiple CLP status code into one Drake SolutionResult code.
solution_result = SolutionResult::kUnknownError;
}
}
double objective_val{-kInf};
if (solution_result == SolutionResult::kInfeasibleConstraints) {
objective_val = kInf;
} else if (solution_result == SolutionResult::kUnbounded) {
objective_val = -kInf;
} else {
objective_val = model.getObjValue();
}
result->set_solution_result(solution_result);
result->set_optimal_cost(objective_val);
}
// bb_con_dual_indices[bb_con] returns the indices of the dual variable for the
// lower/upper bound of this bounding box constraint. If this bounding box
// constraint cannot be active (some other bounding box constraint has a tighter
// bound than this one), then its dual variable index is -1.
// @param xlow The aggregated lower bound for all decision variables across all
// bounding box constraints.
// @param xupp The aggregated upper bound for all decision variables across all
// bounding box constraints.
void SetBoundingBoxConstraintDualIndices(
const MathematicalProgram& prog,
const Binding<BoundingBoxConstraint>& bb_con, const Eigen::VectorXd& xlow,
const Eigen::VectorXd& xupp,
std::unordered_map<Binding<BoundingBoxConstraint>,
std::pair<std::vector<int>, std::vector<int>>>*
bbcon_dual_indices) {
const int num_vars = bb_con.evaluator()->num_constraints();
std::vector<int> lower_dual_indices(num_vars, -1);
std::vector<int> upper_dual_indices(num_vars, -1);
for (int k = 0; k < num_vars; ++k) {
const int idx = prog.FindDecisionVariableIndex(bb_con.variables()(k));
if (xlow[idx] == bb_con.evaluator()->lower_bound()(k)) {
lower_dual_indices[k] = idx;
}
if (xupp[idx] == bb_con.evaluator()->upper_bound()(k)) {
upper_dual_indices[k] = idx;
}
}
bbcon_dual_indices->emplace(
bb_con, std::make_pair(lower_dual_indices, upper_dual_indices));
}
void ParseModelExceptLinearConstraints(
const MathematicalProgram& prog, Eigen::VectorXd* xlow,
Eigen::VectorXd* xupp, Eigen::SparseMatrix<double>* quadratic_matrix,
Eigen::VectorXd* objective_coeff, double* constant_cost) {
// Construct model using loadProblem function.
DRAKE_ASSERT(xlow->rows() == prog.num_vars());
DRAKE_ASSERT(xupp->rows() == prog.num_vars());
AggregateBoundingBoxConstraints(prog, xlow, xupp);
Eigen::SparseVector<double> linear_coeff;
VectorX<symbolic::Variable> quadratic_vars, linear_vars;
Eigen::SparseMatrix<double> Q_lower;
AggregateQuadraticAndLinearCosts(prog.quadratic_costs(), prog.linear_costs(),
&Q_lower, &quadratic_vars, &linear_coeff,
&linear_vars, constant_cost);
// We need to convert 0.5 * quadratic_var.dot(Q_lower * quadratic_var) to
// 0.5 * x.dot(quadratic_matrix * x), where x is the vector of all the
// decision variables. To do so, we first map each entry of quadratic_var to
// its corresponding entry in x.
std::unordered_map<symbolic::Variable::Id, int> quadratic_var_to_index;
for (int i = 0; i < quadratic_vars.rows(); ++i) {
quadratic_var_to_index.emplace(
quadratic_vars(i).get_id(),
prog.FindDecisionVariableIndex(quadratic_vars(i)));
}
std::vector<Eigen::Triplet<double>> quadratic_matrix_triplets;
quadratic_matrix_triplets.reserve(Q_lower.nonZeros());
for (int col = 0; col < Q_lower.outerSize(); ++col) {
const int x_col_index =
quadratic_var_to_index.at(quadratic_vars(col).get_id());
for (Eigen::SparseMatrix<double>::InnerIterator it(Q_lower, col); it;
++it) {
const int x_row_index =
quadratic_var_to_index.at(quadratic_vars(it.row()).get_id());
quadratic_matrix_triplets.emplace_back(x_row_index, x_col_index,
it.value());
}
}
quadratic_matrix->setFromTriplets(quadratic_matrix_triplets.begin(),
quadratic_matrix_triplets.end());
objective_coeff->resize(prog.num_vars());
for (Eigen::SparseVector<double>::InnerIterator it(linear_coeff); it; ++it) {
(*objective_coeff)(prog.FindDecisionVariableIndex(linear_vars(it.row()))) =
it.value();
}
}
int ChooseLogLevel(const SolverOptions& options) {
if (options.get_print_to_console()) {
// Documented as "factorizations plus a bit more" in ClpModel.hpp.
return 3;
}
return 0;
}
int ChooseScaling(const SolverOptions& options) {
const auto& clp_options = options.GetOptionsInt(ClpSolver::id());
auto it = clp_options.find("scaling");
if (it == clp_options.end()) {
// Default scaling is 1.
return 1;
} else {
return it->second;
}
}
} // namespace
bool ClpSolver::is_available() {
return true;
}
void ClpSolver::DoSolve(const MathematicalProgram& prog,
const Eigen::VectorXd& initial_guess,
const SolverOptions& merged_options,
MathematicalProgramResult* result) const {
// TODO(hongkai.dai): use initial guess and more of the merged options.
unused(initial_guess);
ClpSimplex model;
model.setLogLevel(ChooseLogLevel(merged_options));
Eigen::VectorXd xlow(prog.num_vars());
Eigen::VectorXd xupp(prog.num_vars());
Eigen::VectorXd objective_coeff = Eigen::VectorXd::Zero(prog.num_vars());
Eigen::SparseMatrix<double> quadratic_matrix(prog.num_vars(),
prog.num_vars());
double constant_cost{0.};
ParseModelExceptLinearConstraints(prog, &xlow, &xupp, &quadratic_matrix,
&objective_coeff, &constant_cost);
std::vector<Eigen::Triplet<double>> constraint_coeffs;
std::vector<double> constraint_lower, constraint_upper;
std::unordered_map<Binding<Constraint>, int> constraint_dual_start_index;
if (!prog.linear_constraints().empty() ||
!prog.linear_equality_constraints().empty()) {
AddLinearConstraints(prog, &constraint_coeffs, &constraint_lower,
&constraint_upper, &constraint_dual_start_index);
}
ConstructClpModel(constraint_coeffs, constraint_lower, constraint_upper, xlow,
xupp, quadratic_matrix, objective_coeff, constant_cost,
&model);
std::unordered_map<Binding<BoundingBoxConstraint>,
std::pair<std::vector<int>, std::vector<int>>>
bb_con_dual_variable_indices;
for (const auto& bb_con : prog.bounding_box_constraints()) {
SetBoundingBoxConstraintDualIndices(prog, bb_con, xlow, xupp,
&bb_con_dual_variable_indices);
}
// As suggested by the CLP author, we should call scaling() to handle tiny (or
// huge) number in program data. See https://github.com/coin-or/Clp/issues/217
// for the discussion.
model.scaling(ChooseScaling(merged_options));
// Solve
model.primal();
// Set the solution
SetSolution(prog, model, constraint_dual_start_index,
bb_con_dual_variable_indices, result);
}
} // namespace solvers
} // namespace drake