001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.math.estimator; 006 007import edu.wpi.first.math.Matrix; 008import edu.wpi.first.math.Nat; 009import edu.wpi.first.math.Num; 010import edu.wpi.first.math.numbers.N1; 011import org.ejml.simple.SimpleMatrix; 012 013/** 014 * Generates sigma points and weights according to Papakonstantinou's paper[1] for the 015 * UnscentedKalmanFilter class. 016 * 017 * <p>It parameterizes the sigma points using alpha and beta terms. Unless you know better, this 018 * should be your default choice due to its high accuracy and performance. 019 * 020 * <p>[1] K. Papakonstantinou "A Scaled Spherical Simplex Filter (S3F) with a decreased n + 2 sigma 021 * points set size and equivalent 2n + 1 Unscented Kalman Filter (UKF) accuracy" 022 * 023 * @param <States> The dimenstionality of the state. States + 2 weights will be generated. 024 */ 025public class S3SigmaPoints<States extends Num> implements SigmaPoints<States> { 026 private final Nat<States> m_states; 027 private final double m_alpha; 028 private Matrix<?, N1> m_wm; 029 private Matrix<?, N1> m_wc; 030 031 /** 032 * Constructs a generator for Papakonstantinou sigma points. 033 * 034 * @param states an instance of Num that represents the number of states. 035 * @param alpha Determines the spread of the sigma points around the mean. Usually a small 036 * positive value (1e-3). 037 * @param beta Incorporates prior knowledge of the distribution of the mean. For Gaussian 038 * distributions, beta = 2 is optimal. 039 */ 040 @SuppressWarnings("this-escape") 041 public S3SigmaPoints(Nat<States> states, double alpha, double beta) { 042 m_states = states; 043 m_alpha = alpha; 044 045 computeWeights(beta); 046 } 047 048 /** 049 * Constructs a generator for Papakonstantinou sigma points with default values for alpha, beta, 050 * and kappa. 051 * 052 * @param states an instance of Num that represents the number of states. 053 */ 054 public S3SigmaPoints(Nat<States> states) { 055 this(states, 1e-3, 2); 056 } 057 058 /** 059 * Returns number of sigma points for each variable in the state x. 060 * 061 * @return The number of sigma points for each variable in the state x. 062 */ 063 @Override 064 public int getNumSigmas() { 065 return m_states.getNum() + 2; 066 } 067 068 /** 069 * Computes the sigma points for an unscented Kalman filter given the mean (x) and square-root 070 * covariance (s) of the filter. 071 * 072 * @param x An array of the means. 073 * @param s Square-root covariance of the filter. 074 * @return Two-dimensional array of sigma points. Each column contains all the sigmas for one 075 * dimension in the problem space. Ordered by Xi_0, Xi_{1..n}, Xi_{n+1..2n}. 076 */ 077 @Override 078 public Matrix<States, ?> squareRootSigmaPoints(Matrix<States, N1> x, Matrix<States, States> s) { 079 // table (1), equation (12) 080 double[] q = new double[m_states.getNum()]; 081 for (int t = 1; t <= m_states.getNum(); ++t) { 082 q[t - 1] = m_alpha * Math.sqrt(t * (m_states.getNum() + 1) / (double) (t + 1)); 083 } 084 085 Matrix<States, ?> C = new Matrix<>(new SimpleMatrix(m_states.getNum(), getNumSigmas())); 086 for (int row = 0; row < m_states.getNum(); ++row) { 087 C.set(row, 0, 0.0); 088 } 089 for (int col = 1; col < getNumSigmas(); ++col) { 090 for (int row = 0; row < m_states.getNum(); ++row) { 091 if (row < col - 2) { 092 C.set(row, col, 0.0); 093 } else if (row == col - 2) { 094 C.set(row, col, q[row]); 095 } else { 096 C.set(row, col, -q[row] / (row + 1)); 097 } 098 } 099 } 100 101 Matrix<States, ?> sigmas = new Matrix<>(new SimpleMatrix(m_states.getNum(), getNumSigmas())); 102 for (int col = 0; col < getNumSigmas(); ++col) { 103 sigmas.setColumn(col, x.plus(s.times(C.extractColumnVector(col)))); 104 } 105 106 return sigmas; 107 } 108 109 /** 110 * Computes the weights for the scaled unscented Kalman filter. 111 * 112 * @param beta Incorporates prior knowledge of the distribution of the mean. 113 */ 114 private void computeWeights(double beta) { 115 double alpha_sq = m_alpha * m_alpha; 116 117 double c = 1.0 / (alpha_sq * (m_states.getNum() + 1)); 118 119 Matrix<?, N1> wM = new Matrix<>(new SimpleMatrix(getNumSigmas(), 1)); 120 Matrix<?, N1> wC = new Matrix<>(new SimpleMatrix(getNumSigmas(), 1)); 121 wM.fill(c); 122 wC.fill(c); 123 124 wM.set(0, 0, 1.0 - 1.0 / alpha_sq); 125 wC.set(0, 0, 1.0 - 1.0 / alpha_sq + (1 - alpha_sq + beta)); 126 127 this.m_wm = wM; 128 this.m_wc = wC; 129 } 130 131 /** 132 * Returns the weight for each sigma point for the mean. 133 * 134 * @return the weight for each sigma point for the mean. 135 */ 136 @Override 137 public Matrix<?, N1> getWm() { 138 return m_wm; 139 } 140 141 /** 142 * Returns an element of the weight for each sigma point for the mean. 143 * 144 * @param element Element of vector to return. 145 * @return the element i's weight for the mean. 146 */ 147 @Override 148 public double getWm(int element) { 149 return m_wm.get(element, 0); 150 } 151 152 /** 153 * Returns the weight for each sigma point for the covariance. 154 * 155 * @return the weight for each sigma point for the covariance. 156 */ 157 @Override 158 public Matrix<?, N1> getWc() { 159 return m_wc; 160 } 161 162 /** 163 * Returns an element of the weight for each sigma point for the covariance. 164 * 165 * @param element Element of vector to return. 166 * @return The element I's weight for the covariance. 167 */ 168 @Override 169 public double getWc(int element) { 170 return m_wc.get(element, 0); 171 } 172}