// Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. #pragma once #include "Eigen/Core" #include "units/time.h" #include "unsupported/Eigen/MatrixFunctions" namespace frc { /** * Discretizes the given continuous A matrix. * * @param contA Continuous system matrix. * @param dt Discretization timestep. * @param discA Storage for discrete system matrix. */ template void DiscretizeA(const Eigen::Matrix& contA, units::second_t dt, Eigen::Matrix* discA) { *discA = (contA * dt.value()).exp(); } /** * Discretizes the given continuous A and B matrices. * * @param contA Continuous system matrix. * @param contB Continuous input matrix. * @param dt Discretization timestep. * @param discA Storage for discrete system matrix. * @param discB Storage for discrete input matrix. */ template void DiscretizeAB(const Eigen::Matrix& contA, const Eigen::Matrix& contB, units::second_t dt, Eigen::Matrix* discA, Eigen::Matrix* discB) { // Matrices are blocked here to minimize matrix exponentiation calculations Eigen::Matrix Mcont; Mcont.setZero(); Mcont.template block(0, 0) = contA * dt.value(); Mcont.template block(0, States) = contB * dt.value(); // Discretize A and B with the given timestep Eigen::Matrix Mdisc = Mcont.exp(); *discA = Mdisc.template block(0, 0); *discB = Mdisc.template block(0, States); } /** * Discretizes the given continuous A and Q matrices. * * @param contA Continuous system matrix. * @param contQ Continuous process noise covariance matrix. * @param dt Discretization timestep. * @param discA Storage for discrete system matrix. * @param discQ Storage for discrete process noise covariance matrix. */ template void DiscretizeAQ(const Eigen::Matrix& contA, const Eigen::Matrix& contQ, units::second_t dt, Eigen::Matrix* discA, Eigen::Matrix* discQ) { // Make continuous Q symmetric if it isn't already Eigen::Matrix Q = (contQ + contQ.transpose()) / 2.0; // Set up the matrix M = [[-A, Q], [0, A.T]] Eigen::Matrix M; M.template block(0, 0) = -contA; M.template block(0, States) = Q; M.template block(States, 0).setZero(); M.template block(States, States) = contA.transpose(); Eigen::Matrix phi = (M * dt.value()).exp(); // Phi12 = phi[0:States, States:2*States] // Phi22 = phi[States:2*States, States:2*States] Eigen::Matrix phi12 = phi.block(0, States, States, States); Eigen::Matrix phi22 = phi.block(States, States, States, States); *discA = phi22.transpose(); Q = *discA * phi12; // Make discrete Q symmetric if it isn't already *discQ = (Q + Q.transpose()) / 2.0; } /** * Discretizes the given continuous A and Q matrices. * * Rather than solving a 2N x 2N matrix exponential like in DiscretizeAQ() * (which is expensive), we take advantage of the structure of the block matrix * of A and Q. * * 1) The exponential of A*t, which is only N x N, is relatively cheap. * 2) The upper-right quarter of the 2N x 2N matrix, which we can approximate * using a taylor series to several terms and still be substantially cheaper * than taking the big exponential. * * @param contA Continuous system matrix. * @param contQ Continuous process noise covariance matrix. * @param dt Discretization timestep. * @param discA Storage for discrete system matrix. * @param discQ Storage for discrete process noise covariance matrix. */ template void DiscretizeAQTaylor(const Eigen::Matrix& contA, const Eigen::Matrix& contQ, units::second_t dt, Eigen::Matrix* discA, Eigen::Matrix* discQ) { // Make continuous Q symmetric if it isn't already Eigen::Matrix Q = (contQ + contQ.transpose()) / 2.0; Eigen::Matrix lastTerm = Q; double lastCoeff = dt.value(); // Aᵀⁿ Eigen::Matrix Atn = contA.transpose(); Eigen::Matrix phi12 = lastTerm * lastCoeff; // i = 6 i.e. 5th order should be enough precision for (int i = 2; i < 6; ++i) { lastTerm = -contA * lastTerm + Q * Atn; lastCoeff *= dt.value() / static_cast(i); phi12 += lastTerm * lastCoeff; Atn *= contA.transpose(); } DiscretizeA(contA, dt, discA); Q = *discA * phi12; // Make discrete Q symmetric if it isn't already *discQ = (Q + Q.transpose()) / 2.0; } /** * Returns a discretized version of the provided continuous measurement noise * covariance matrix. * * @param R Continuous measurement noise covariance matrix. * @param dt Discretization timestep. */ template Eigen::Matrix DiscretizeR( const Eigen::Matrix& R, units::second_t dt) { return R / dt.value(); } } // namespace frc