203 lines
6.9 KiB
C++
203 lines
6.9 KiB
C++
/*
|
|
* LPSolver.cpp
|
|
* @brief:
|
|
* @date: May 1, 2014
|
|
* @author: Duy-Nguyen Ta
|
|
*/
|
|
|
|
#include <boost/foreach.hpp>
|
|
#include <boost/range/adaptor/map.hpp>
|
|
#include <gtsam/inference/Symbol.h>
|
|
#include <gtsam_unstable/linear/LPSolver.h>
|
|
|
|
using namespace std;
|
|
using namespace gtsam;
|
|
|
|
namespace gtsam {
|
|
|
|
/* ************************************************************************* */
|
|
void LPSolver::buildMetaInformation() {
|
|
size_t firstVarIndex = 1; // Warning: lpsolve's column number starts from 1 !!!
|
|
// Collect variables in objective function first
|
|
BOOST_FOREACH(Key key, objectiveCoeffs_ | boost::adaptors::map_keys) {
|
|
variableColumnNo_.insert(make_pair(key, firstVarIndex));
|
|
variableDims_.insert(make_pair(key,objectiveCoeffs_.dim(key)));
|
|
firstVarIndex += variableDims_[key];
|
|
freeVars_.insert(key);
|
|
}
|
|
// Now collect remaining keys in constraints
|
|
VariableIndex factorIndex(*constraints_);
|
|
BOOST_FOREACH(Key key, factorIndex | boost::adaptors::map_keys) {
|
|
if (!variableColumnNo_.count(key)) {
|
|
JacobianFactor::shared_ptr jacobian = boost::dynamic_pointer_cast<JacobianFactor>
|
|
(constraints_->at(*factorIndex[key].begin()));
|
|
if (!jacobian || !jacobian->isConstrained()) {
|
|
throw std::runtime_error("Invalid constrained graph!");
|
|
}
|
|
size_t dim = jacobian->getDim(jacobian->find(key));
|
|
variableColumnNo_.insert(make_pair(key, firstVarIndex));
|
|
variableDims_.insert(make_pair(key,dim));
|
|
firstVarIndex += variableDims_[key];
|
|
freeVars_.insert(key);
|
|
}
|
|
}
|
|
// Collect the remaining keys in lowerBounds
|
|
BOOST_FOREACH(Key key, lowerBounds_ | boost::adaptors::map_keys){
|
|
if (!variableColumnNo_.count(key)) {
|
|
variableColumnNo_.insert(make_pair(key, firstVarIndex));
|
|
variableDims_.insert(make_pair(key,lowerBounds_.dim(key)));
|
|
firstVarIndex += variableDims_[key];
|
|
}
|
|
freeVars_.erase(key);
|
|
}
|
|
// Collect the remaining keys in upperBounds
|
|
BOOST_FOREACH(Key key, upperBounds_ | boost::adaptors::map_keys){
|
|
if (!variableColumnNo_.count(key)) {
|
|
variableColumnNo_.insert(make_pair(key, firstVarIndex));
|
|
variableDims_.insert(make_pair(key,upperBounds_.dim(key)));
|
|
firstVarIndex += variableDims_[key];
|
|
}
|
|
freeVars_.erase(key);
|
|
}
|
|
|
|
nrColumns_ = firstVarIndex - 1;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void LPSolver::addConstraints(const boost::shared_ptr<lprec>& lp,
|
|
const JacobianFactor::shared_ptr& jacobian) const {
|
|
if (!jacobian || !jacobian->isConstrained())
|
|
throw std::runtime_error("LP only accepts constrained factors!");
|
|
|
|
// Build column number from keys
|
|
KeyVector keys = jacobian->keys();
|
|
vector<int> columnNo = buildColumnNo(keys);
|
|
|
|
// Add each row to lp one by one. TODO: is there a faster way?
|
|
Vector sigmas = jacobian->get_model()->sigmas();
|
|
Matrix A = jacobian->getA();
|
|
Vector b = jacobian->getb();
|
|
for (int i = 0; i<A.rows(); ++i) {
|
|
// A.row(i).data() gives wrong result so have to make a copy
|
|
// TODO: Why? Probably because lpsolve's add_constraintex modifies this raw buffer!!!
|
|
Vector r = A.row(i);
|
|
|
|
// WARNING: lpsolve's add_constraintex modifies the columnNo raw buffer!
|
|
// so we have to make a new copy for every row!!!!!
|
|
vector<int> columnNoCopy(columnNo);
|
|
|
|
if (sigmas[i]>0) {
|
|
cout << "Warning: Ignore Gaussian noise (sigma>0) in LP constraints!" << endl;
|
|
}
|
|
int constraintType = (sigmas[i]<0)?LE:EQ;
|
|
if(!add_constraintex(lp.get(), columnNoCopy.size(), r.data(), columnNoCopy.data(),
|
|
constraintType, b[i]))
|
|
throw runtime_error("LP can't accept Gaussian noise!");
|
|
}
|
|
}
|
|
|
|
|
|
/* ************************************************************************* */
|
|
void LPSolver::addBounds(const boost::shared_ptr<lprec>& lp) const {
|
|
// Set lower bounds
|
|
BOOST_FOREACH(Key key, lowerBounds_ | boost::adaptors::map_keys){
|
|
Vector lb = lowerBounds_.at(key);
|
|
for (size_t i = 0; i<lb.size(); ++i) {
|
|
set_lowbo(lp.get(), variableColumnNo_.at(key)+i, lb[i]);
|
|
}
|
|
}
|
|
|
|
// Set upper bounds
|
|
BOOST_FOREACH(Key key, upperBounds_ | boost::adaptors::map_keys){
|
|
Vector ub = upperBounds_.at(key);
|
|
for (size_t i = 0; i<ub.size(); ++i) {
|
|
set_upbo(lp.get(), variableColumnNo_.at(key)+i, ub[i]);
|
|
}
|
|
}
|
|
|
|
// Set the rest as free variables
|
|
BOOST_FOREACH(Key key, freeVars_) {
|
|
for (size_t i = 0; i<variableDims_.at(key); ++i) {
|
|
set_unbounded(lp.get(), variableColumnNo_.at(key)+i);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
boost::shared_ptr<lprec> LPSolver::buildModel() const {
|
|
boost::shared_ptr<lprec> lp(make_lp(0, nrColumns_));
|
|
|
|
// Makes building the model faster if it is done rows by row
|
|
set_add_rowmode(lp.get(), TRUE);
|
|
|
|
// Add constraints
|
|
BOOST_FOREACH(const GaussianFactor::shared_ptr& factor, *constraints_) {
|
|
JacobianFactor::shared_ptr jacobian =
|
|
boost::dynamic_pointer_cast<JacobianFactor>(factor);
|
|
addConstraints(lp, jacobian);
|
|
}
|
|
|
|
// Add bounds
|
|
addBounds(lp);
|
|
|
|
// Rowmode should be turned off again when done building the model
|
|
set_add_rowmode(lp.get(), FALSE);
|
|
|
|
// Finally, the objective function from the objective coefficients
|
|
KeyVector keys;
|
|
BOOST_FOREACH(Key key, objectiveCoeffs_ | boost::adaptors::map_keys) {
|
|
keys.push_back(key);
|
|
}
|
|
|
|
Vector f = objectiveCoeffs_.vector(keys);
|
|
vector<int> columnNo = buildColumnNo(keys);
|
|
|
|
if(!set_obj_fnex(lp.get(), f.size(), f.data(), columnNo.data()))
|
|
throw std::runtime_error("lpsolve cannot set obj function!");
|
|
|
|
// Set the object direction to minimize
|
|
set_minim(lp.get());
|
|
|
|
// Set lp's verbose
|
|
set_verbose(lp.get(), CRITICAL);
|
|
|
|
return lp;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
VectorValues LPSolver::convertToVectorValues(REAL* row) const {
|
|
VectorValues values;
|
|
BOOST_FOREACH(Key key, variableColumnNo_ | boost::adaptors::map_keys) {
|
|
// Warning: the columnNo starts from 1, but C's array index starts from 0!!
|
|
Vector v = Eigen::Map<Eigen::VectorXd>(&row[variableColumnNo_.at(key)-1], variableDims_.at(key));
|
|
values.insert(key, v);
|
|
}
|
|
return values;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
VectorValues LPSolver::solve() const {
|
|
static const bool debug = false;
|
|
|
|
boost::shared_ptr<lprec> lp = buildModel();
|
|
|
|
/* just out of curioucity, now show the model in lp format on screen */
|
|
/* this only works if this is a console application. If not, use write_lp and a filename */
|
|
if (debug) write_LP(lp.get(), stdout);
|
|
|
|
int ret = ::solve(lp.get());
|
|
if (ret != 0) {
|
|
throw std::runtime_error(
|
|
(boost::format( "lpsolve cannot find the optimal solution and terminates with %d error. "\
|
|
"See lpsolve's solve() documentation for details.") % ret).str());
|
|
}
|
|
REAL* row = NULL;
|
|
get_ptr_variables(lp.get(), &row);
|
|
|
|
VectorValues solution = convertToVectorValues(row);
|
|
|
|
return solution;
|
|
}
|
|
|
|
} /* namespace gtsam */
|