STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_robust_ddp_reference.py
Go to the documentation of this file.
1"""Unit tests for reference generation module."""
2
3import unittest
4import numpy as np
5from unittest.mock import Mock
6
8 build_reference,
9 Reference,
10 _compute_thrust_command,
11 _compute_altitude_command,
12 _project_thrust,
13 _estimate_thrust_bounds,
14)
16 NavState,
17 Measurement,
18 Command,
19 CommandType,
20 ControllerConfig,
21)
22from engine.control.robust_ddp.engine_wrapper import EngineEstimate, EngineWrapper
23
24
25class TestReferenceGeneration(unittest.TestCase):
26 """Test reference generation."""
27
28 def setUp(self):
29 """Set up test fixtures."""
31 N=10,
32 dt=0.01,
33 MR_min=1.5,
34 MR_max=3.0,
35 headroom_dp_min=50000.0,
36 P_copv_min=1e6,
37 )
38
39 self.nav = NavState(
40 h=100.0, # 100 m altitude
41 vz=10.0, # 10 m/s upward
42 theta=0.0, # Vertical
43 mass_estimate=100.0, # 100 kg
44 )
45
47 P_copv=30e6, # 30 MPa
48 P_reg=24e6, # 24 MPa
49 P_u_fuel=3e6, # 3 MPa
50 P_u_ox=3.5e6, # 3.5 MPa
51 P_d_fuel=2.5e6, # 2.5 MPa
52 P_d_ox=3e6, # 3 MPa
53 )
54
56 """Test constant thrust command."""
57 cmd = Command(
58 command_type=CommandType.THRUST_DESIRED,
59 thrust_desired=5000.0, # 5 kN constant
60 )
61
62 F_des = _compute_thrust_command(cmd, horizon_N=10, dt=0.01)
63
64 # Should be constant
65 self.assertEqual(len(F_des), 10)
66 np.testing.assert_array_equal(F_des, 5000.0)
67
69 """Test piecewise thrust schedule."""
70 cmd = Command(
71 command_type=CommandType.THRUST_DESIRED,
72 thrust_desired=[
73 (0.0, 1000.0), # 1 kN at t=0
74 (0.05, 5000.0), # 5 kN at t=0.05
75 (0.1, 3000.0), # 3 kN at t=0.1
76 ],
77 )
78
79 F_des = _compute_thrust_command(cmd, horizon_N=10, dt=0.01)
80
81 # Should interpolate
82 self.assertEqual(len(F_des), 10)
83 # First value should be 1000
84 self.assertAlmostEqual(F_des[0], 1000.0, places=1)
85 # Last value should be 3000 (extrapolated)
86 self.assertAlmostEqual(F_des[-1], 3000.0, places=1)
87
89 """Test altitude command produces reasonable thrust."""
90 cmd = Command(
91 command_type=CommandType.ALTITUDE_GOAL,
92 altitude_goal=200.0, # 200 m goal
93 )
94
95 # Current at 100 m, goal at 200 m -> should increase thrust
96 F_des = _compute_altitude_command(
97 self.nav, cmd.altitude_goal, horizon_N=10, dt=0.01, cfg=self.cfg
98 )
99
100 # Should be positive
101 self.assertEqual(len(F_des), 10)
102 self.assertTrue(np.all(F_des > 0))
103
104 # Should be reasonable (hover ~ m*g = 100*9.81 = 981 N)
105 # With altitude error, should be higher
106 self.assertTrue(np.all(F_des >= 500.0)) # At least 500 N
107 self.assertTrue(np.all(F_des <= 1e5)) # Less than 100 kN
108
110 """Test altitude command when below goal increases thrust."""
111 # Start below goal
112 nav_low = NavState(h=50.0, vz=5.0, theta=0.0, mass_estimate=100.0)
113 cmd = Command(
114 command_type=CommandType.ALTITUDE_GOAL,
115 altitude_goal=200.0,
116 )
117
118 F_des_low = _compute_altitude_command(
119 nav_low, cmd.altitude_goal, horizon_N=10, dt=0.01, cfg=self.cfg
120 )
121
122 # Compare to case at goal
123 nav_at_goal = NavState(h=200.0, vz=0.0, theta=0.0, mass_estimate=100.0)
124 F_des_at_goal = _compute_altitude_command(
125 nav_at_goal, cmd.altitude_goal, horizon_N=10, dt=0.01, cfg=self.cfg
126 )
127
128 # Below goal should have higher thrust
129 self.assertGreater(np.mean(F_des_low), np.mean(F_des_at_goal))
130
132 """Test thrust projection respects bounds."""
133 F_min = 1000.0
134 F_max = 10000.0
135 F_prev = 5000.0
136 dt = 0.01
137
138 # Desired above max -> should clamp to max
139 F_ref = _project_thrust(15000.0, F_min, F_max, F_prev, dt, self.cfg)
140 self.assertAlmostEqual(F_ref, F_max, places=1)
141
142 # Desired below min -> should clamp to min
143 F_ref = _project_thrust(500.0, F_min, F_max, F_prev, dt, self.cfg)
144 self.assertAlmostEqual(F_ref, F_min, places=1)
145
146 # Desired in range -> should pass through (if slew allows)
147 F_ref = _project_thrust(6000.0, F_min, F_max, F_prev, dt, self.cfg)
148 self.assertGreaterEqual(F_ref, F_min)
149 self.assertLessEqual(F_ref, F_max)
150
152 """Test thrust projection respects slew rate limit."""
153 F_min = 0.0
154 F_max = 20000.0
155 F_prev = 5000.0
156 dt = 0.01
157
158 # Set slew rate limit
159 self.cfg.thrust_slew_max = 10000.0 # 10 kN/s
160
161 # Large step -> should be limited by slew rate
162 F_des = 15000.0 # 10 kN step
163 F_ref = _project_thrust(F_des, F_min, F_max, F_prev, dt, self.cfg)
164
165 # Should be limited: F_prev + dF_max*dt = 5000 + 10000*0.01 = 6000
166 max_allowed = F_prev + self.cfg.thrust_slew_max * dt
167 self.assertLessEqual(F_ref, max_allowed + 1.0) # Allow small numerical error
168
169 # Small step -> should pass through
170 F_des = 5100.0 # 100 N step
171 F_ref = _project_thrust(F_des, F_min, F_max, F_prev, dt, self.cfg)
172 self.assertAlmostEqual(F_ref, F_des, places=1)
173
175 """Test thrust bounds estimation."""
176 state = np.zeros(8) # Dummy state
177
178 # Mock engine estimate
179 eng_est = EngineEstimate(
180 P_ch=2.5e6,
181 F=5000.0,
182 mdot_F=1.0,
183 mdot_O=2.0,
184 MR=2.0,
185 injector_dp_F=0.5e6,
186 injector_dp_O=0.5e6,
187 )
188
189 F_min, F_max = _estimate_thrust_bounds(
190 state, self.meas, self.cfg, None, eng_est
191 )
192
193 # Bounds should be valid
194 self.assertGreaterEqual(F_min, 0.0)
195 self.assertGreater(F_max, F_min)
196
197 # F_max should be related to current thrust
198 self.assertGreater(F_max, eng_est.F)
199
201 """Test build_reference with thrust command mode."""
202 cmd = Command(
203 command_type=CommandType.THRUST_DESIRED,
204 thrust_desired=5000.0,
205 )
206
207 # Mock engine wrapper
208 engine_wrapper = Mock(spec=EngineWrapper)
209 eng_est = EngineEstimate(
210 P_ch=2.5e6,
211 F=5000.0,
212 mdot_F=1.0,
213 mdot_O=2.0,
214 MR=2.0,
215 injector_dp_F=0.5e6,
216 injector_dp_O=0.5e6,
217 )
218 engine_wrapper.estimate_from_pressures = Mock(return_value=eng_est)
219
220 ref = build_reference(
221 nav=self.nav,
222 meas=self.meas,
223 cmd=cmd,
224 cfg=self.cfg,
225 horizon_N=10,
226 engine_wrapper=engine_wrapper,
227 )
228
229 # Check structure
230 self.assertIsInstance(ref, Reference)
231 self.assertEqual(len(ref.F_ref), 10)
232 self.assertEqual(len(ref.MR_ref), 10)
233 self.assertEqual(len(ref.F_min), 10)
234 self.assertEqual(len(ref.F_max), 10)
235 self.assertEqual(len(ref.feasible), 10)
236
237 # Check MR_ref is mid-band
238 MR_mid = (self.cfg.MR_min + self.cfg.MR_max) / 2.0
239 np.testing.assert_array_almost_equal(ref.MR_ref, MR_mid, decimal=2)
240
241 # Check bounds
242 self.assertTrue(np.all(ref.F_ref >= ref.F_min))
243 self.assertTrue(np.all(ref.F_ref <= ref.F_max))
244
246 """Test build_reference with altitude command mode."""
247 cmd = Command(
248 command_type=CommandType.ALTITUDE_GOAL,
249 altitude_goal=200.0,
250 )
251
252 # Mock engine wrapper
253 engine_wrapper = Mock(spec=EngineWrapper)
254 eng_est = EngineEstimate(
255 P_ch=2.5e6,
256 F=5000.0,
257 mdot_F=1.0,
258 mdot_O=2.0,
259 MR=2.0,
260 injector_dp_F=0.5e6,
261 injector_dp_O=0.5e6,
262 )
263 engine_wrapper.estimate_from_pressures = Mock(return_value=eng_est)
264
265 ref = build_reference(
266 nav=self.nav,
267 meas=self.meas,
268 cmd=cmd,
269 cfg=self.cfg,
270 horizon_N=10,
271 engine_wrapper=engine_wrapper,
272 )
273
274 # Check structure
275 self.assertIsInstance(ref, Reference)
276 self.assertEqual(len(ref.F_ref), 10)
277
278 # Should produce positive thrust
279 self.assertTrue(np.all(ref.F_ref > 0))
280
281 # Should respect bounds
282 self.assertTrue(np.all(ref.F_ref >= ref.F_min))
283 self.assertTrue(np.all(ref.F_ref <= ref.F_max))
284
286 """Test build_reference respects slew rate across horizon."""
287 cmd = Command(
288 command_type=CommandType.THRUST_DESIRED,
289 thrust_desired=20000.0, # Large step from previous
290 )
291
292 # Set slew rate limit
293 self.cfg.thrust_slew_max = 10000.0 # 10 kN/s
294
295 # Mock engine wrapper
296 engine_wrapper = Mock(spec=EngineWrapper)
297 eng_est = EngineEstimate(
298 P_ch=2.5e6,
299 F=5000.0,
300 mdot_F=1.0,
301 mdot_O=2.0,
302 MR=2.0,
303 injector_dp_F=0.5e6,
304 injector_dp_O=0.5e6,
305 )
306 engine_wrapper.estimate_from_pressures = Mock(return_value=eng_est)
307
308 F_ref_prev = 5000.0 # Previous reference
309 ref = build_reference(
310 nav=self.nav,
311 meas=self.meas,
312 cmd=cmd,
313 cfg=self.cfg,
314 horizon_N=10,
315 engine_wrapper=engine_wrapper,
316 F_ref_prev=F_ref_prev,
317 )
318
319 # Check slew rate is respected
320 for k in range(1, len(ref.F_ref)):
321 dF = abs(ref.F_ref[k] - ref.F_ref[k - 1])
322 dF_max_allowed = self.cfg.thrust_slew_max * self.cfg.dt
323 self.assertLessEqual(dF, dF_max_allowed + 1.0) # Allow small error
324
325
326if __name__ == "__main__":
327 unittest.main()
328
void setUp(void)
Definition test_main.cpp:17