7#include <smooth/concepts/lie_group.hpp>
8#include <smooth/lie_sparse.hpp>
15namespace smooth::feedback {
22template<Time T, LieGroup X>
26 std::function<X(T)> xdes = [](T) -> X {
return Default<X>(); };
27 std::function<Tangent<X>(T)> dxdes = [](T) -> Tangent<X> {
return Tangent<X>::Zero(); };
29 X operator()(
const double t_rel)
const
35 Tangent<X> jacobian(
const double t_rel)
const
45template<Time T, Manifold U>
49 std::function<U(T)> udes = [](T) -> U {
return Default<U>(); };
51 U operator()(
const double t_rel)
const
71 static constexpr auto Nx = Dof<X>;
74 X xf_des = Default<X>();
75 Eigen::Matrix<double, Nx, Nx> Qtf = Eigen::Matrix<double, Nx, Nx>::Identity();
78 Eigen::SparseMatrix<double> hess{1 + 2 * Nx + 1, 1 + 2 * Nx + 1};
84 const double,
const X &, [[maybe_unused]]
const X & xf, [[maybe_unused]]
const Eigen::Vector<double, 1> & q)
const
87 assert(xf_des.isApprox(xf, 1e-4));
88 if constexpr (
false) {
89 const auto e = rminus(xf, xf_des);
90 return 0.5 * e.dot(Qtf * e) + q(0);
96 Eigen::RowVector<double, 1 + 2 * Nx + 1>
97 jacobian(
const double,
const X &, [[maybe_unused]]
const X & xf,
const Eigen::Vector<double, 1> &)
99 assert(xf_des.isApprox(xf, 1e-4));
100 return Eigen::RowVector<double, 1 + 2 * Nx + 1>::Unit(1 + 2 * Nx);
103 std::reference_wrapper<const Eigen::SparseMatrix<double>>
104 hessian(
const double,
const X &, [[maybe_unused]]
const X & xf,
const Eigen::Vector<double, 1> &)
106 assert(xf_des.isApprox(xf, 1e-4));
110 for (
auto i = 0u; i < Nx; ++i) {
111 for (
auto j = 0u; j < Nx; ++j) {
112 if (Qtf(i, j) != 0) { hess.coeffRef(1 + i, 1 + j) = Qtf(i, j); }
116 hess.makeCompressed();
124template<Time T, LieGroup X, Manifold U,
typename F, diff::Type DT>
127 static constexpr auto Nx = Dof<X>;
128 static constexpr auto Nu = Dof<U>;
135 Tangent<X> operator()(
const double t,
const X & x,
const U & u)
137 if constexpr (
requires(F & fvar, T tvar) { fvar.set_time(tvar); }) { f.set_time(
time_trait<T>::plus(t0, t)); }
142 Eigen::Matrix<double, Nx, 1 + Nx + Nu> jacobian(
const double t,
const X & x,
const U & u)
144 if constexpr (
requires(F & fvar, T tvar) { fvar.set_time(tvar); }) { f.set_time(
time_trait<T>::plus(t0, t)); }
146 const auto & [fval, df] = diff::dr<1, DT>(f, smooth::wrt(x, u));
148 Eigen::Matrix<double, Nx, 1 + Nx + Nu> ret;
149 ret << Eigen::Vector<double, Nx>::Zero(), df;
165template<Time T, LieGroup X, Manifold U>
168 static constexpr auto Nx = Dof<X>;
169 static constexpr auto Nu = Dof<U>;
172 std::shared_ptr<XDes<T, X>> xdes;
173 std::shared_ptr<UDes<T, U>> udes;
175 Eigen::Matrix<double, Nx, Nx> Q = Eigen::Matrix<double, Nx, Nx>::Identity();
176 Eigen::Matrix<double, Nu, Nu> R = Eigen::Matrix<double, Nu, Nu>::Identity();
179 Eigen::SparseMatrix<double> hess{1 + Nx + Nu, 1 + Nx + Nu};
184 Eigen::Vector<double, 1>
185 operator()([[maybe_unused]]
const double t_rel, [[maybe_unused]]
const X & x, [[maybe_unused]]
const U & u)
const
187 assert((*xdes)(t_rel).isApprox(x, 1e-4));
188 assert((*udes)(t_rel).isApprox(u, 1e-4));
189 if constexpr (
false) {
190 const auto ex = rminus(x, (*xdes)(t_rel));
191 const auto eu = rminus(u, (*udes)(t_rel));
192 return Eigen::Vector<double, 1>{0.5 * ex.dot(Q * ex) + 0.5 * eu.dot(R * eu)};
194 return Eigen::Vector<double, 1>{0.};
198 Eigen::RowVector<double, 1 + Nx + Nu>
199 jacobian([[maybe_unused]]
const double t_rel, [[maybe_unused]]
const X & x, [[maybe_unused]]
const U & u)
201 assert((*xdes)(t_rel).isApprox(x, 1e-4));
202 assert((*udes)(t_rel).isApprox(u, 1e-4));
203 return Eigen::RowVector<double, 1 + Nx + Nu>::Zero();
206 std::reference_wrapper<const Eigen::SparseMatrix<double>>
207 hessian([[maybe_unused]]
const double t_rel, [[maybe_unused]]
const X & x, [[maybe_unused]]
const U & u)
209 assert((*xdes)(t_rel).isApprox(x, 1e-4));
210 assert((*udes)(t_rel).isApprox(u, 1e-4));
214 for (
auto i = 0u; i < Nx; ++i) {
215 for (
auto j = 0u; j < Nx; ++j) {
216 if (Q(i, j) != 0) { hess.coeffRef(1 + i, 1 + j) = Q(i, j); }
219 for (
auto i = 0u; i < Nu; ++i) {
220 for (
auto j = 0u; j < Nu; ++j) {
221 if (Q(i, j) != 0) { hess.coeffRef(1 + Nx + i, 1 + Nx + j) = R(i, j); }
225 hess.makeCompressed();
233template<Time T, LieGroup X, Manifold U,
typename F, diff::Type DT>
236 static constexpr auto Nx = Dof<X>;
237 static constexpr auto Nu = Dof<U>;
238 static constexpr auto Ncr = std::invoke_result_t<F, X, U>::SizeAtCompileTime;
245 Eigen::SparseMatrix<double> jac{Ncr, 1 + Nx + Nu};
248 Eigen::Vector<double, Ncr> operator()(
const double t,
const X & x,
const U & u)
250 if constexpr (
requires(F & fvar, T tvar) { fvar.set_time(tvar); }) { f.set_time(
time_trait<T>::plus(t0, t)); }
255 std::reference_wrapper<const Eigen::SparseMatrix<double>> jacobian(
const double t,
const X & x,
const U & u)
257 if constexpr (
requires(F & fvar, T tvar) { fvar.set_time(tvar); }) { f.set_time(
time_trait<T>::plus(t0, t)); }
259 const auto & [fval, df] = diff::dr<1, DT>(f, smooth::wrt(x, u));
262 jac.makeCompressed();
278 static constexpr auto Nx = Dof<X>;
281 X x0_fix = Default<X>();
284 Eigen::SparseMatrix<double> dexpinv_tmp = d_exp_sparse_pattern<X>;
285 Eigen::SparseMatrix<double> jac{Nx, 1 + 2 * Nx + 1};
288 Tangent<X> operator()(
const double,
const X & x0,
const X &,
const Eigen::Vector<double, 1> &)
const
290 return rminus(x0, x0_fix);
293 std::reference_wrapper<const Eigen::SparseMatrix<double>>
294 jacobian(
const double,
const X & x0,
const X &,
const Eigen::Vector<double, 1> &)
296 dr_expinv_sparse<X>(dexpinv_tmp, rminus(x0, x0_fix));
298 jac.middleCols(1, Nx) = dexpinv_tmp;
299 jac.makeCompressed();
344template<LieGroup X, Manifold U>
347 static constexpr auto Nx = Dof<X>;
348 static constexpr auto Nu = Dof<U>;
351 Eigen::Matrix<double, Nx, Nx>
Q = Eigen::Matrix<double, Nx, Nx>::Identity();
353 Eigen::Matrix<double, Nx, Nx>
Qtf = Eigen::Matrix<double, Nx, Nx>::Identity();
355 Eigen::Matrix<double, Nu, Nu>
R = Eigen::Matrix<double, Nu, Nu>::Identity();
378 std::size_t Kmesh = 4,
379 diff::Type DT = diff::Type::Default>
382 static constexpr auto Ncr = std::invoke_result_t<CR, X, U>::SizeAtCompileTime;
406 F && f, CR && cr, Eigen::Vector<double, Ncr> && crl, Eigen::Vector<double, Ncr> && cru,
MPCParams && prm = {})
407 : xdes_{std::make_shared<detail::XDes<T, X>>()}, udes_{std::make_shared<detail::UDes<T, U>>()},
408 mesh_{(prm.K + Kmesh - 1) / Kmesh},
411 .f = {.f = std::forward<F>(f)},
412 .g = {.xdes = xdes_, .udes = udes_},
413 .cr = {.f = std::forward<CR>(cr)},
414 .crl = std::move(crl),
415 .cru = std::move(cru),
417 .cel = Eigen::Vector<double, Dof<X>>::Zero(),
418 .ceu = Eigen::Vector<double, Dof<X>>::Zero(),
420 prm_{std::move(prm)}, qp_solver_{prm_.
qp}
422 detail::ocp_to_qp_allocate<DT>(qp_, work_, ocp_, mesh_);
423 ocp_to_qp_update<diff::Type::Analytic>(qp_, work_, ocp_, mesh_, prm_.
tf, *xdes_, *udes_);
424 qp_solver_.analyze(qp_);
430 const Eigen::Vector<double, Ncr> & crl,
431 const Eigen::Vector<double, Ncr> & cru,
433 :
MPC(F(f), CR(cr), Eigen::Vector<double, Ncr>(crl), Eigen::Vector<double, Ncr>(cru),
MPCParams(prm))
461 std::optional<std::reference_wrapper<std::vector<U>>> u_traj = std::nullopt,
462 std::optional<std::reference_wrapper<std::vector<X>>> x_traj = std::nullopt)
464 static constexpr auto Nx = Dof<X>;
465 static constexpr auto Nu = Dof<U>;
467 const auto N = mesh_.N_colloc();
468 const auto xvar_L = Nx * (N + 1);
469 const auto xvar_B = 0u;
470 const auto uvar_B = xvar_L;
475 ocp_.theta.xf_des = (*xdes_)(prm_.
tf);
481 ocp_to_qp_update_dyn<diff::Type::Analytic>(qp_, work_, ocp_, mesh_, prm_.
tf, *xdes_, *udes_);
482 if constexpr (
requires(CR & crvar, T tvar) { crvar.set_time(tvar); }) {
484 ocp_to_qp_update_cr<diff::Type::Analytic>(qp_, work_, ocp_, mesh_, prm_.
tf, *xdes_, *udes_);
486 ocp_to_qp_update_ce<diff::Type::Analytic>(qp_, work_, ocp_, mesh_, prm_.
tf, *xdes_, *udes_);
487 qp_.
A.makeCompressed();
488 qp_.
P.makeCompressed();
491 const auto & sol = qp_solver_.solve(qp_, warmstart_);
494 if (u_traj.has_value()) {
495 u_traj.value().get().resize(N);
496 for (
const auto & [i, tau] : zip(std::views::iota(0u, N), mesh_.all_nodes())) {
497 const double t_rel = prm_.
tf * tau;
498 u_traj.value().get()[i] = (*udes_)(t_rel) + sol.primal.template segment<Nu>(uvar_B + i * Nu);
501 if (x_traj.has_value()) {
502 x_traj.value().get().resize(N + 1);
503 for (
const auto & [i, tau] : zip(std::views::iota(0u, N + 1), mesh_.all_nodes())) {
504 const double t_rel = prm_.
tf * tau;
505 x_traj.value().get()[i] = (*xdes_)(t_rel) + sol.primal.template segment<Nx>(xvar_B + i * Nx);
512 if (sol.code == QPSolutionStatus::Optimal || sol.code == QPSolutionStatus::MaxTime || sol.code == QPSolutionStatus::MaxIterations) {
518 return {rplus((*udes_)(0), sol.primal.template segment<Nu>(uvar_B)), sol.code};
524 inline void set_udes(std::function<U(T)> && u_des) { udes_->udes = std::move(u_des); }
529 inline void set_udes(
const std::function<U(T)> & u_des) {
set_udes(std::function<U(T)>(u_des)); }
537 template<
typename Fun>
538 requires(std::is_same_v<std::invoke_result_t<Fun, Scalar<U>>, U>)
541 set_udes([t0 = t0, f = std::forward<Fun>(f)](T t_abs) -> U {
543 return std::invoke(f, t_rel);
550 inline void set_xdes(std::function<X(T)> && x_des, std::function<Tangent<X>(T)> && dx_des)
552 xdes_->xdes = std::move(x_des);
553 xdes_->dxdes = std::move(dx_des);
559 inline void set_xdes(
const std::function<X(T)> & x_des,
const std::function<Tangent<X>(T)> & dx_des)
561 set_xdes(std::function<X(T)>(x_des), std::function<Tangent<X>(T)>(dx_des));
573 template<
typename Fun>
574 requires(std::is_same_v<std::invoke_result_t<Fun, Scalar<X>>, X>)
577 std::function<X(T)> x_des = [t0 = t0, f = f](T t_abs) -> X {
579 return std::invoke(f, t_rel);
582 std::function<Tangent<X>(T)> dx_des = [t0 = t0, f = f](T t_abs) -> Tangent<X> {
584 return std::get<1>(diff::dr<1, DT>(f, wrt(t_rel)));
587 set_xdes(std::move(x_des), std::move(dx_des));
595 ocp_.g.R = weights.
R;
596 ocp_.g.Q = weights.
Q;
597 ocp_.theta.Qtf = weights.
Qtf;
607 std::shared_ptr<detail::XDes<T, X>> xdes_;
608 std::shared_ptr<detail::UDes<T, U>> udes_;
618 detail::MPCDyn<T, X, U, F, DT>,
619 detail::MPCIntegrand<T, X, U>,
620 detail::MPCCR<T, X, U, CR, DT>,
628 detail::OcpToQpWorkmemory work_;
629 QuadraticProgramSparse<double> qp_;
632 QPSolver<QuadraticProgramSparse<double>> qp_solver_;
635 std::optional<QPSolution<-1, -1,
double>> warmstart_{};
Model-Predictive Control (MPC) on Lie groups.
~MPC()=default
Default destructor.
MPC & operator=(const MPC &)=default
Default copy assignment.
void set_xdes(const std::function< X(T)> &x_des, const std::function< Tangent< X >(T)> &dx_des)
Set the desired state trajectory (absolute time, rvalue version)
void reset_warmstart()
Reset initial guess for next iteration to zero.
void set_xdes(std::function< X(T)> &&x_des, std::function< Tangent< X >(T)> &&dx_des)
Set the desired state trajectory and velocity (absolute time)
MPC(const MPC &)=default
Default copy constructor.
MPC & operator=(MPC &&)=default
Default move assignment.
MPC(MPC &&)=default
Default move constructor.
void set_xdes_rel(Fun &&f, T t0=T(0))
Set the desired state trajectry (relative time, automatic differentiation).
void set_weights(const MPCWeights< X, U > &weights)
Update MPC weights.
void set_udes(std::function< U(T)> &&u_des)
Set the desired input trajectory (absolute time)
std::pair< U, QPSolutionStatus > operator()(const T &t, const X &x, std::optional< std::reference_wrapper< std::vector< U > > > u_traj=std::nullopt, std::optional< std::reference_wrapper< std::vector< X > > > x_traj=std::nullopt)
Calculate new MPC input.
MPC()=default
Default constructor.
MPC(F &&f, CR &&cr, Eigen::Vector< double, Ncr > &&crl, Eigen::Vector< double, Ncr > &&cru, MPCParams &&prm={})
Create an MPC instance.
MPC(const F &f, const CR &cr, const Eigen::Vector< double, Ncr > &crl, const Eigen::Vector< double, Ncr > &cru, const MPCParams &prm={})
Same as above but for lvalues.
void set_udes_rel(Fun &&f, T t0=T(0))
Set the desired input trajectory (relative time).
void set_udes(const std::function< U(T)> &u_des)
Set the desired input trajectory (absolute time, rvalue version)
Collocation mesh of interval [0, 1].
A Time type supports right-addition with a double, and subtraction of two time types should be conver...
void set_zero(MeshValue< Deriv > &mv)
Reset MeshValue to zeros.
Formulate optimal control problem as a quadratic program.
Quadratic Program solver.
void block_write(Eigen::SparseMatrix< double, Options > &dest, Eigen::Index row0, Eigen::Index col0, const Source &source, double scale=1, bool upper_only=false)
Write block into a sparse matrix.
double tf
MPC time horizon (seconds)
std::size_t K
Minimum number of collocation points.
QPSolverParams qp
QP solvers parameters.
bool warmstart
Enable warmstarting.
Objective weights for MPC.
Eigen::Matrix< double, Nx, Nx > Q
Running state cost.
Eigen::Matrix< double, Nx, Nx > Qtf
Final state cost.
Eigen::Matrix< double, Nu, Nu > R
Running input cost.
Eigen::SparseMatrix< Scalar, Eigen::RowMajor > A
Inequality matrix.
Eigen::SparseMatrix< Scalar > P
Positive semi-definite square cost (only upper trianglular part is used)
MPC end constraint function.
MPC running constraints (jacobian obtained via automatic differentiation).
MPC dynamics (derivatives obtained via autodiff).
Wrapper for desired input and its derivative.
Wrapper for desired trajectory and its derivative.
Trait class to specify Time operations.