WPILibC++ 2024.1.1-beta-4
KalmanFilterLatencyCompensator.h
Go to the documentation of this file.
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#pragma once
6
7#include <algorithm>
8#include <array>
9#include <functional>
10#include <utility>
11#include <vector>
12
13#include "frc/EigenCore.h"
14#include "units/math.h"
15#include "units/time.h"
16
17namespace frc {
18
19template <int States, int Inputs, int Outputs, typename KalmanFilterType>
21 public:
27
28 ObserverSnapshot(const KalmanFilterType& observer, const Vectord<Inputs>& u,
29 const Vectord<Outputs>& localY)
30 : xHat(observer.Xhat()),
31 squareRootErrorCovariances(observer.S()),
32 inputs(u),
33 localMeasurements(localY) {}
34 };
35
36 /**
37 * Clears the observer snapshot buffer.
38 */
39 void Reset() { m_pastObserverSnapshots.clear(); }
40
41 /**
42 * Add past observer states to the observer snapshots list.
43 *
44 * @param observer The observer.
45 * @param u The input at the timestamp.
46 * @param localY The local output at the timestamp
47 * @param timestamp The timesnap of the state.
48 */
49 void AddObserverState(const KalmanFilterType& observer, Vectord<Inputs> u,
50 Vectord<Outputs> localY, units::second_t timestamp) {
51 // Add the new state into the vector.
52 m_pastObserverSnapshots.emplace_back(timestamp,
53 ObserverSnapshot{observer, u, localY});
54
55 // Remove the oldest snapshot if the vector exceeds our maximum size.
56 if (m_pastObserverSnapshots.size() > kMaxPastObserverStates) {
57 m_pastObserverSnapshots.erase(m_pastObserverSnapshots.begin());
58 }
59 }
60
61 /**
62 * Add past global measurements (such as from vision)to the estimator.
63 *
64 * @param observer The observer to apply the past global
65 * measurement.
66 * @param nominalDt The nominal timestep.
67 * @param y The measurement.
68 * @param globalMeasurementCorrect The function take calls correct() on the
69 * observer.
70 * @param timestamp The timestamp of the measurement.
71 */
72 template <int Rows>
74 KalmanFilterType* observer, units::second_t nominalDt, Vectord<Rows> y,
75 std::function<void(const Vectord<Inputs>& u, const Vectord<Rows>& y)>
76 globalMeasurementCorrect,
77 units::second_t timestamp) {
78 if (m_pastObserverSnapshots.size() == 0) {
79 // State map was empty, which means that we got a measurement right at
80 // startup. The only thing we can do is ignore the measurement.
81 return;
82 }
83
84 // Perform a binary search to find the index of first snapshot whose
85 // timestamp is greater than or equal to the global measurement timestamp
86 auto it = std::lower_bound(
87 m_pastObserverSnapshots.cbegin(), m_pastObserverSnapshots.cend(),
88 timestamp,
89 [](const auto& entry, const auto& ts) { return entry.first < ts; });
90
91 size_t indexOfClosestEntry;
92
93 if (it == m_pastObserverSnapshots.cbegin()) {
94 // If the global measurement is older than any snapshot, throw out the
95 // measurement because there's no state estimate into which to incorporate
96 // the measurement
97 if (timestamp < it->first) {
98 return;
99 }
100
101 // If the first snapshot has same timestamp as the global measurement, use
102 // that snapshot
103 indexOfClosestEntry = 0;
104 } else if (it == m_pastObserverSnapshots.cend()) {
105 // If all snapshots are older than the global measurement, use the newest
106 // snapshot
107 indexOfClosestEntry = m_pastObserverSnapshots.size() - 1;
108 } else {
109 // Index of snapshot taken after the global measurement
110 int nextIdx = std::distance(m_pastObserverSnapshots.cbegin(), it);
111
112 // Index of snapshot taken before the global measurement. Since we already
113 // handled the case where the index points to the first snapshot, this
114 // computation is guaranteed to be nonnegative.
115 int prevIdx = nextIdx - 1;
116
117 // Find the snapshot closest in time to global measurement
118 units::second_t prevTimeDiff =
119 units::math::abs(timestamp - m_pastObserverSnapshots[prevIdx].first);
120 units::second_t nextTimeDiff =
121 units::math::abs(timestamp - m_pastObserverSnapshots[nextIdx].first);
122 indexOfClosestEntry = prevTimeDiff < nextTimeDiff ? prevIdx : nextIdx;
123 }
124
125 units::second_t lastTimestamp =
126 m_pastObserverSnapshots[indexOfClosestEntry].first - nominalDt;
127
128 // We will now go back in time to the state of the system at the time when
129 // the measurement was captured. We will reset the observer to that state,
130 // and apply correction based on the measurement. Then, we will go back
131 // through all observer states until the present and apply past inputs to
132 // get the present estimated state.
133 for (size_t i = indexOfClosestEntry; i < m_pastObserverSnapshots.size();
134 ++i) {
135 auto& [key, snapshot] = m_pastObserverSnapshots[i];
136
137 if (i == indexOfClosestEntry) {
138 observer->SetS(snapshot.squareRootErrorCovariances);
139 observer->SetXhat(snapshot.xHat);
140 }
141
142 observer->Predict(snapshot.inputs, key - lastTimestamp);
143 observer->Correct(snapshot.inputs, snapshot.localMeasurements);
144
145 if (i == indexOfClosestEntry) {
146 // Note that the measurement is at a timestep close but probably not
147 // exactly equal to the timestep for which we called predict. This makes
148 // the assumption that the dt is small enough that the difference
149 // between the measurement time and the time that the inputs were
150 // captured at is very small.
151 globalMeasurementCorrect(snapshot.inputs, y);
152 }
153
154 lastTimestamp = key;
155 snapshot = ObserverSnapshot{*observer, snapshot.inputs,
156 snapshot.localMeasurements};
157 }
158 }
159
160 private:
161 static constexpr size_t kMaxPastObserverStates = 300;
162 std::vector<std::pair<units::second_t, ObserverSnapshot>>
163 m_pastObserverSnapshots;
164};
165} // namespace frc
Definition: KalmanFilterLatencyCompensator.h:20
void ApplyPastGlobalMeasurement(KalmanFilterType *observer, units::second_t nominalDt, Vectord< Rows > y, std::function< void(const Vectord< Inputs > &u, const Vectord< Rows > &y)> globalMeasurementCorrect, units::second_t timestamp)
Add past global measurements (such as from vision)to the estimator.
Definition: KalmanFilterLatencyCompensator.h:73
void Reset()
Clears the observer snapshot buffer.
Definition: KalmanFilterLatencyCompensator.h:39
void AddObserverState(const KalmanFilterType &observer, Vectord< Inputs > u, Vectord< Outputs > localY, units::second_t timestamp)
Add past observer states to the observer snapshots list.
Definition: KalmanFilterLatencyCompensator.h:49
UnitType abs(const UnitType x) noexcept
Compute absolute value.
Definition: math.h:721
const T & first(const T &value, const Tail &...)
Definition: compile.h:60
Definition: AprilTagPoseEstimator.h:15
Eigen::Matrix< double, Rows, Cols, Options, MaxRows, MaxCols > Matrixd
Definition: EigenCore.h:21
Eigen::Vector< double, Size > Vectord
Definition: EigenCore.h:12
Definition: KalmanFilterLatencyCompensator.h:22
Vectord< Inputs > inputs
Definition: KalmanFilterLatencyCompensator.h:25
Vectord< States > xHat
Definition: KalmanFilterLatencyCompensator.h:23
Vectord< Outputs > localMeasurements
Definition: KalmanFilterLatencyCompensator.h:26
Matrixd< States, States > squareRootErrorCovariances
Definition: KalmanFilterLatencyCompensator.h:24
ObserverSnapshot(const KalmanFilterType &observer, const Vectord< Inputs > &u, const Vectord< Outputs > &localY)
Definition: KalmanFilterLatencyCompensator.h:28
#define S(label, offset, message)
Definition: Errors.h:119