Tool collection for control and estimation on Lie groups leveraging the smooth library.
Control on Lie groups
These controllers are implemented for systems with dynamics on the form where T
is a smooth::feedback::Time
, X
is a smooth::LieGroup
, and U
is a smooth::Manifold
.
Nonlinearities are handled via linearization around a reference point or trajectory. For group-linear dynamics this automatically results in a linear system in the tangent space, in which case these algorithms are expected to work very well. Linearization is done via automatic differentiation. For this to work with the most performant methods (e.g. autodiff) the functions must be templated on the scalar type. The dynamical system
can be defined via a lambda function that supports automatic differentiation as follows:
#include <Eigen/Core>
#include <smooth/bundle.hpp>
#include <smooth/se2.hpp>
#include <chrono>
using T = std::chrono::duration<double>;
template<typename S>
using X = smooth::Bundle<smooth::SE2<S>, Eigen::Matrix<S, 3, 1>>;
template<typename S>
using U = Eigen::Matrix<S, 2, 1>;
const Eigen::Matrix3d A{
{-0.2, 0, 0},
{0, 0, 0},
{0, 0, -0.4},
};
const Eigen::Matrix<double, 3, 2> B{
{1, 0},
{0, 0},
{0, 1},
};
auto Sigma = []<typename S>(T, const X<S> & x, const U<S> & u) -> smooth::Tangent<X<S>> {
smooth::Tangent<X<S>> dx_dt;
dx_dt.head(3) = x.template part<1>();
dx_dt.tail(3) = A * x.template part<1>() + B * u;
return dx_dt;
};
PID Control
- Model-free
- Assumes that inputs control body acceleration. See
examples/pid_se2.cpp
for an example of allocating PID inputs to actuators.
Example PID controller on SE(2)
#include <smooth/feedpack/pid.hpp>
pid.
set_xdes([](T T) -> std::tuple<smooth::SE2d, Eigen::Vector3d, Eigen::Vector3d> {
return {
smooth::SE2d::Identity(),
Eigen::Vector3d::Zero(),
Eigen::Vector3d::Zero(),
};
});
T t = T(1);
smooth::SE2d x = smooth::SE2d::Random();
Eigen::Vector3d v = Eigen::Vector3d::Random();
Eigen::Vector3d u = pid(t, x, v);
Proportional-Integral-Derivative controller for Lie groups.
void set_xdes(T t0, const smooth::Spline< K, G > &c)
Set desired trajectory as a smooth::Spline.
Model-Predictive Control
- Automatic linearization and time discretization of nonlinear continuous dynamics
- Define state and input reference trajectories via arbitrary functions
T -> X
and T -> U
for a time type T. The bus in the video above uses MPC to track the boundary of the circle.
Example: Model-predictive control for the system Sigma
(see also examples/mpc_asif_vehicle.cpp
)
#include <smooth/feedback/mpc.hpp>
mpc.
set_udes([]<
typename S>(S t) -> U<S> {
return U<S>::Zero(); });
mpc.set_xdes([]<typename S>(S t) -> X<S> { return X<S>::Identity(); });
T t(0);
X<double> x = X<double>::Identity();
auto [u, code] = mpc(t, x);
Model-Predictive Control (MPC) on Lie groups.
void set_udes(std::function< U(T)> &&u_des)
Set the desired input trajectory (absolute time)
Active Set Invariance Filtering (ASIF)
- Minimally invasive filtering of a control input in order to enforce state constraints. The bus in the video above is using an ASIF that avoids the red cylinder.
- Automatic differentiation of nonlinear continuous dynamics and constraints
- Theory (non-Lie group case) is described in e.g. Thomas Gurriet's Ph.D. thesis
Example: Safety filtering for the system Sigma
#include <smooth/feedback/asif.hpp>
auto h = []<typename S>(S, const X<S> & x) -> Eigen::Matrix<S, 1, 1> {
return Eigen::Matrix<S, 1, 1>(x.template part<0>().r2().x() - S(0.2));
};
auto bu = []<typename S>(S, const X<S> &) -> U<S> { return U<S>(1, 1); };
T t = T(1);
X<double> x = X<double>::Random();
U<double> u_des = U<double>::Zero();
auto [u_asif, code] = asif(t, x, u_des, h, bu);
Estimation on Lie groups
Estimators take system models on the form where X
is a smooth::LieGroup
, and measurements on the form .
To use in a feedback loop for a controlled system use partial application:
U<double> u = U<double>::Random();
auto SigmaCL = [&u]<typename S>(double t, const X<S> & x) -> smooth::Tangent<X<S>> {
return Sigma(T(t), x, u.template cast<S>());
};
Extended Kalman Filter
Example: localization with a known 2D landmark for the system SigmaCL
#include <smooth/feedback/ekf.hpp>
Eigen::Vector2d landmark(1, 1);
auto h = [&landmark]<typename S>(const X<S> & x) -> Eigen::Matrix<S, 2, 1> {
return x.template part<0>().inverse() * landmark;
};
Eigen::Matrix<double, 6, 6>::Identity(),
1.
);
Eigen::Vector2d(0.3, 0.6),
Eigen::Matrix2d::Identity()
);
Extended Kalman filter on Lie groups.
void predict(F &&f, const Eigen::MatrixBase< QDer > &Q, Scalar< G > tau, std::optional< Scalar< G > > dt={})
Propagate EKF through dynamics with covariance over a time interval .
void update(F &&h, const Y &y, const Eigen::MatrixBase< RDev > &R)
Update EKF with a measurement where .
G estimate() const
Access filter state estimate.
CovT covariance() const
Access filter covariance.
Optimization
MPC and ASIF relies on online quadratic program optimization.
Fast QP Solver
- Eigen-native port of the operator splitting QP solver.
- Solves both dense and sparse problems.
- Eigen lazy evaluations enable fast SIMD in the compiled assembly.
The plot below compares solution times (lower is better) for random square QPs over three different levels of sparsity. The results suggest that the dense solver is the best choice except for problems that are both large and very sparse. Performance is however highly problem-dependent and should ideally be evaluated on a per-application basis.
The results are generated from the benchmarking program in benchmark/
.
Example: Define and solve a dynamically sized dense quadratic program.
.q = q,
.A = A,
.l = l,
.u = u,
};
Quadratic Program definition.
detail::qp_solution_t< Pbm > solve_qp(const Pbm &pbm, const QPSolverParams &prm, std::optional< std::reference_wrapper< const detail::qp_solution_t< Pbm > > > warmstart={})
Solve a quadratic program using the operator splitting approach.
Quadratic program definition.
Eigen::Matrix< Scalar, N, N > P
Positive semi-definite square cost (only upper triangular part is used)