/*----------------------------------------------------------------------------*/ /* Copyright (c) 2020 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ /*----------------------------------------------------------------------------*/ #pragma once #include #include #include "Eigen/Core" #include "frc/system/NumericalJacobian.h" #include "units/time.h" namespace frc { /** * Constructs a control-affine plant inversion model-based feedforward from * given model dynamics. * * If given the vector valued function as f(x, u) where x is the state * vector and u is the input vector, the B matrix(continuous input matrix) * is calculated through a NumericalJacobian. In this case f has to be * control-affine (of the form f(x) + Bu). * * The feedforward is calculated as * u_ff = B+ (rDot - f(x)) , where * B+ is the pseudoinverse of B. * * This feedforward does not account for a dynamic B matrix, B is either * determined or supplied when the feedforward is created and remains constant. * * For more on the underlying math, read * https://file.tavsys.net/control/controls-engineering-in-frc.pdf. */ template class ControlAffinePlantInversionFeedforward { public: /** * Constructs a feedforward with given model dynamics as a function * of state and input. * * @param f A vector-valued function of x, the state, and * u, the input, that returns the derivative of * the state vector. HAS to be control-affine * (of the form f(x) + Bu). * @param dt The timestep between calls of calculate(). */ ControlAffinePlantInversionFeedforward( std::function( const Eigen::Matrix&, const Eigen::Matrix&)> f, units::second_t dt) : m_dt(dt), m_f(f) { m_B = NumericalJacobianU( f, Eigen::Matrix::Zero(), Eigen::Matrix::Zero()); m_r.setZero(); Reset(m_r); } /** * Constructs a feedforward with given model dynamics as a function of state, * and the plant's B matrix(continuous input matrix). * * @param f A vector-valued function of x, the state, * that returns the derivative of the state vector. * @param B Continuous input matrix of the plant being controlled. * @param dt The timestep between calls of calculate(). */ ControlAffinePlantInversionFeedforward( std::function( const Eigen::Matrix&)> f, const Eigen::Matrix& B, units::second_t dt) : m_B(B), m_dt(dt) { m_f = [=](const Eigen::Matrix& x, const Eigen::Matrix& u) -> Eigen::Matrix { return f(x); }; m_r.setZero(); Reset(m_r); } ControlAffinePlantInversionFeedforward( ControlAffinePlantInversionFeedforward&&) = default; ControlAffinePlantInversionFeedforward& operator=( ControlAffinePlantInversionFeedforward&&) = default; /** * Returns the previously calculated feedforward as an input vector. * * @return The calculated feedforward. */ const Eigen::Matrix& Uff() const { return m_uff; } /** * Returns an element of the previously calculated feedforward. * * @param row Row of uff. * * @return The row of the calculated feedforward. */ double Uff(int i) const { return m_uff(i, 0); } /** * Returns the current reference vector r. * * @return The current reference vector. */ const Eigen::Matrix& R() const { return m_r; } /** * Returns an element of the reference vector r. * * @param i Row of r. * * @return The row of the current reference vector. */ double R(int i) const { return m_r(i, 0); } /** * Resets the feedforward with a specified initial state vector. * * @param initialState The initial state vector. */ void Reset(const Eigen::Matrix& initialState) { m_r = initialState; m_uff.setZero(); } /** * Resets the feedforward with a zero initial state vector. */ void Reset() { m_r.setZero(); m_uff.setZero(); } /** * Calculate the feedforward with only the desired * future reference. This uses the internally stored "current" * reference. * * If this method is used the initial state of the system is the one * set using Reset(const Eigen::Matrix&). * If the initial state is not set it defaults to a zero vector. * * @param nextR The reference state of the future timestep (k + dt). * * @return The calculated feedforward. */ Eigen::Matrix Calculate( const Eigen::Matrix& nextR) { return Calculate(m_r, nextR); } /** * Calculate the feedforward with current and future reference vectors. * * @param r The reference state of the current timestep (k). * @param nextR The reference state of the future timestep (k + dt). * * @return The calculated feedforward. */ Eigen::Matrix Calculate( const Eigen::Matrix& r, const Eigen::Matrix& nextR) { Eigen::Matrix rDot = (nextR - r) / m_dt.to(); m_uff = m_B.householderQr().solve( rDot - m_f(r, Eigen::Matrix::Zero())); m_r = nextR; return m_uff; } private: Eigen::Matrix m_B; units::second_t m_dt; /** * The model dynamics. */ std::function( const Eigen::Matrix&, const Eigen::Matrix&)> m_f; // Current reference Eigen::Matrix m_r; // Computed feedforward Eigen::Matrix m_uff; }; } // namespace frc