204 lines
7.5 KiB
C++
204 lines
7.5 KiB
C++
/* ----------------------------------------------------------------------------
|
|
|
|
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
|
|
* Atlanta, Georgia 30332-0415
|
|
* All Rights Reserved
|
|
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
|
|
|
|
* See LICENSE for the license information
|
|
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
* @file ActiveSetSolver.h
|
|
* @brief Active set method for solving LP, QP problems
|
|
* @author Ivan Dario Jimenez
|
|
* @author Duy Nguyen Ta
|
|
* @date 1/25/16
|
|
*/
|
|
#pragma once
|
|
|
|
#include <gtsam/linear/GaussianFactorGraph.h>
|
|
#include <gtsam_unstable/linear/InequalityFactorGraph.h>
|
|
#include <boost/range/adaptor/map.hpp>
|
|
|
|
namespace gtsam {
|
|
|
|
/**
|
|
* This class implements the active set algorithm for solving convex
|
|
* Programming problems.
|
|
*
|
|
* @tparam PROBLEM Type of the problem to solve, e.g. LP (linear program) or
|
|
* QP (quadratic program).
|
|
* @tparam POLICY specific detail policy tailored for the particular program
|
|
* @tparam INITSOLVER Solver for an initial feasible solution of this problem.
|
|
*/
|
|
template <class PROBLEM, class POLICY, class INITSOLVER>
|
|
class ActiveSetSolver {
|
|
public:
|
|
/// This struct contains the state information for a single iteration
|
|
struct State {
|
|
VectorValues values; //!< current best values at each step
|
|
VectorValues duals; //!< current values of dual variables at each step
|
|
InequalityFactorGraph workingSet; /*!< keep track of current active/inactive
|
|
inequality constraints */
|
|
bool converged; //!< True if the algorithm has converged to a solution
|
|
size_t iterations; /*!< Number of iterations. Incremented at the end of
|
|
each iteration. */
|
|
|
|
/// Default constructor
|
|
State()
|
|
: values(), duals(), workingSet(), converged(false), iterations(0) {}
|
|
|
|
/// Constructor with initial values
|
|
State(const VectorValues& initialValues, const VectorValues& initialDuals,
|
|
const InequalityFactorGraph& initialWorkingSet, bool _converged,
|
|
size_t _iterations)
|
|
: values(initialValues),
|
|
duals(initialDuals),
|
|
workingSet(initialWorkingSet),
|
|
converged(_converged),
|
|
iterations(_iterations) {}
|
|
};
|
|
|
|
protected:
|
|
const PROBLEM& problem_; //!< the particular [convex] problem to solve
|
|
VariableIndex equalityVariableIndex_,
|
|
inequalityVariableIndex_; /*!< index to corresponding factors to build
|
|
dual graphs */
|
|
KeySet constrainedKeys_; /*!< all constrained keys, will become factors in
|
|
dual graphs */
|
|
|
|
/// Vector of key matrix pairs. Matrices are usually the A term for a factor.
|
|
typedef std::vector<std::pair<Key, Matrix> > TermsContainer;
|
|
|
|
public:
|
|
/// Constructor
|
|
ActiveSetSolver(const PROBLEM& problem) : problem_(problem) {
|
|
equalityVariableIndex_ = VariableIndex(problem_.equalities);
|
|
inequalityVariableIndex_ = VariableIndex(problem_.inequalities);
|
|
constrainedKeys_ = problem_.equalities.keys();
|
|
constrainedKeys_.merge(problem_.inequalities.keys());
|
|
}
|
|
|
|
/**
|
|
* Optimize with provided initial values
|
|
* For this version, it is the responsibility of the caller to provide
|
|
* a feasible initial value, otherwise, an exception will be thrown.
|
|
* @return a pair of <primal, dual> solutions
|
|
*/
|
|
std::pair<VectorValues, VectorValues> optimize(
|
|
const VectorValues& initialValues,
|
|
const VectorValues& duals = VectorValues(),
|
|
bool useWarmStart = false) const;
|
|
|
|
/**
|
|
* For this version the caller will not have to provide an initial value
|
|
* @return a pair of <primal, dual> solutions
|
|
*/
|
|
std::pair<VectorValues, VectorValues> optimize() const;
|
|
|
|
protected:
|
|
/**
|
|
* Compute minimum step size alpha to move from the current point @p xk to the
|
|
* next feasible point along a direction @p p: x' = xk + alpha*p,
|
|
* where alpha \f$\in\f$ [0,maxAlpha].
|
|
*
|
|
* For QP, maxAlpha = 1. For LP: maxAlpha = Inf.
|
|
*
|
|
* @return a tuple of (minAlpha, closestFactorIndex) where closestFactorIndex
|
|
* is the closest inactive inequality constraint that blocks xk to move
|
|
* further and that has the minimum alpha, or (-1, maxAlpha) if there is no
|
|
* such inactive blocking constraint.
|
|
*
|
|
* If there is a blocking constraint, the closest one will be added to the
|
|
* working set and become active in the next iteration.
|
|
*/
|
|
std::tuple<double, int> computeStepSize(
|
|
const InequalityFactorGraph& workingSet, const VectorValues& xk,
|
|
const VectorValues& p, const double& maxAlpha) const;
|
|
|
|
/**
|
|
* Finds the active constraints in the given factor graph and returns the
|
|
* Dual Jacobians used to build a dual factor graph.
|
|
*/
|
|
template<typename FACTOR>
|
|
TermsContainer collectDualJacobians(Key key, const FactorGraph<FACTOR>& graph,
|
|
const VariableIndex& variableIndex) const {
|
|
/*
|
|
* Iterates through each factor in the factor graph and checks
|
|
* whether it's active. If the factor is active it reutrns the A
|
|
* term of the factor.
|
|
*/
|
|
TermsContainer Aterms;
|
|
if (variableIndex.find(key) != variableIndex.end()) {
|
|
for (size_t factorIx : variableIndex[key]) {
|
|
typename FACTOR::shared_ptr factor = graph.at(factorIx);
|
|
if (!factor->active())
|
|
continue;
|
|
Matrix Ai = factor->getA(factor->find(key)).transpose();
|
|
Aterms.push_back(std::make_pair(factor->dualKey(), Ai));
|
|
}
|
|
}
|
|
return Aterms;
|
|
}
|
|
|
|
/**
|
|
* Creates a dual factor from the current workingSet and the key of the
|
|
* the variable used to created the dual factor.
|
|
*/
|
|
JacobianFactor::shared_ptr createDualFactor(
|
|
Key key, const InequalityFactorGraph& workingSet,
|
|
const VectorValues& delta) const;
|
|
|
|
public: /// Just for testing...
|
|
|
|
/// Builds a dual graph from the current working set.
|
|
GaussianFactorGraph buildDualGraph(const InequalityFactorGraph &workingSet,
|
|
const VectorValues &delta) const;
|
|
|
|
/**
|
|
* Build a working graph of cost, equality and active inequality constraints
|
|
* to solve at each iteration.
|
|
* @param workingSet the collection of all cost and constrained factors
|
|
* @param xk current solution, used to build a special quadratic cost in LP
|
|
* @return a new better solution
|
|
*/
|
|
GaussianFactorGraph buildWorkingGraph(
|
|
const InequalityFactorGraph& workingSet,
|
|
const VectorValues& xk = VectorValues()) const;
|
|
|
|
/// Iterate 1 step, return a new state with a new workingSet and values
|
|
State iterate(const State& state) const;
|
|
|
|
/// Identify active constraints based on initial values.
|
|
InequalityFactorGraph identifyActiveConstraints(
|
|
const InequalityFactorGraph& inequalities,
|
|
const VectorValues& initialValues,
|
|
const VectorValues& duals = VectorValues(),
|
|
bool useWarmStart = false) const;
|
|
|
|
/// Identifies active constraints that shouldn't be active anymore.
|
|
int identifyLeavingConstraint(const InequalityFactorGraph& workingSet,
|
|
const VectorValues& lambdas) const;
|
|
|
|
};
|
|
|
|
/**
|
|
* Find the max key in a problem.
|
|
* Useful to determine unique keys for additional slack variables
|
|
*/
|
|
template <class PROBLEM>
|
|
Key maxKey(const PROBLEM& problem) {
|
|
auto keys = problem.cost.keys();
|
|
Key maxKey = *std::max_element(keys.begin(), keys.end());
|
|
if (!problem.equalities.empty())
|
|
maxKey = std::max(maxKey, *problem.equalities.keys().rbegin());
|
|
if (!problem.inequalities.empty())
|
|
maxKey = std::max(maxKey, *problem.inequalities.keys().rbegin());
|
|
return maxKey;
|
|
}
|
|
|
|
} // namespace gtsam
|
|
|
|
#include <gtsam_unstable/linear/ActiveSetSolver-inl.h> |