Austin Schuh | 54e5bb5 | 2018-02-19 01:09:18 -0800 | [diff] [blame] | 1 | #include "y2018/control_loops/superstructure/arm/ekf.h" |
| 2 | |
| 3 | #include "Eigen/Dense" |
| 4 | #include <iostream> |
| 5 | |
| 6 | #include "frc971/control_loops/jacobian.h" |
| 7 | #include "y2018/control_loops/superstructure/arm/dynamics.h" |
| 8 | |
Austin Schuh | cb09171 | 2018-02-21 20:01:55 -0800 | [diff] [blame] | 9 | DEFINE_double(proximal_voltage_error_uncertainty, 8.0, |
| 10 | "Proximal joint voltage error uncertainty."); |
| 11 | DEFINE_double(distal_voltage_error_uncertainty, 2.0, |
| 12 | "Distal joint voltage error uncertainty."); |
| 13 | |
Austin Schuh | 54e5bb5 | 2018-02-19 01:09:18 -0800 | [diff] [blame] | 14 | namespace y2018 { |
| 15 | namespace control_loops { |
| 16 | namespace superstructure { |
| 17 | namespace arm { |
| 18 | |
| 19 | namespace { |
| 20 | // TODO(austin): When tuning this, make sure to verify that you are waiting |
| 21 | // enough cycles to make sure it converges at startup. Otherwise you will have a |
| 22 | // bad day. |
Austin Schuh | cb09171 | 2018-02-21 20:01:55 -0800 | [diff] [blame] | 23 | ::Eigen::Matrix<double, 6, 6> Q_covariance( |
Austin Schuh | 54e5bb5 | 2018-02-19 01:09:18 -0800 | [diff] [blame] | 24 | (::Eigen::DiagonalMatrix<double, 6>().diagonal() << ::std::pow(0.1, 2), |
| 25 | ::std::pow(2.0, 2), ::std::pow(0.1, 2), ::std::pow(2.0, 2), |
Austin Schuh | cb09171 | 2018-02-21 20:01:55 -0800 | [diff] [blame] | 26 | ::std::pow(FLAGS_proximal_voltage_error_uncertainty, 2), |
| 27 | ::std::pow(FLAGS_distal_voltage_error_uncertainty, 2)) |
Austin Schuh | 54e5bb5 | 2018-02-19 01:09:18 -0800 | [diff] [blame] | 28 | .finished() |
| 29 | .asDiagonal()); |
| 30 | } // namespace |
| 31 | |
| 32 | EKF::EKF() { |
| 33 | X_hat_.setZero(); |
Austin Schuh | cb09171 | 2018-02-21 20:01:55 -0800 | [diff] [blame] | 34 | Q_covariance = |
| 35 | ((::Eigen::DiagonalMatrix<double, 6>().diagonal() << ::std::pow(0.1, 2), |
| 36 | ::std::pow(2.0, 2), ::std::pow(0.1, 2), ::std::pow(2.0, 2), |
| 37 | ::std::pow(FLAGS_proximal_voltage_error_uncertainty, 2), |
| 38 | ::std::pow(FLAGS_distal_voltage_error_uncertainty, 2)) |
| 39 | .finished() |
| 40 | .asDiagonal()); |
Austin Schuh | 54e5bb5 | 2018-02-19 01:09:18 -0800 | [diff] [blame] | 41 | P_ = Q_covariance; |
Austin Schuh | cb09171 | 2018-02-21 20:01:55 -0800 | [diff] [blame] | 42 | P_reset_ = P_; |
Austin Schuh | 54e5bb5 | 2018-02-19 01:09:18 -0800 | [diff] [blame] | 43 | //::std::cout << "Reset P: " << P_ << ::std::endl; |
| 44 | // TODO(austin): Running the EKF 2000 cycles works, but isn't super clever. |
| 45 | // We could just solve the DARE. |
| 46 | |
| 47 | for (int i = 0; i < 1000; ++i) { |
| 48 | Predict(::Eigen::Matrix<double, 2, 1>::Zero(), 0.00505); |
| 49 | Correct(::Eigen::Matrix<double, 2, 1>::Zero(), 0.00505); |
| 50 | } |
Austin Schuh | cb09171 | 2018-02-21 20:01:55 -0800 | [diff] [blame] | 51 | P_half_converged_ = P_; |
Austin Schuh | 54e5bb5 | 2018-02-19 01:09:18 -0800 | [diff] [blame] | 52 | //::std::cout << "Stabilized P: " << P_ << ::std::endl; |
| 53 | for (int i = 0; i < 1000; ++i) { |
| 54 | Predict(::Eigen::Matrix<double, 2, 1>::Zero(), 0.00505); |
| 55 | Correct(::Eigen::Matrix<double, 2, 1>::Zero(), 0.00505); |
| 56 | } |
| 57 | //::std::cout << "Really stabilized P: " << P_ << ::std::endl; |
Austin Schuh | cb09171 | 2018-02-21 20:01:55 -0800 | [diff] [blame] | 58 | P_converged_ = P_; |
Austin Schuh | 54e5bb5 | 2018-02-19 01:09:18 -0800 | [diff] [blame] | 59 | |
| 60 | Reset(::Eigen::Matrix<double, 4, 1>::Zero()); |
| 61 | } |
| 62 | |
| 63 | void EKF::Reset(const ::Eigen::Matrix<double, 4, 1> &X) { |
| 64 | X_hat_.setZero(); |
Austin Schuh | cb09171 | 2018-02-21 20:01:55 -0800 | [diff] [blame] | 65 | P_ = P_converged_; |
Austin Schuh | 54e5bb5 | 2018-02-19 01:09:18 -0800 | [diff] [blame] | 66 | X_hat_.block<4, 1>(0, 0) = X; |
| 67 | } |
| 68 | |
| 69 | void EKF::Predict(const ::Eigen::Matrix<double, 2, 1> &U, double dt) { |
| 70 | const ::Eigen::Matrix<double, 6, 6> A = |
| 71 | ::frc971::control_loops::NumericalJacobianX<6, 2>( |
| 72 | Dynamics::UnboundedEKFDiscreteDynamics, X_hat_, U, dt); |
| 73 | |
| 74 | X_hat_ = Dynamics::UnboundedEKFDiscreteDynamics(X_hat_, U, dt); |
| 75 | P_ = A * P_ * A.transpose() + Q_covariance; |
| 76 | } |
| 77 | |
| 78 | void EKF::Correct(const ::Eigen::Matrix<double, 2, 1> &Y, double /*dt*/) { |
| 79 | const ::Eigen::Matrix<double, 2, 2> R_covariance( |
| 80 | (::Eigen::DiagonalMatrix<double, 2>().diagonal() << ::std::pow(0.01, 2), |
| 81 | ::std::pow(0.01, 2)) |
| 82 | .finished() |
| 83 | .asDiagonal()); |
| 84 | // H is the jacobian of the h(x) measurement prediction function |
| 85 | const ::Eigen::Matrix<double, 2, 6> H_jacobian = |
| 86 | (::Eigen::Matrix<double, 2, 6>() << 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, |
| 87 | 0.0, 0.0, 1.0, 0.0, 0.0, 0.0) |
| 88 | .finished(); |
| 89 | |
| 90 | // Update step Measurement residual error of proximal and distal joint |
| 91 | // angles. |
| 92 | const ::Eigen::Matrix<double, 2, 1> Y_hat = |
| 93 | Y - (::Eigen::Matrix<double, 2, 1>() << X_hat_(0), X_hat_(2)).finished(); |
| 94 | // Residual covariance |
| 95 | const ::Eigen::Matrix<double, 2, 2> S = |
| 96 | H_jacobian * P_ * H_jacobian.transpose() + R_covariance; |
| 97 | |
| 98 | // K is the Near-optimal Kalman gain |
| 99 | const ::Eigen::Matrix<double, 6, 2> kalman_gain = |
| 100 | P_ * H_jacobian.transpose() * S.inverse(); |
| 101 | // Updated state estimate |
| 102 | X_hat_ = X_hat_ + kalman_gain * Y_hat; |
| 103 | // Updated covariance estimate |
| 104 | P_ = (::Eigen::Matrix<double, 6, 6>::Identity() - kalman_gain * H_jacobian) * |
| 105 | P_; |
| 106 | } |
| 107 | |
| 108 | } // namespace arm |
| 109 | } // namespace superstructure |
| 110 | } // namespace control_loops |
| 111 | } // namespace y2018 |