From 1530fccbd0193f33821e2a5289609bf3e9ebf194 Mon Sep 17 00:00:00 2001
From: Joseph Eng <91924258+KangarooKoala@users.noreply.github.com>
Date: Tue, 15 Jul 2025 21:17:25 -0700
Subject: [PATCH] [wpimath] Implement Scaled Spherical Simplex Filter (S3F)
(#8091)
Adds S3SigmaPoints based on MerweScaledSigmaPoints. In addition, restructures UnscentedKalmanFilter to support different sigma point generators and provides MerweUKF and S3UKF for convenience when working with either kind of filter.
S3UKFTest is copied from MerweUKFTest (which is a rename of UnscentedKalmanFilterTest). Curiously, however, in Java the original tolerance used in MerweUKFTest.testDriveConvergence() for the final rotation was too low for S3UKFTest, so the tolerance is increased from 0.000005 (5e-6) radians to 0.00015 (1.5e-4) radians. However, the C++ version still uses the original tolerance. (This difference is probably because Java uses a final rotation of 5.846 degrees while C++ uses a final rotation of 5.846 radians)
Closes #8072.
Breaking changes:
- (C++) UnscentedKalmanFilter has a new template parameter for the sigma point generator type.
- (Java) UnscentedKalmanFilter has an additional parameter to every constructor providing an instance of a sigma point generator.
- (C++) int MerweScaledSigmaPoints.NumSigmas() has been replaced with constexpr int MerweScaledSigmaPoints::NumSigmas.
- (C++) The second parameter of SquareRootUnscentedTransform has been changed from States to NumSigmas.
---
.../estimator/MerweScaledSigmaPoints.java | 11 +-
.../wpi/first/math/estimator/MerweUKF.java | 117 ++++++
.../first/math/estimator/S3SigmaPoints.java | 172 ++++++++
.../edu/wpi/first/math/estimator/S3UKF.java | 117 ++++++
.../wpi/first/math/estimator/SigmaPoints.java | 64 +++
.../math/estimator/UnscentedKalmanFilter.java | 74 ++--
.../main/native/cpp/estimator/MerweUKF.cpp | 14 +
.../{UnscentedKalmanFilter.cpp => S3UKF.cpp} | 6 +-
.../frc/estimator/MerweScaledSigmaPoints.h | 11 +-
.../native/include/frc/estimator/MerweUKF.h | 25 ++
.../include/frc/estimator/S3SigmaPoints.h | 135 ++++++
.../main/native/include/frc/estimator/S3UKF.h | 25 ++
.../include/frc/estimator/SigmaPoints.h | 26 ++
.../frc/estimator/UnscentedKalmanFilter.h | 71 ++--
.../frc/estimator/UnscentedTransform.h | 25 +-
.../estimator/ExtendedKalmanFilterTest.java | 2 +-
...almanFilterTest.java => MerweUKFTest.java} | 39 +-
.../math/estimator/S3SigmaPointsTest.java | 86 ++++
.../wpi/first/math/estimator/S3UKFTest.java | 393 ++++++++++++++++++
...dKalmanFilterTest.cpp => MerweUKFTest.cpp} | 58 +--
.../cpp/estimator/S3SigmaPointsTest.cpp | 50 +++
.../test/native/cpp/estimator/S3UKFTest.cpp | 309 ++++++++++++++
22 files changed, 1694 insertions(+), 136 deletions(-)
create mode 100644 wpimath/src/main/java/edu/wpi/first/math/estimator/MerweUKF.java
create mode 100644 wpimath/src/main/java/edu/wpi/first/math/estimator/S3SigmaPoints.java
create mode 100644 wpimath/src/main/java/edu/wpi/first/math/estimator/S3UKF.java
create mode 100644 wpimath/src/main/java/edu/wpi/first/math/estimator/SigmaPoints.java
create mode 100644 wpimath/src/main/native/cpp/estimator/MerweUKF.cpp
rename wpimath/src/main/native/cpp/estimator/{UnscentedKalmanFilter.cpp => S3UKF.cpp} (71%)
create mode 100644 wpimath/src/main/native/include/frc/estimator/MerweUKF.h
create mode 100644 wpimath/src/main/native/include/frc/estimator/S3SigmaPoints.h
create mode 100644 wpimath/src/main/native/include/frc/estimator/S3UKF.h
create mode 100644 wpimath/src/main/native/include/frc/estimator/SigmaPoints.h
rename wpimath/src/test/java/edu/wpi/first/math/estimator/{UnscentedKalmanFilterTest.java => MerweUKFTest.java} (91%)
create mode 100644 wpimath/src/test/java/edu/wpi/first/math/estimator/S3SigmaPointsTest.java
create mode 100644 wpimath/src/test/java/edu/wpi/first/math/estimator/S3UKFTest.java
rename wpimath/src/test/native/cpp/estimator/{UnscentedKalmanFilterTest.cpp => MerweUKFTest.cpp} (83%)
create mode 100644 wpimath/src/test/native/cpp/estimator/S3SigmaPointsTest.cpp
create mode 100644 wpimath/src/test/native/cpp/estimator/S3UKFTest.cpp
diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/MerweScaledSigmaPoints.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/MerweScaledSigmaPoints.java
index d3131a83e9..d159af5d19 100644
--- a/wpimath/src/main/java/edu/wpi/first/math/estimator/MerweScaledSigmaPoints.java
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/MerweScaledSigmaPoints.java
@@ -15,7 +15,8 @@ import org.ejml.simple.SimpleMatrix;
* UnscentedKalmanFilter class.
*
*
It parametrizes the sigma points using alpha, beta, kappa terms, and is the version seen in
- * most publications. Unless you know better, this should be your default choice.
+ * most publications. S3SigmaPoints is generally preferred due to its greater performance with
+ * nearly identical accuracy.
*
*
States is the dimensionality of the state. 2*States+1 weights will be generated.
*
@@ -24,7 +25,7 @@ import org.ejml.simple.SimpleMatrix;
*
* @param The dimensionality of the state. 2 * States + 1 weights will be generated.
*/
-public class MerweScaledSigmaPoints {
+public class MerweScaledSigmaPoints implements SigmaPoints {
private final double m_alpha;
private final int m_kappa;
private final Nat m_states;
@@ -64,6 +65,7 @@ public class MerweScaledSigmaPoints {
*
* @return The number of sigma points for each variable in the state x.
*/
+ @Override
public int getNumSigmas() {
return 2 * m_states.getNum() + 1;
}
@@ -77,6 +79,7 @@ public class MerweScaledSigmaPoints {
* @return Two-dimensional array of sigma points. Each column contains all the sigmas for one
* dimension in the problem space. Ordered by Xi_0, Xi_{1..n}, Xi_{n+1..2n}.
*/
+ @Override
public Matrix squareRootSigmaPoints(Matrix x, Matrix s) {
double lambda = Math.pow(m_alpha, 2) * (m_states.getNum() + m_kappa) - m_states.getNum();
double eta = Math.sqrt(lambda + m_states.getNum());
@@ -125,6 +128,7 @@ public class MerweScaledSigmaPoints {
*
* @return the weight for each sigma point for the mean.
*/
+ @Override
public Matrix, N1> getWm() {
return m_wm;
}
@@ -135,6 +139,7 @@ public class MerweScaledSigmaPoints {
* @param element Element of vector to return.
* @return the element i's weight for the mean.
*/
+ @Override
public double getWm(int element) {
return m_wm.get(element, 0);
}
@@ -144,6 +149,7 @@ public class MerweScaledSigmaPoints {
*
* @return the weight for each sigma point for the covariance.
*/
+ @Override
public Matrix, N1> getWc() {
return m_wc;
}
@@ -154,6 +160,7 @@ public class MerweScaledSigmaPoints {
* @param element Element of vector to return.
* @return The element I's weight for the covariance.
*/
+ @Override
public double getWc(int element) {
return m_wc.get(element, 0);
}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/MerweUKF.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/MerweUKF.java
new file mode 100644
index 0000000000..d34ebe1ff9
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/MerweUKF.java
@@ -0,0 +1,117 @@
+// 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.
+
+package edu.wpi.first.math.estimator;
+
+import edu.wpi.first.math.Matrix;
+import edu.wpi.first.math.Nat;
+import edu.wpi.first.math.Num;
+import edu.wpi.first.math.numbers.N1;
+import java.util.function.BiFunction;
+
+/**
+ * An Unscented Kalman Filter using sigma points and weights from Van der Merwe's 2004 dissertation.
+ * S3UKF is generally preferred due to its greater performance with nearly identical accuracy.
+ *
+ * @param Number of states.
+ * @param Number of inputs.
+ * @param Number of outputs.
+ */
+public class MerweUKF
+ extends UnscentedKalmanFilter {
+ /**
+ * Constructs a Merwe Unscented Kalman Filter.
+ *
+ * See https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-observers.html#process-and-measurement-noise-covariance-matrices
+ * for how to select the standard deviations.
+ *
+ * @param states A Nat representing the number of states.
+ * @param outputs A Nat representing the number of outputs.
+ * @param f A vector-valued function of x and u that returns the derivative of the state vector.
+ * @param h A vector-valued function of x and u that returns the measurement vector.
+ * @param stateStdDevs Standard deviations of model states.
+ * @param measurementStdDevs Standard deviations of measurements.
+ * @param nominalDt Nominal discretization timestep in seconds.
+ */
+ public MerweUKF(
+ Nat states,
+ Nat outputs,
+ BiFunction, Matrix, Matrix> f,
+ BiFunction, Matrix, Matrix> h,
+ Matrix stateStdDevs,
+ Matrix measurementStdDevs,
+ double nominalDt) {
+ super(
+ new MerweScaledSigmaPoints<>(states),
+ states,
+ outputs,
+ f,
+ h,
+ stateStdDevs,
+ measurementStdDevs,
+ (sigmas, Wm) -> sigmas.times(Matrix.changeBoundsUnchecked(Wm)),
+ (sigmas, Wm) -> sigmas.times(Matrix.changeBoundsUnchecked(Wm)),
+ Matrix::minus,
+ Matrix::minus,
+ Matrix::plus,
+ nominalDt);
+ }
+
+ /**
+ * Constructs a Merwe Unscented Kalman filter with custom mean, residual, and addition functions.
+ * Using custom functions for arithmetic can be useful if you have angles in the state or
+ * measurements, because they allow you to correctly account for the modular nature of angle
+ * arithmetic.
+ *
+ * See https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-observers.html#process-and-measurement-noise-covariance-matrices
+ * for how to select the standard deviations.
+ *
+ * @param states A Nat representing the number of states.
+ * @param outputs A Nat representing the number of outputs.
+ * @param f A vector-valued function of x and u that returns the derivative of the state vector.
+ * @param h A vector-valued function of x and u that returns the measurement vector.
+ * @param stateStdDevs Standard deviations of model states.
+ * @param measurementStdDevs Standard deviations of measurements.
+ * @param meanFuncX A function that computes the mean of NumSigmas state vectors using a given set
+ * of weights.
+ * @param meanFuncY A function that computes the mean of NumSigmas measurement vectors using a
+ * given set of weights.
+ * @param residualFuncX A function that computes the residual of two state vectors (i.e. it
+ * subtracts them.)
+ * @param residualFuncY A function that computes the residual of two measurement vectors (i.e. it
+ * subtracts them.)
+ * @param addFuncX A function that adds two state vectors.
+ * @param nominalDt Nominal discretization timestep in seconds.
+ */
+ public MerweUKF(
+ Nat states,
+ Nat outputs,
+ BiFunction, Matrix, Matrix> f,
+ BiFunction, Matrix, Matrix> h,
+ Matrix stateStdDevs,
+ Matrix measurementStdDevs,
+ BiFunction, Matrix, N1>, Matrix> meanFuncX,
+ BiFunction, Matrix, N1>, Matrix> meanFuncY,
+ BiFunction, Matrix, Matrix> residualFuncX,
+ BiFunction, Matrix, Matrix> residualFuncY,
+ BiFunction, Matrix, Matrix> addFuncX,
+ double nominalDt) {
+ super(
+ new MerweScaledSigmaPoints<>(states),
+ states,
+ outputs,
+ f,
+ h,
+ stateStdDevs,
+ measurementStdDevs,
+ meanFuncX,
+ meanFuncY,
+ residualFuncX,
+ residualFuncY,
+ addFuncX,
+ nominalDt);
+ }
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/S3SigmaPoints.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/S3SigmaPoints.java
new file mode 100644
index 0000000000..430296e109
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/S3SigmaPoints.java
@@ -0,0 +1,172 @@
+// 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.
+
+package edu.wpi.first.math.estimator;
+
+import edu.wpi.first.math.Matrix;
+import edu.wpi.first.math.Nat;
+import edu.wpi.first.math.Num;
+import edu.wpi.first.math.numbers.N1;
+import org.ejml.simple.SimpleMatrix;
+
+/**
+ * Generates sigma points and weights according to Papakonstantinou's paper[1] for the
+ * UnscentedKalmanFilter class.
+ *
+ * It parameterizes the sigma points using alpha and beta terms. Unless you know better, this
+ * should be your default choice due to its high accuracy and performance.
+ *
+ *
[1] K. Papakonstantinou "A Scaled Spherical Simplex Filter (S3F) with a decreased n + 2 sigma
+ * points set size and equivalent 2n + 1 Unscented Kalman Filter (UKF) accuracy"
+ *
+ * @param The dimenstionality of the state. States + 2 weights will be generated.
+ */
+public class S3SigmaPoints implements SigmaPoints {
+ private final Nat m_states;
+ private final double m_alpha;
+ private Matrix, N1> m_wm;
+ private Matrix, N1> m_wc;
+
+ /**
+ * Constructs a generator for Papakonstantinou sigma points.
+ *
+ * @param states an instance of Num that represents the number of states.
+ * @param alpha Determines the spread of the sigma points around the mean. Usually a small
+ * positive value (1e-3).
+ * @param beta Incorporates prior knowledge of the distribution of the mean. For Gaussian
+ * distributions, beta = 2 is optimal.
+ */
+ @SuppressWarnings("this-escape")
+ public S3SigmaPoints(Nat states, double alpha, double beta) {
+ m_states = states;
+ m_alpha = alpha;
+
+ computeWeights(beta);
+ }
+
+ /**
+ * Constructs a generator for Papakonstantinou sigma points with default values for alpha, beta,
+ * and kappa.
+ *
+ * @param states an instance of Num that represents the number of states.
+ */
+ public S3SigmaPoints(Nat states) {
+ this(states, 1e-3, 2);
+ }
+
+ /**
+ * Returns number of sigma points for each variable in the state x.
+ *
+ * @return The number of sigma points for each variable in the state x.
+ */
+ @Override
+ public int getNumSigmas() {
+ return m_states.getNum() + 2;
+ }
+
+ /**
+ * Computes the sigma points for an unscented Kalman filter given the mean (x) and square-root
+ * covariance (s) of the filter.
+ *
+ * @param x An array of the means.
+ * @param s Square-root covariance of the filter.
+ * @return Two-dimensional array of sigma points. Each column contains all the sigmas for one
+ * dimension in the problem space. Ordered by Xi_0, Xi_{1..n}, Xi_{n+1..2n}.
+ */
+ @Override
+ public Matrix squareRootSigmaPoints(Matrix x, Matrix s) {
+ // table (1), equation (12)
+ double[] q = new double[m_states.getNum()];
+ for (int t = 1; t <= m_states.getNum(); ++t) {
+ q[t - 1] = m_alpha * Math.sqrt(t * (m_states.getNum() + 1) / (double) (t + 1));
+ }
+
+ Matrix C = new Matrix<>(new SimpleMatrix(m_states.getNum(), getNumSigmas()));
+ for (int row = 0; row < m_states.getNum(); ++row) {
+ C.set(row, 0, 0.0);
+ }
+ for (int col = 1; col < getNumSigmas(); ++col) {
+ for (int row = 0; row < m_states.getNum(); ++row) {
+ if (row < col - 2) {
+ C.set(row, col, 0.0);
+ } else if (row == col - 2) {
+ C.set(row, col, q[row]);
+ } else {
+ C.set(row, col, -q[row] / (row + 1));
+ }
+ }
+ }
+
+ Matrix sigmas = new Matrix<>(new SimpleMatrix(m_states.getNum(), getNumSigmas()));
+ for (int col = 0; col < getNumSigmas(); ++col) {
+ sigmas.setColumn(col, x.plus(s.times(C.extractColumnVector(col))));
+ }
+
+ return sigmas;
+ }
+
+ /**
+ * Computes the weights for the scaled unscented Kalman filter.
+ *
+ * @param beta Incorporates prior knowledge of the distribution of the mean.
+ */
+ private void computeWeights(double beta) {
+ double alpha_sq = m_alpha * m_alpha;
+
+ double c = 1.0 / (alpha_sq * (m_states.getNum() + 1));
+
+ Matrix, N1> wM = new Matrix<>(new SimpleMatrix(getNumSigmas(), 1));
+ Matrix, N1> wC = new Matrix<>(new SimpleMatrix(getNumSigmas(), 1));
+ wM.fill(c);
+ wC.fill(c);
+
+ wM.set(0, 0, 1.0 - 1.0 / alpha_sq);
+ wC.set(0, 0, 1.0 - 1.0 / alpha_sq + (1 - alpha_sq + beta));
+
+ this.m_wm = wM;
+ this.m_wc = wC;
+ }
+
+ /**
+ * Returns the weight for each sigma point for the mean.
+ *
+ * @return the weight for each sigma point for the mean.
+ */
+ @Override
+ public Matrix, N1> getWm() {
+ return m_wm;
+ }
+
+ /**
+ * Returns an element of the weight for each sigma point for the mean.
+ *
+ * @param element Element of vector to return.
+ * @return the element i's weight for the mean.
+ */
+ @Override
+ public double getWm(int element) {
+ return m_wm.get(element, 0);
+ }
+
+ /**
+ * Returns the weight for each sigma point for the covariance.
+ *
+ * @return the weight for each sigma point for the covariance.
+ */
+ @Override
+ public Matrix, N1> getWc() {
+ return m_wc;
+ }
+
+ /**
+ * Returns an element of the weight for each sigma point for the covariance.
+ *
+ * @param element Element of vector to return.
+ * @return The element I's weight for the covariance.
+ */
+ @Override
+ public double getWc(int element) {
+ return m_wc.get(element, 0);
+ }
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/S3UKF.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/S3UKF.java
new file mode 100644
index 0000000000..4de46faee3
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/S3UKF.java
@@ -0,0 +1,117 @@
+// 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.
+
+package edu.wpi.first.math.estimator;
+
+import edu.wpi.first.math.Matrix;
+import edu.wpi.first.math.Nat;
+import edu.wpi.first.math.Num;
+import edu.wpi.first.math.numbers.N1;
+import java.util.function.BiFunction;
+
+/**
+ * An Unscented Kalman Filter using sigma points and weights from Papakonstantinou's paper. This is
+ * generally preferred over other UKF variants due to its high accuracy and performance.
+ *
+ * @param Number of states.
+ * @param Number of inputs.
+ * @param Number of outputs.
+ */
+public class S3UKF
+ extends UnscentedKalmanFilter {
+ /**
+ * Constructs a Scaled Spherical Simplex (S3) Unscented Kalman Filter.
+ *
+ * See https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-observers.html#process-and-measurement-noise-covariance-matrices
+ * for how to select the standard deviations.
+ *
+ * @param states A Nat representing the number of states.
+ * @param outputs A Nat representing the number of outputs.
+ * @param f A vector-valued function of x and u that returns the derivative of the state vector.
+ * @param h A vector-valued function of x and u that returns the measurement vector.
+ * @param stateStdDevs Standard deviations of model states.
+ * @param measurementStdDevs Standard deviations of measurements.
+ * @param nominalDt Nominal discretization timestep in seconds.
+ */
+ public S3UKF(
+ Nat states,
+ Nat outputs,
+ BiFunction, Matrix, Matrix> f,
+ BiFunction, Matrix, Matrix> h,
+ Matrix stateStdDevs,
+ Matrix measurementStdDevs,
+ double nominalDt) {
+ super(
+ new S3SigmaPoints<>(states),
+ states,
+ outputs,
+ f,
+ h,
+ stateStdDevs,
+ measurementStdDevs,
+ (sigmas, Wm) -> sigmas.times(Matrix.changeBoundsUnchecked(Wm)),
+ (sigmas, Wm) -> sigmas.times(Matrix.changeBoundsUnchecked(Wm)),
+ Matrix::minus,
+ Matrix::minus,
+ Matrix::plus,
+ nominalDt);
+ }
+
+ /**
+ * Constructs a Scaled Spherical Simplex (S3) Unscented Kalman filter with custom mean, residual,
+ * and addition functions. Using custom functions for arithmetic can be useful if you have angles
+ * in the state or measurements, because they allow you to correctly account for the modular
+ * nature of angle arithmetic.
+ *
+ * See https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-observers.html#process-and-measurement-noise-covariance-matrices
+ * for how to select the standard deviations.
+ *
+ * @param states A Nat representing the number of states.
+ * @param outputs A Nat representing the number of outputs.
+ * @param f A vector-valued function of x and u that returns the derivative of the state vector.
+ * @param h A vector-valued function of x and u that returns the measurement vector.
+ * @param stateStdDevs Standard deviations of model states.
+ * @param measurementStdDevs Standard deviations of measurements.
+ * @param meanFuncX A function that computes the mean of NumSigmas state vectors using a given set
+ * of weights.
+ * @param meanFuncY A function that computes the mean of NumSigmas measurement vectors using a
+ * given set of weights.
+ * @param residualFuncX A function that computes the residual of two state vectors (i.e. it
+ * subtracts them.)
+ * @param residualFuncY A function that computes the residual of two measurement vectors (i.e. it
+ * subtracts them.)
+ * @param addFuncX A function that adds two state vectors.
+ * @param nominalDt Nominal discretization timestep in seconds.
+ */
+ public S3UKF(
+ Nat states,
+ Nat outputs,
+ BiFunction, Matrix, Matrix> f,
+ BiFunction, Matrix, Matrix> h,
+ Matrix stateStdDevs,
+ Matrix measurementStdDevs,
+ BiFunction, Matrix, N1>, Matrix> meanFuncX,
+ BiFunction, Matrix, N1>, Matrix> meanFuncY,
+ BiFunction, Matrix, Matrix> residualFuncX,
+ BiFunction, Matrix, Matrix> residualFuncY,
+ BiFunction, Matrix, Matrix> addFuncX,
+ double nominalDt) {
+ super(
+ new S3SigmaPoints<>(states),
+ states,
+ outputs,
+ f,
+ h,
+ stateStdDevs,
+ measurementStdDevs,
+ meanFuncX,
+ meanFuncY,
+ residualFuncX,
+ residualFuncY,
+ addFuncX,
+ nominalDt);
+ }
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/SigmaPoints.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/SigmaPoints.java
new file mode 100644
index 0000000000..cb0ca2334b
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/SigmaPoints.java
@@ -0,0 +1,64 @@
+// 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.
+
+package edu.wpi.first.math.estimator;
+
+import edu.wpi.first.math.Matrix;
+import edu.wpi.first.math.Num;
+import edu.wpi.first.math.numbers.N1;
+
+/**
+ * A sigma points generator for the UnscentedKalmanFilter class.
+ *
+ * @param The dimensionality of the state.
+ */
+public interface SigmaPoints {
+ /**
+ * Returns number of sigma points for each variable in the state x.
+ *
+ * @return The number of sigma points for each variable in the state x.
+ */
+ int getNumSigmas();
+
+ /**
+ * Computes the sigma points for an unscented Kalman filter given the mean (x) and square-root
+ * covariance (s) of the filter.
+ *
+ * @param x An array of the means.
+ * @param s Square-root covariance of the filter.
+ * @return Two-dimensional array of sigma points. Each column contains all the sigmas for one
+ * dimension in the problem space. Ordered by Xi_0, Xi_{1..n}, Xi_{n+1..2n}.
+ */
+ Matrix squareRootSigmaPoints(Matrix x, Matrix s);
+
+ /**
+ * Returns the weight for each sigma point for the mean.
+ *
+ * @return the weight for each sigma point for the mean.
+ */
+ Matrix, N1> getWm();
+
+ /**
+ * Returns an element of the weight for each sigma point for the mean.
+ *
+ * @param element Element of vector to return.
+ * @return the element i's weight for the mean.
+ */
+ double getWm(int element);
+
+ /**
+ * Returns the weight for each sigma point for the covariance.
+ *
+ * @return the weight for each sigma point for the covariance.
+ */
+ Matrix, N1> getWc();
+
+ /**
+ * Returns an element of the weight for each sigma point for the covariance.
+ *
+ * @param element Element of vector to return.
+ * @return The element I's weight for the covariance.
+ */
+ double getWc(int element);
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/UnscentedKalmanFilter.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/UnscentedKalmanFilter.java
index 0d402c7af9..455cd67d46 100644
--- a/wpimath/src/main/java/edu/wpi/first/math/estimator/UnscentedKalmanFilter.java
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/UnscentedKalmanFilter.java
@@ -22,6 +22,11 @@ import org.ejml.simple.SimpleMatrix;
* true system state. This is useful because many states cannot be measured directly as a result of
* sensor noise, or because the state is "hidden".
*
+ * This class's constructors require a SigmaPoints generator. For convenience, {@link S3UKF} and
+ * {@link MerweUKF} subclasses are provided to create a suitable generator for you. S3UKF is
+ * generally preferred over MerweUKF because of its greater performance while maintaining nearly
+ * identical accuracy.
+ *
*
Kalman filters use a K gain matrix to determine whether to trust the model or measurements
* more. Kalman filter theory uses statistics to compute an optimal K gain which minimizes the sum
* of squares error in the state estimate. This K gain is used to correct the state estimate by some
@@ -64,7 +69,7 @@ public class UnscentedKalmanFilter m_sigmasF;
private double m_dt;
- private final MerweScaledSigmaPoints m_pts;
+ private final SigmaPoints m_pts;
/**
* Constructs an Unscented Kalman Filter.
@@ -73,6 +78,7 @@ public class UnscentedKalmanFilterhttps://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-observers.html#process-and-measurement-noise-covariance-matrices
* for how to select the standard deviations.
*
+ * @param pts A sigma points and weights generator.
* @param states A Nat representing the number of states.
* @param outputs A Nat representing the number of outputs.
* @param f A vector-valued function of x and u that returns the derivative of the state vector.
@@ -82,6 +88,7 @@ public class UnscentedKalmanFilter pts,
Nat states,
Nat outputs,
BiFunction, Matrix, Matrix> f,
@@ -90,6 +97,7 @@ public class UnscentedKalmanFilter measurementStdDevs,
double nominalDt) {
this(
+ pts,
states,
outputs,
f,
@@ -113,16 +121,17 @@ public class UnscentedKalmanFilterhttps://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-observers.html#process-and-measurement-noise-covariance-matrices
* for how to select the standard deviations.
*
+ * @param pts A sigma points and weights generator.
* @param states A Nat representing the number of states.
* @param outputs A Nat representing the number of outputs.
* @param f A vector-valued function of x and u that returns the derivative of the state vector.
* @param h A vector-valued function of x and u that returns the measurement vector.
* @param stateStdDevs Standard deviations of model states.
* @param measurementStdDevs Standard deviations of measurements.
- * @param meanFuncX A function that computes the mean of 2 * States + 1 state vectors using a
+ * @param meanFuncX A function that computes the mean of NumSigmas state vectors using a given set
+ * of weights.
+ * @param meanFuncY A function that computes the mean of NumSigmas measurement vectors using a
* given set of weights.
- * @param meanFuncY A function that computes the mean of 2 * States + 1 measurement vectors using
- * a given set of weights.
* @param residualFuncX A function that computes the residual of two state vectors (i.e. it
* subtracts them.)
* @param residualFuncY A function that computes the residual of two measurement vectors (i.e. it
@@ -131,6 +140,7 @@ public class UnscentedKalmanFilter pts,
Nat states,
Nat outputs,
BiFunction, Matrix, Matrix> f,
@@ -160,37 +170,36 @@ public class UnscentedKalmanFilter(states);
+ m_pts = pts;
reset();
}
- static
- Pair, Matrix> squareRootUnscentedTransform(
- Nat s,
- Nat dim,
- Matrix sigmas,
- Matrix, N1> Wm,
- Matrix, N1> Wc,
- BiFunction, Matrix, N1>, Matrix> meanFunc,
- BiFunction, Matrix, Matrix> residualFunc,
- Matrix squareRootR) {
- if (sigmas.getNumRows() != dim.getNum() || sigmas.getNumCols() != 2 * s.getNum() + 1) {
+ static Pair, Matrix> squareRootUnscentedTransform(
+ Nat covdim,
+ int numSigmas,
+ Matrix sigmas,
+ Matrix, N1> Wm,
+ Matrix, N1> Wc,
+ BiFunction, Matrix, N1>, Matrix> meanFunc,
+ BiFunction, Matrix, Matrix> residualFunc,
+ Matrix squareRootR) {
+ if (sigmas.getNumRows() != covdim.getNum() || sigmas.getNumCols() != numSigmas) {
throw new IllegalArgumentException(
- "Sigmas must be covDim by 2 * states + 1! Got "
+ "Sigmas must be covDim by numSigmas! Got "
+ sigmas.getNumRows()
+ " by "
+ sigmas.getNumCols());
}
- if (Wm.getNumRows() != 2 * s.getNum() + 1 || Wm.getNumCols() != 1) {
+ if (Wm.getNumRows() != numSigmas || Wm.getNumCols() != 1) {
throw new IllegalArgumentException(
- "Wm must be 2 * states + 1 by 1! Got " + Wm.getNumRows() + " by " + Wm.getNumCols());
+ "Wm must be numSigmas by 1! Got " + Wm.getNumRows() + " by " + Wm.getNumCols());
}
- if (Wc.getNumRows() != 2 * s.getNum() + 1 || Wc.getNumCols() != 1) {
+ if (Wc.getNumRows() != numSigmas || Wc.getNumCols() != 1) {
throw new IllegalArgumentException(
- "Wc must be 2 * states + 1 by 1! Got " + Wc.getNumRows() + " by " + Wc.getNumCols());
+ "Wc must be numSigmas by 1! Got " + Wc.getNumRows() + " by " + Wc.getNumCols());
}
// New mean is usually just the sum of the sigmas * weights:
@@ -208,13 +217,18 @@ public class UnscentedKalmanFilter Sbar = new Matrix<>(new SimpleMatrix(dim.getNum(), 2 * s.getNum() + dim.getNum()));
- for (int i = 0; i < 2 * s.getNum(); i++) {
+ //
+ // Note that we allow a custom function instead of the difference to allow
+ // angle wrapping. Furthermore, we allow an arbitrary number of sigma points to
+ // support similar methods such as the Scaled Spherical Simplex Filter (S3F).
+ Matrix Sbar =
+ new Matrix<>(new SimpleMatrix(covdim.getNum(), numSigmas - 1 + covdim.getNum()));
+ for (int i = 0; i < numSigmas - 1; i++) {
Sbar.setColumn(
i,
residualFunc.apply(sigmas.extractColumnVector(1 + i), x).times(Math.sqrt(Wc.get(1, 0))));
}
- Sbar.assignBlock(0, 2 * s.getNum(), squareRootR);
+ Sbar.assignBlock(0, numSigmas - 1, squareRootR);
QRDecompositionHouseholder_DDRM qr = new QRDecompositionHouseholder_DDRM();
var qrStorage = Sbar.transpose().getStorage();
@@ -355,7 +369,7 @@ public class UnscentedKalmanFilter(m_states, Nat.N1());
m_S = new Matrix<>(m_states, m_states);
- m_sigmasF = new Matrix<>(new SimpleMatrix(m_states.getNum(), 2 * m_states.getNum() + 1));
+ m_sigmasF = new Matrix<>(new SimpleMatrix(m_states.getNum(), m_pts.getNumSigmas()));
}
/**
@@ -397,7 +411,7 @@ public class UnscentedKalmanFilter sigmasH = new Matrix<>(new SimpleMatrix(rows.getNum(), 2 * m_states.getNum() + 1));
+ Matrix sigmasH = new Matrix<>(new SimpleMatrix(rows.getNum(), m_pts.getNumSigmas()));
var sigmas = m_pts.squareRootSigmaPoints(m_xHat, m_S);
for (int i = 0; i < m_pts.getNumSigmas(); i++) {
Matrix hRet = h.apply(sigmas.extractColumnVector(i), u);
@@ -521,8 +535,8 @@ public class UnscentedKalmanFilter>;
+template class EXPORT_TEMPLATE_DEFINE(WPILIB_DLLEXPORT)
+ UnscentedKalmanFilter<5, 3, 3, MerweScaledSigmaPoints<5>>;
+
+} // namespace frc
diff --git a/wpimath/src/main/native/cpp/estimator/UnscentedKalmanFilter.cpp b/wpimath/src/main/native/cpp/estimator/S3UKF.cpp
similarity index 71%
rename from wpimath/src/main/native/cpp/estimator/UnscentedKalmanFilter.cpp
rename to wpimath/src/main/native/cpp/estimator/S3UKF.cpp
index d5e869b6cb..d0302d7ca9 100644
--- a/wpimath/src/main/native/cpp/estimator/UnscentedKalmanFilter.cpp
+++ b/wpimath/src/main/native/cpp/estimator/S3UKF.cpp
@@ -2,13 +2,13 @@
// 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.
-#include "frc/estimator/UnscentedKalmanFilter.h"
+#include "frc/estimator/S3UKF.h"
namespace frc {
template class EXPORT_TEMPLATE_DEFINE(WPILIB_DLLEXPORT)
- UnscentedKalmanFilter<3, 3, 1>;
+ UnscentedKalmanFilter<3, 3, 1, S3SigmaPoints<3>>;
template class EXPORT_TEMPLATE_DEFINE(WPILIB_DLLEXPORT)
- UnscentedKalmanFilter<5, 3, 3>;
+ UnscentedKalmanFilter<5, 3, 3, S3SigmaPoints<5>>;
} // namespace frc
diff --git a/wpimath/src/main/native/include/frc/estimator/MerweScaledSigmaPoints.h b/wpimath/src/main/native/include/frc/estimator/MerweScaledSigmaPoints.h
index 2605d5e03e..1b5b5cfa1f 100644
--- a/wpimath/src/main/native/include/frc/estimator/MerweScaledSigmaPoints.h
+++ b/wpimath/src/main/native/include/frc/estimator/MerweScaledSigmaPoints.h
@@ -15,8 +15,8 @@ namespace frc {
* dissertation[1] for the UnscentedKalmanFilter class.
*
* It parametrizes the sigma points using alpha, beta, kappa terms, and is the
- * version seen in most publications. Unless you know better, this should be
- * your default choice.
+ * version seen in most publications. S3SigmaPoints is generally preferred due
+ * to its greater performance with nearly identical accuracy.
*
* [1] R. Van der Merwe "Sigma-Point Kalman Filters for Probabilistic
* Inference in Dynamic State-Space Models" (Doctoral dissertation)
@@ -27,6 +27,8 @@ namespace frc {
template
class MerweScaledSigmaPoints {
public:
+ static constexpr int NumSigmas = 2 * States + 1;
+
/**
* Constructs a generator for Van der Merwe scaled sigma points.
*
@@ -44,11 +46,6 @@ class MerweScaledSigmaPoints {
ComputeWeights(beta);
}
- /**
- * Returns number of sigma points for each variable in the state x.
- */
- int NumSigmas() { return 2 * States + 1; }
-
/**
* Computes the sigma points for an unscented Kalman filter given the mean
* (x) and square-root covariance (S) of the filter.
diff --git a/wpimath/src/main/native/include/frc/estimator/MerweUKF.h b/wpimath/src/main/native/include/frc/estimator/MerweUKF.h
new file mode 100644
index 0000000000..1b5165aeb4
--- /dev/null
+++ b/wpimath/src/main/native/include/frc/estimator/MerweUKF.h
@@ -0,0 +1,25 @@
+// 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 "frc/estimator/MerweScaledSigmaPoints.h"
+#include "frc/estimator/UnscentedKalmanFilter.h"
+
+namespace frc {
+
+template
+using MerweUKF = UnscentedKalmanFilter>;
+
+// Because MerweUKF is a type alias and not a class, we have to use
+// UnscentedKalmanFilter instead
+extern template class EXPORT_TEMPLATE_DECLARE(WPILIB_DLLEXPORT)
+ UnscentedKalmanFilter<3, 3, 1, MerweScaledSigmaPoints<3>>;
+extern template class EXPORT_TEMPLATE_DECLARE(WPILIB_DLLEXPORT)
+ UnscentedKalmanFilter<5, 3, 3, MerweScaledSigmaPoints<5>>;
+
+} // namespace frc
diff --git a/wpimath/src/main/native/include/frc/estimator/S3SigmaPoints.h b/wpimath/src/main/native/include/frc/estimator/S3SigmaPoints.h
new file mode 100644
index 0000000000..a19cd072ac
--- /dev/null
+++ b/wpimath/src/main/native/include/frc/estimator/S3SigmaPoints.h
@@ -0,0 +1,135 @@
+// 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 "frc/EigenCore.h"
+#include "frc/estimator/SigmaPoints.h"
+
+namespace frc {
+
+/**
+ * Generates sigma points and weights according to Papakonstantinou's paper[1]
+ * for the UnscentedKalmanFilter class.
+ *
+ * It parameterizes the sigma points using alpha and beta terms. Unless you know
+ * better, this should be your default choice due to its high accuracy and
+ * performance.
+ *
+ * [1] K. Papakonstantinou "A Scaled Spherical Simplex Filter (S3F) with a
+ * decreased n + 2 sigma points set size and equivalent 2n + 1 Unscented Kalman
+ * Filter (UKF) accuracy"
+ *
+ * @tparam States The dimenstionality of the state. States + 2 weights will be
+ * generated.
+ */
+template
+class S3SigmaPoints {
+ public:
+ static constexpr int NumSigmas = States + 2;
+
+ /**
+ * Constructs a generator for Papakonstantinou sigma points.
+ *
+ * @param alpha Determines the spread of the sigma points around the mean.
+ * Usually a small positive value (1e-3).
+ * @param beta Incorporates prior knowledge of the distribution of the mean.
+ * For Gaussian distributions, beta = 2 is optimal.
+ */
+ explicit S3SigmaPoints(double alpha = 1e-3, double beta = 2)
+ : m_alpha{alpha} {
+ ComputeWeights(beta);
+ }
+
+ /**
+ * Computes the sigma points for an unscented Kalman filter given the mean (x)
+ * and square-root covariance (S) of the filter.
+ *
+ * @param x An array of the means.
+ * @param S Square-root covariance of the filter.
+ *
+ * @return Two dimensional array of sigma points. Each column contains all of
+ * the sigmas for one dimension in the problem space. Ordered by Xi_0,
+ * Xi_{1..n+1}.
+ */
+ Matrixd SquareRootSigmaPoints(
+ const Vectord& x, const Matrixd& S) const {
+ // table (1), equation (12)
+ wpi::array q(wpi::empty_array);
+ for (size_t t = 1; t <= States; ++t) {
+ q[t - 1] = m_alpha * std::sqrt(static_cast(t * (States + 1)) /
+ static_cast(t + 1));
+ }
+
+ Matrixd C;
+ C.template block(0, 0) = Vectord::Constant(0.0);
+ for (int col = 1; col < NumSigmas; ++col) {
+ for (int row = 0; row < States; ++row) {
+ if (row < col - 2) {
+ C(row, col) = 0.0;
+ } else if (row == col - 2) {
+ C(row, col) = q[row];
+ } else {
+ C(row, col) = -q[row] / (row + 1);
+ }
+ }
+ }
+
+ Matrixd sigmas;
+ for (int col = 0; col < NumSigmas; ++col) {
+ sigmas.col(col) = x + S * C.col(col);
+ }
+
+ return sigmas;
+ }
+
+ /**
+ * Returns the weight for each sigma point for the mean.
+ */
+ const Vectord& Wm() const { return m_Wm; }
+
+ /**
+ * Returns an element of the weight for each sigma point for the mean.
+ *
+ * @param i Element of vector to return.
+ */
+ double Wm(int i) const { return m_Wm(i, 0); }
+
+ /**
+ * Returns the weight for each sigma point for the covariance.
+ */
+ const Vectord& Wc() const { return m_Wc; }
+
+ /**
+ * Returns an element of the weight for each sigma point for the covariance.
+ *
+ * @param i Element of vector to return.
+ */
+ double Wc(int i) const { return m_Wc(i, 0); }
+
+ private:
+ Vectord m_Wm;
+ Vectord m_Wc;
+ double m_alpha;
+
+ /**
+ * Computes the weights for the scaled unscented Kalman filter.
+ *
+ * @param beta Incorporates prior knowledge of the distribution of the mean.
+ */
+ void ComputeWeights(double beta) {
+ double alpha_sq = m_alpha * m_alpha;
+
+ double c = 1.0 / (alpha_sq * (States + 1));
+ m_Wm = Vectord::Constant(c);
+ m_Wc = Vectord::Constant(c);
+
+ m_Wm(0) = 1.0 - 1.0 / alpha_sq;
+ m_Wc(0) = 1.0 - 1.0 / alpha_sq + (1 - alpha_sq + beta);
+ }
+};
+
+} // namespace frc
diff --git a/wpimath/src/main/native/include/frc/estimator/S3UKF.h b/wpimath/src/main/native/include/frc/estimator/S3UKF.h
new file mode 100644
index 0000000000..e94f2af0c6
--- /dev/null
+++ b/wpimath/src/main/native/include/frc/estimator/S3UKF.h
@@ -0,0 +1,25 @@
+// 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 "frc/estimator/S3SigmaPoints.h"
+#include "frc/estimator/UnscentedKalmanFilter.h"
+
+namespace frc {
+
+template
+using S3UKF =
+ UnscentedKalmanFilter>;
+
+// Because S3UKF is a type alias and not a class, we have to use
+// UnscentedKalmanFilter instead
+extern template class EXPORT_TEMPLATE_DECLARE(WPILIB_DLLEXPORT)
+ UnscentedKalmanFilter<3, 3, 1, S3SigmaPoints<3>>;
+extern template class EXPORT_TEMPLATE_DECLARE(WPILIB_DLLEXPORT)
+ UnscentedKalmanFilter<5, 3, 3, S3SigmaPoints<5>>;
+
+} // namespace frc
diff --git a/wpimath/src/main/native/include/frc/estimator/SigmaPoints.h b/wpimath/src/main/native/include/frc/estimator/SigmaPoints.h
new file mode 100644
index 0000000000..a459197c75
--- /dev/null
+++ b/wpimath/src/main/native/include/frc/estimator/SigmaPoints.h
@@ -0,0 +1,26 @@
+// 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 "frc/EigenCore.h"
+
+namespace frc {
+
+template
+concept SigmaPoints =
+ requires(T t, Vectord x, Matrixd S, int i) {
+ { T::NumSigmas } -> std::convertible_to;
+ {
+ t.SquareRootSigmaPoints(x, S)
+ } -> std::same_as>;
+ { t.Wm() } -> std::convertible_to>;
+ { t.Wm(i) } -> std::same_as;
+ { t.Wc() } -> std::convertible_to>;
+ { t.Wc(i) } -> std::same_as;
+ } && std::default_initializable;
+
+} // namespace frc
diff --git a/wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.h b/wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.h
index 21838870a8..2d2ed82c62 100644
--- a/wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.h
+++ b/wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.h
@@ -13,7 +13,7 @@
#include "frc/EigenCore.h"
#include "frc/StateSpaceUtil.h"
-#include "frc/estimator/MerweScaledSigmaPoints.h"
+#include "frc/estimator/SigmaPoints.h"
#include "frc/estimator/UnscentedTransform.h"
#include "frc/system/Discretization.h"
#include "frc/system/NumericalIntegration.h"
@@ -28,6 +28,11 @@ namespace frc {
* be measured directly as a result of sensor noise, or because the state is
* "hidden".
*
+ * This class requires a SigmaPoints template parameter. For convenience, S3UKF
+ * and MerweUKF type aliases are provided to specify a suitable generator for
+ * you. S3UKF is generally preferred over MerweUKF because of its greater
+ * performance while maintaining nearly identical accuracy.
+ *
* Kalman filters use a K gain matrix to determine whether to trust the model or
* measurements more. Kalman filter theory uses statistics to compute an optimal
* K gain which minimizes the sum of squares error in the state estimate. This K
@@ -51,10 +56,13 @@ namespace frc {
* @tparam States Number of states.
* @tparam Inputs Number of inputs.
* @tparam Outputs Number of outputs.
+ * @tparam SigmaPoints Type used to generate sigma points and weights.
*/
-template
+template SigmaPoints>
class UnscentedKalmanFilter {
public:
+ static constexpr int NumSigmas = SigmaPoints::NumSigmas;
+
using StateVector = Vectord;
using InputVector = Vectord;
using OutputVector = Vectord;
@@ -87,12 +95,12 @@ class UnscentedKalmanFilter {
: m_f(std::move(f)), m_h(std::move(h)) {
m_contQ = MakeCovMatrix(stateStdDevs);
m_contR = MakeCovMatrix(measurementStdDevs);
- m_meanFuncX = [](const Matrixd& sigmas,
- const Vectord<2 * States + 1>& Wm) -> StateVector {
+ m_meanFuncX = [](const Matrixd& sigmas,
+ const Vectord& Wm) -> StateVector {
return sigmas * Wm;
};
- m_meanFuncY = [](const Matrixd& sigmas,
- const Vectord<2 * States + 1>& Wc) -> OutputVector {
+ m_meanFuncY = [](const Matrixd& sigmas,
+ const Vectord& Wc) -> OutputVector {
return sigmas * Wc;
};
m_residualFuncX = [](const StateVector& a,
@@ -141,11 +149,11 @@ class UnscentedKalmanFilter {
std::function f,
std::function h,
const StateArray& stateStdDevs, const OutputArray& measurementStdDevs,
- std::function&,
- const Vectord<2 * States + 1>&)>
+ std::function&,
+ const Vectord&)>
meanFuncX,
- std::function&,
- const Vectord<2 * States + 1>&)>
+ std::function&,
+ const Vectord&)>
meanFuncY,
std::function
residualFuncX,
@@ -257,7 +265,7 @@ class UnscentedKalmanFilter {
// Generate sigma points around the state mean
//
// equation (17)
- Matrixd sigmas =
+ Matrixd sigmas =
m_pts.SquareRootSigmaPoints(m_xHat, m_S);
// Project each sigma point forward in time according to the
@@ -267,7 +275,7 @@ class UnscentedKalmanFilter {
// sigmasF = 𝒳ₖ,ₖ₋₁ or just 𝒳 for readability
//
// equation (18)
- for (int i = 0; i < m_pts.NumSigmas(); ++i) {
+ for (int i = 0; i < NumSigmas; ++i) {
StateVector x = sigmas.template block(0, i);
m_sigmasF.template block(0, i) = RK4(m_f, x, u, dt);
}
@@ -276,7 +284,7 @@ class UnscentedKalmanFilter {
// to compute the prior state mean and covariance
//
// equations (18) (19) and (20)
- auto [xHat, S] = SquareRootUnscentedTransform(
+ auto [xHat, S] = SquareRootUnscentedTransform(
m_sigmasF, m_pts.Wm(), m_pts.Wc(), m_meanFuncX, m_residualFuncX,
discQ.template triangularView());
m_xHat = xHat;
@@ -327,8 +335,8 @@ class UnscentedKalmanFilter {
const InputVector& u, const Vectord& y,
std::function(const StateVector&, const InputVector&)> h,
const Matrixd& R) {
- auto meanFuncY = [](const Matrixd& sigmas,
- const Vectord<2 * States + 1>& Wc) -> Vectord {
+ auto meanFuncY = [](const Matrixd& sigmas,
+ const Vectord& Wc) -> Vectord {
return sigmas * Wc;
};
auto residualFuncX = [](const StateVector& a,
@@ -358,7 +366,7 @@ class UnscentedKalmanFilter {
* @param h A vector-valued function of x and u that returns the
* measurement vector.
* @param R Continuous measurement noise covariance matrix.
- * @param meanFuncY A function that computes the mean of 2 * States + 1
+ * @param meanFuncY A function that computes the mean of NumSigmas
* measurement vectors using a given set of weights.
* @param residualFuncY A function that computes the residual of two
* measurement vectors (i.e. it subtracts them.)
@@ -371,8 +379,8 @@ class UnscentedKalmanFilter {
const InputVector& u, const Vectord& y,
std::function(const StateVector&, const InputVector&)> h,
const Matrixd& R,
- std::function(const Matrixd&,
- const Vectord<2 * States + 1>&)>
+ std::function(const Matrixd&,
+ const Vectord&)>
meanFuncY,
std::function(const Vectord&, const Vectord&)>
residualFuncY,
@@ -392,10 +400,10 @@ class UnscentedKalmanFilter {
// This differs from equation (22) which uses
// the prior sigma points, regenerating them allows
// multiple measurement updates per time update
- Matrixd sigmasH;
- Matrixd sigmas =
+ Matrixd sigmasH;
+ Matrixd sigmas =
m_pts.SquareRootSigmaPoints(m_xHat, m_S);
- for (int i = 0; i < m_pts.NumSigmas(); ++i) {
+ for (int i = 0; i < NumSigmas; ++i) {
sigmasH.template block(0, i) =
h(sigmas.template block(0, i), u);
}
@@ -405,7 +413,7 @@ class UnscentedKalmanFilter {
// covariance.
//
// equations (23) (24) and (25)
- auto [yHat, Sy] = SquareRootUnscentedTransform(
+ auto [yHat, Sy] = SquareRootUnscentedTransform(
sigmasH, m_pts.Wm(), m_pts.Wc(), meanFuncY, residualFuncY,
discR.template triangularView());
@@ -419,7 +427,7 @@ class UnscentedKalmanFilter {
// equation (26)
Matrixd