// 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 #include #include #include #include "Eigen/Core" #include "Eigen/QR" #include "Eigen/src/Eigenvalues/EigenSolver.h" #include "frc/geometry/Pose2d.h" namespace frc { namespace detail { template void MatrixImpl(Matrix& result, T elem, Ts... elems) { constexpr int count = Rows * Cols - (sizeof...(Ts) + 1); result(count / Cols, count % Cols) = elem; if constexpr (sizeof...(Ts) > 0) { MatrixImpl(result, elems...); } } template void CostMatrixImpl(Matrix& result, T elem, Ts... elems) { result(result.rows() - (sizeof...(Ts) + 1)) = 1.0 / std::pow(elem, 2); if constexpr (sizeof...(Ts) > 0) { CostMatrixImpl(result, elems...); } } template void CovMatrixImpl(Matrix& result, T elem, Ts... elems) { result(result.rows() - (sizeof...(Ts) + 1)) = std::pow(elem, 2); if constexpr (sizeof...(Ts) > 0) { CovMatrixImpl(result, elems...); } } template void WhiteNoiseVectorImpl(Matrix& result, T elem, Ts... elems) { std::random_device rd; std::mt19937 gen{rd()}; std::normal_distribution<> distr{0.0, elem}; result(result.rows() - (sizeof...(Ts) + 1)) = distr(gen); if constexpr (sizeof...(Ts) > 0) { WhiteNoiseVectorImpl(result, elems...); } } template bool IsStabilizableImpl(const Eigen::Matrix& A, const Eigen::Matrix& B) { Eigen::EigenSolver> es(A); for (int i = 0; i < States; ++i) { if (es.eigenvalues()[i].real() * es.eigenvalues()[i].real() + es.eigenvalues()[i].imag() * es.eigenvalues()[i].imag() < 1) { continue; } Eigen::Matrix, States, States + Inputs> E; E << es.eigenvalues()[i] * Eigen::Matrix, States, States>::Identity() - A, B; Eigen::ColPivHouseholderQR< Eigen::Matrix, States, States + Inputs>> qr(E); if (qr.rank() < States) { return false; } } return true; } } // namespace detail /** * Creates a matrix from the given list of elements. * * The elements of the matrix are filled in in row-major order. * * @param elems An array of elements in the matrix. * @return A matrix containing the given elements. */ template ...>>> Eigen::Matrix MakeMatrix(Ts... elems) { static_assert( sizeof...(elems) == Rows * Cols, "Number of provided elements doesn't match matrix dimensionality"); Eigen::Matrix result; detail::MatrixImpl(result, elems...); return result; } /** * Creates a cost matrix from the given vector for use with LQR. * * The cost matrix is constructed using Bryson's rule. The inverse square of * each element in the input is taken and placed on the cost matrix diagonal. * * @param costs An array. For a Q matrix, its elements are the maximum allowed * excursions of the states from the reference. For an R matrix, * its elements are the maximum allowed excursions of the control * inputs from no actuation. * @return State excursion or control effort cost matrix. */ template ...>>> Eigen::Matrix MakeCostMatrix( Ts... costs) { Eigen::DiagonalMatrix result; detail::CostMatrixImpl(result.diagonal(), costs...); return result; } /** * Creates a covariance matrix from the given vector for use with Kalman * filters. * * Each element is squared and placed on the covariance matrix diagonal. * * @param stdDevs An array. For a Q matrix, its elements are the standard * deviations of each state from how the model behaves. For an R * matrix, its elements are the standard deviations for each * output measurement. * @return Process noise or measurement noise covariance matrix. */ template ...>>> Eigen::Matrix MakeCovMatrix( Ts... stdDevs) { Eigen::DiagonalMatrix result; detail::CovMatrixImpl(result.diagonal(), stdDevs...); return result; } /** * Creates a cost matrix from the given vector for use with LQR. * * The cost matrix is constructed using Bryson's rule. The inverse square of * each element in the input is taken and placed on the cost matrix diagonal. * * @param costs An array. For a Q matrix, its elements are the maximum allowed * excursions of the states from the reference. For an R matrix, * its elements are the maximum allowed excursions of the control * inputs from no actuation. * @return State excursion or control effort cost matrix. */ template Eigen::Matrix MakeCostMatrix(const std::array& costs) { Eigen::DiagonalMatrix result; auto& diag = result.diagonal(); for (size_t i = 0; i < N; ++i) { diag(i) = 1.0 / std::pow(costs[i], 2); } return result; } /** * Creates a covariance matrix from the given vector for use with Kalman * filters. * * Each element is squared and placed on the covariance matrix diagonal. * * @param stdDevs An array. For a Q matrix, its elements are the standard * deviations of each state from how the model behaves. For an R * matrix, its elements are the standard deviations for each * output measurement. * @return Process noise or measurement noise covariance matrix. */ template Eigen::Matrix MakeCovMatrix( const std::array& stdDevs) { Eigen::DiagonalMatrix result; auto& diag = result.diagonal(); for (size_t i = 0; i < N; ++i) { diag(i) = std::pow(stdDevs[i], 2); } return result; } template ...>>> Eigen::Matrix MakeWhiteNoiseVector(Ts... stdDevs) { Eigen::Matrix result; detail::WhiteNoiseVectorImpl(result, stdDevs...); return result; } /** * Creates a vector of normally distributed white noise with the given noise * intensities for each element. * * @param stdDevs An array whose elements are the standard deviations of each * element of the noise vector. * @return White noise vector. */ template Eigen::Matrix MakeWhiteNoiseVector( const std::array& stdDevs) { std::random_device rd; std::mt19937 gen{rd()}; Eigen::Matrix result; for (int i = 0; i < N; ++i) { // Passing a standard deviation of 0.0 to std::normal_distribution is // undefined behavior if (stdDevs[i] == 0.0) { result(i) = 0.0; } else { std::normal_distribution distr{0.0, stdDevs[i]}; result(i) = distr(gen); } } return result; } /** * Converts a Pose2d into a vector of [x, y, theta]. * * @param pose The pose that is being represented. * * @return The vector. */ Eigen::Matrix PoseTo3dVector(const Pose2d& pose); /** * Converts a Pose2d into a vector of [x, y, std::cos(theta), std::sin(theta)]. * * @param pose The pose that is being represented. * * @return The vector. */ Eigen::Matrix PoseTo4dVector(const Pose2d& pose); /** * Returns true if (A, B) is a stabilizable pair. * * (A,B) is stabilizable if and only if the uncontrollable eigenvalues of A, if * any, have absolute values less than one, where an eigenvalue is * uncontrollable if rank(lambda * I - A, B) < n where n is number of states. * * @param A System matrix. * @param B Input matrix. */ template bool IsStabilizable(const Eigen::Matrix& A, const Eigen::Matrix& B) { return detail::IsStabilizableImpl(A, B); } // Template specializations are used here to make common state-input pairs // compile faster. template <> bool IsStabilizable<1, 1>(const Eigen::Matrix& A, const Eigen::Matrix& B); // Template specializations are used here to make common state-input pairs // compile faster. template <> bool IsStabilizable<2, 1>(const Eigen::Matrix& A, const Eigen::Matrix& B); /** * Converts a Pose2d into a vector of [x, y, theta]. * * @param pose The pose that is being represented. * * @return The vector. */ Eigen::Matrix PoseToVector(const Pose2d& pose); /** * Clamps input vector between system's minimum and maximum allowable input. * * @param u Input vector to clamp. * @return Clamped input vector. */ template Eigen::Matrix ClampInputMaxMagnitude( const Eigen::Matrix& u, const Eigen::Matrix& umin, const Eigen::Matrix& umax) { Eigen::Matrix result; for (int i = 0; i < Inputs; ++i) { result(i) = std::clamp(u(i), umin(i), umax(i)); } return result; } /** * Normalize all inputs if any excedes the maximum magnitude. Useful for systems * such as differential drivetrains. * * @param u The input vector. * @param maxMagnitude The maximum magnitude any input can have. * @param The number of inputs. * @return The normalizedInput */ template Eigen::Matrix NormalizeInputVector( const Eigen::Matrix& u, double maxMagnitude) { double maxValue = u.template lpNorm(); if (maxValue > maxMagnitude) { return u * maxMagnitude / maxValue; } return u; } } // namespace frc