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}