Avionics
Core avionics package for CURE flight computers
Loading...
Searching...
No Matches
ApogeePredictor.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <cmath>
6#include <cstdint>
7#include <cstdio>
8
9namespace {
10
11// Clamp utility
12template <typename T>
13constexpr const T& clamp(const T& value, const T& lower, const T& upper) {
14 return (value < lower) ? lower : (value > upper) ? upper : value;
15}
16
17// Constants
18constexpr float kMinValidDecel = 0.000001F;
19constexpr float kMillisecondsPerSecond = 1000.0F;
20constexpr float kOneHalf = 0.5F;
21constexpr float kGravity_mps2 = 9.80665F;
22constexpr uint32_t kMaxWarmups = 1000;
23
24} // namespace
25
27 float accelFilterAlpha, // caution: two floats here are swappable //NOLINT(bugprone-easily-swappable-parameters)
28 float minimumClimbVelocity_mps) //NOLINT(bugprone-easily-swappable-parameters)
29 : vve_(velocityEstimator),
30 filteredDecel_mps2_(0.0F),
31 alpha_(clamp(accelFilterAlpha, 0.0F, 1.0F)),
32 minimumClimbVelocity_mps_(minimumClimbVelocity_mps),
33 valid_(false),
34 tToApogee_(0.0F),
35 predApogeeTs_(0),
36 predApogeeAlt_(0.0F),
37 lastTs_(0),
38 lastVel_(0.0F),
39 numWarmups_(0) {}
40
42 const uint32_t currentTimestamp = vve_.getTimestamp();
43 const float velocity = vve_.getEstimatedVelocity();
44 const float acceleration = vve_.getInertialVerticalAcceleration();
45
46 // Raw deceleration estimate
47 float decelSample = std::max(0.0F, -acceleration);
48
49 if (currentTimestamp > lastTs_) {
50 const auto deltaTime_s = static_cast<float>(currentTimestamp - lastTs_) * kMillisecondsToSeconds;
51 const float deltaVelocity_mps = velocity - lastVel_;
52 float estimatedDecel_mps2 = (deltaTime_s > 0.0F) ? std::max(0.0F, -deltaVelocity_mps / deltaTime_s) : 0.0F;
53 decelSample = kOneHalf * (decelSample + estimatedDecel_mps2);
54 }
55
56 filteredDecel_mps2_ = alpha_ * decelSample + (1.0F - alpha_) * filteredDecel_mps2_;
57
58 if (velocity > minimumClimbVelocity_mps_ && filteredDecel_mps2_ > kMinValidDecel) {
59 tToApogee_ = velocity / filteredDecel_mps2_;
60 predApogeeTs_ = currentTimestamp + static_cast<uint32_t>(tToApogee_ * kMillisecondsPerSecond);
61
62 const float altitude = vve_.getEstimatedAltitude();
63 predApogeeAlt_ = altitude + velocity * tToApogee_ -
64 kOneHalf * filteredDecel_mps2_ * tToApogee_ * tToApogee_;
65
66 valid_ = true;
67 } else {
68 valid_ = false;
69 }
70
71 lastTs_ = currentTimestamp;
72 lastVel_ = velocity;
73 numWarmups_ = std::min(numWarmups_ + 1, kMaxWarmups);
74}
75
77 const uint32_t currentTimestamp = vve_.getTimestamp();
78 const float velocity = vve_.getEstimatedVelocity();
79 const float acceleration = vve_.getInertialVerticalAcceleration();
80
81 float kEstimate = 0.0F;
82 if (std::fabs(velocity) > 1.0F) {
83 kEstimate = std::max(0.0F, -(acceleration + kGravity_mps2)) / (velocity * velocity);
84 }
85
86 filteredDecel_mps2_ = alpha_ * kEstimate + (1.0F - alpha_) * filteredDecel_mps2_;
87 const float dragToMassRatio = filteredDecel_mps2_;
88
89 if (velocity > minimumClimbVelocity_mps_ && dragToMassRatio > kMinValidDecel) {
90 const float terminalVelocity = std::sqrt(kGravity_mps2 / dragToMassRatio);
91 tToApogee_ = (terminalVelocity / kGravity_mps2) * std::atan(velocity / terminalVelocity);
92 const float deltaAltitude = (terminalVelocity * terminalVelocity / (2.0F * kGravity_mps2)) *
93 std::log1p((velocity * velocity) /
94 (terminalVelocity * terminalVelocity));
95
96 predApogeeTs_ = currentTimestamp + static_cast<uint32_t>(tToApogee_ * kMillisecondsPerSecond);
97 predApogeeAlt_ = vve_.getEstimatedAltitude() + deltaAltitude;
98 valid_ = true;
99 } else {
100 valid_ = false;
101 }
102
103 lastTs_ = currentTimestamp;
104 lastVel_ = velocity;
105}
107 const uint32_t currentTimestamp_ms = vve_.getTimestamp();
108 const float altitude_m = vve_.getEstimatedAltitude();
109 const float velocity_mps = vve_.getEstimatedVelocity();
110 const float acceleration_mps2 = vve_.getInertialVerticalAcceleration();
111
112 constexpr size_t kFeatureCount = 10; // NOLINT(cppcoreguidelines-init-variables)
113
114 // Polynomial Regression Coefficients for C++
115 const std::array<float, kFeatureCount> coeffs = {
116 /* 1 */ 0.00000000F,
117 /* vertical_velocity */ 5.06108448F,
118 /* vertical_acceleration */ 63.94744144F,
119 /* delta_h_simple */ 0.52115350F,
120 /* vertical_velocity^2 */ 0.01494354F,
121 /* vertical_velocity vertical_acceleration */ 0.46012269F,
122 /* vertical_velocity delta_h_simple */ 0.01274390F,
123 /* vertical_acceleration^2 */ 3.27864634F,
124 /* vertical_acceleration delta_h_simple */ -0.00747177F,
125 /* delta_h_simple^2 */ -0.00208120F,
126 };
127 const float intercept = 308.64734694F;
128
129 // ───────────────────────────────────────────────────────
130 // Compute delta_h_simple = v^2 / (2 * decel), with decel > 0
131 const float decel = std::fabs(acceleration_mps2);
132 const float delta_h_simple = kOneHalf * (velocity_mps * velocity_mps) / decel;
133
134 // ───────────────────────────────────────────────────────
135 // Evaluate the regression model
136 const std::array<float, kFeatureCount> inputs = {
137 1.0F,
138 velocity_mps, // vertical_velocity
139 acceleration_mps2, // vertical_acceleration
140 delta_h_simple,
141 velocity_mps * velocity_mps, // vertical_velocity^2
142 velocity_mps * acceleration_mps2, // vertical_velocity vertical_acceleration
143 velocity_mps * delta_h_simple, // vertical_velocity delta_h_simple
144 acceleration_mps2 * acceleration_mps2, // vertical_acceleration^2
145 acceleration_mps2 * delta_h_simple, // vertical_acceleration delta_h_simple
146 delta_h_simple * delta_h_simple, // delta_h_simple^2
147 };
148
149 float apogeeRemaining_m = intercept;
150 for (size_t i = 0; i < kFeatureCount; ++i) { // NOLINT(cppcoreguidelines-init-variables)
151 apogeeRemaining_m += coeffs[i] * inputs[i];
152 }
153
154 // ───────────────────────────────────────────────────────
155 // Combine with current altitude to compute predicted apogee
156 predApogeeAlt_ = altitude_m + apogeeRemaining_m;
157
158 // Estimate time to apogee using kinematic model
159 tToApogee_ = velocity_mps / decel;
160 predApogeeTs_ = currentTimestamp_ms + static_cast<uint32_t>(tToApogee_ * kMillisecondsPerSecond);
161
162 valid_ = true;
163
164 lastTs_ = currentTimestamp_ms;
165 lastVel_ = velocity_mps;
166 numWarmups_ = std::min(numWarmups_ + 1, kMaxWarmups);
167
168 // printf("Current Timestamp: %u, Altitude: %.2f, Velocity: %.2f, Acceleration: %.2f, Predicted Apogee Remaining: %.2f, Delta H: %.2f\n",
169 // currentTimestamp_ms, altitude_m, velocity_mps, acceleration_mps2, apogeeRemaining_m, delta_h_simple);
170}
171
172
174{
175 //gets the current velocity and altitude of the rocket
176 const float velocity = vve_.getEstimatedVelocity();
177 const float height = vve_.getEstimatedAltitude();
178
179
180 //variables for analytical calculation
181 const float kVelocityEpsilon = 0.001F;
182 const float kVelocityScaleForAlpha = 150.0F;
183 const float kAlphaMin = 0.02F;
184 const float kAlphaMax = 0.25F;
185 const float kMinDragCoefficient = 0.00001F;
186 const float kApogeeFactor = 0.5F;
187 const float kBallisticDenominator = 2.0F;
188
189 //if the velocity is less than or equal to zero, the rocket has already reach apogee and the apogee is the current altitude
190 if (velocity <= 0.0F)
191 {
192 predApogeeAlt_ = height;
193 valid_ = true;
194 return;
195 }
196
197 //gets the current acceleration of the rocket
198 const float acceleration = vve_.getInertialVerticalAcceleration();
199
200 //calculates the measured drag coefficient
201 const float kMeasured = -(acceleration + kGravity_mps2) /
202 (velocity * velocity + kVelocityEpsilon);
203
204 if (kMeasured > 0.0F && kMeasured < 1.0F)
205 {
206 float alpha = clamp(std::fabs(velocity) / kVelocityScaleForAlpha,
207 kAlphaMin,
208 kAlphaMax);
209 currentDragCoefficient_ = (1.0F - alpha) * currentDragCoefficient_ + alpha * kMeasured;
210 }
211
212 // Analytic apogee calculation
213 float apogee = 0.0F;
214
215 if (currentDragCoefficient_ > kMinDragCoefficient)
216 {
217 apogee = height + (kApogeeFactor / currentDragCoefficient_) *
218 logf((kGravity_mps2 + currentDragCoefficient_ * velocity * velocity) / kGravity_mps2);
219 }
220 else
221 {
222 // fallback if drag unknown
223 apogee = height + (velocity * velocity) /
224 (kBallisticDenominator * kGravity_mps2);
225 }
226
227 predApogeeAlt_ = apogee;
228 valid_ = true;
229}
230
231
233{
234 const float estimatedVelocity = vve_.getEstimatedVelocity();
235 const float estimatedAltitude = vve_.getEstimatedAltitude();
236 const float inertialAccel = vve_.getInertialVerticalAcceleration();
237
238 const float kVelocityEpsilon = 0.001F;
239 const float kMinVelocityForDrag = 15.0F;
240 const float kAlpha = 0.05F; // slow filter constant
241 const float kMaxDragCoefficient = 0.05F;
242 const float kDt = 0.01F; // simulation time step
243 const int kMaxSimSteps = 500; // safety limit
244
245 // Already descending
246 if (estimatedVelocity <= 0.0F)
247 {
248 predApogeeAlt_ = estimatedAltitude;
249 valid_ = true;
250 return;
251 }
252
253 // Only update drag during cost phase
254 if (estimatedVelocity > kMinVelocityForDrag)
255 {
256 const float measuredDrag =
257 -(inertialAccel + kGravity_mps2) / (estimatedVelocity * estimatedVelocity + kVelocityEpsilon);
258
259 if (measuredDrag > 0.0F && measuredDrag < kMaxDragCoefficient)
260 {
261 currentDragCoefficient_ =
262 (1.0F - kAlpha) * currentDragCoefficient_ +
263 kAlpha * measuredDrag;
264 }
265 }
266
267 // -------- Forward simulate trajectory --------
268 float simAltitude = estimatedAltitude;
269 float simVelocity = estimatedVelocity;
270
271 for (int step = 0; step < kMaxSimSteps; step++)
272 {
273 const float dragAcceleration = currentDragCoefficient_ * simVelocity * simVelocity;
274 const float totalAcceleration = -kGravity_mps2 - dragAcceleration;
275
276 simVelocity += totalAcceleration * kDt;
277 simAltitude += simVelocity * kDt;
278
279 if (simVelocity <= 0.0F)
280 {
281 break;
282 }
283 }
284
285 predApogeeAlt_ = simAltitude;
286 valid_ = true;
287}
288
289// Simple getters
290bool ApogeePredictor::isPredictionValid() const { return valid_; }
291
293 return valid_ ? tToApogee_ : 0.0F;
294}
295
297 return valid_ ? predApogeeTs_ : 0;
298}
299
301 return valid_ ? predApogeeAlt_ : 0.0F;
302}
303
305 return filteredDecel_mps2_;
306}
307
309 return currentDragCoefficient_;
310}
constexpr float kMillisecondsToSeconds
float getPredictedApogeeAltitude_m() const
uint32_t getPredictedApogeeTimestamp_ms() const
bool isPredictionValid() const
float getDragCoefficient() const
ApogeePredictor(const VerticalVelocityEstimator &velocityEstimator, float accelFilterAlpha=0.2F, float minimumClimbVelocity_mps=1.0F)
float getFilteredDeceleration() const
float getTimeToApogee_s() const
1D Kalman filter fusing altimeter and accelerometer data.