24 """Set up test fixtures."""
44 def mock_estimate(P_d_F, P_d_O):
46 F = 1000.0 * (P_d_F + P_d_O) / 1e6
47 MR = P_d_O /
max(P_d_F, 1e3)
if P_d_F > 0
else 2.0
48 mdot_F = 0.5 * P_d_F / 1e6
49 mdot_O = 1.0 * P_d_O / 1e6
52 P_ch=0.5 * (P_d_F + P_d_O),
57 injector_dp_F=P_d_F - 0.5 * (P_d_F + P_d_O),
58 injector_dp_O=P_d_O - 0.5 * (P_d_F + P_d_O),
61 self.
engine_wrapper.estimate_from_pressures = Mock(side_effect=mock_estimate)
64 """Test running cost computation."""
65 x = np.array([30e6, 24e6, 3e6, 3.5e6, 2.5e6, 3e6, 0.01, 0.01])
66 u = np.array([0.5, 0.5])
73 injector_dp_F=0.125e6,
74 injector_dp_O=0.125e6,
80 cost = running_cost(x, u, eng_est, F_ref, MR_ref, self.
cfg, constraints)
83 self.assertGreaterEqual(cost, 0.0)
85 self.assertLess(cost, 100.0)
88 """Test running cost with tracking errors."""
89 x = np.array([30e6, 24e6, 3e6, 3.5e6, 2.5e6, 3e6, 0.01, 0.01])
90 u = np.array([0.5, 0.5])
97 injector_dp_F=0.125e6,
98 injector_dp_O=0.125e6,
104 cost = running_cost(x, u, eng_est, F_ref, MR_ref, self.
cfg, constraints)
107 self.assertGreater(cost, 1000.0)
110 """Test forward rollout."""
111 x0 = np.array([30e6, 24e6, 3e6, 3.5e6, 2.5e6, 3e6, 0.01, 0.01])
112 u_seq = np.ones((self.
cfg.N, N_CONTROL)) * 0.5
113 F_ref = np.ones(self.
cfg.N) * 5000.0
114 MR_ref = np.ones(self.
cfg.N) * 2.4
116 x_seq, eng_estimates, cost, violations = forward_rollout(
117 x0, u_seq, F_ref, MR_ref, self.
cfg, self.
params,
122 self.assertEqual(x_seq.shape, (self.
cfg.N + 1, N_STATE))
123 self.assertEqual(len(eng_estimates), self.
cfg.N)
124 self.assertEqual(len(violations), self.
cfg.N)
127 np.testing.assert_array_equal(x_seq[0], x0)
130 self.assertTrue(np.isfinite(cost))
131 self.assertGreaterEqual(cost, 0.0)
134 """Test DDP on a simplified toy linear system."""
144 convergence_tol=1e-3,
147 x0 = np.array([30e6, 24e6, 3e6, 3.5e6, 2.5e6, 3e6, 0.01, 0.01])
148 u_seq_init = np.ones((cfg_simple.N, N_CONTROL)) * 0.5
149 F_ref = np.ones(cfg_simple.N) * 5000.0
150 MR_ref = np.ones(cfg_simple.N) * 2.4
152 params = DynamicsParams.from_config(cfg_simple)
155 solution = solve_ddp(
157 u_seq_init=u_seq_init,
161 dynamics_params=params,
163 w_bar=np.zeros(N_STATE),
164 use_robustification=
False,
168 self.assertIsInstance(solution, DDPSolution)
169 self.assertEqual(solution.u_seq.shape, (cfg_simple.N, N_CONTROL))
170 self.assertEqual(solution.x_seq.shape, (cfg_simple.N + 1, N_STATE))
171 self.assertEqual(len(solution.eng_estimates), cfg_simple.N)
174 self.assertTrue(np.all(solution.u_seq >= 0.0))
175 self.assertTrue(np.all(solution.u_seq <= 1.0))
178 self.assertTrue(np.isfinite(solution.objective))
179 self.assertGreaterEqual(solution.objective, 0.0)
182 self.assertGreater(solution.iterations, 0)
183 self.assertLessEqual(solution.iterations, cfg_simple.max_iterations)
186 """Test that DDP decreases cost over iterations."""
195 convergence_tol=1e-4,
198 x0 = np.array([30e6, 24e6, 3e6, 3.5e6, 2.5e6, 3e6, 0.01, 0.01])
199 u_seq_init = np.ones((cfg_simple.N, N_CONTROL)) * 0.3
200 F_ref = np.ones(cfg_simple.N) * 5000.0
201 MR_ref = np.ones(cfg_simple.N) * 2.4
203 params = DynamicsParams.from_config(cfg_simple)
206 x_seq_init, _, cost_init, _ = forward_rollout(
207 x0, u_seq_init, F_ref, MR_ref, cfg_simple, params,
212 solution = solve_ddp(
214 u_seq_init=u_seq_init,
218 dynamics_params=params,
220 w_bar=np.zeros(N_STATE),
221 use_robustification=
False,
226 self.assertLessEqual(solution.objective, cost_init * 1.5)
229 """Test that DDP respects control bounds [0, 1]."""
238 convergence_tol=1e-3,
241 x0 = np.array([30e6, 24e6, 3e6, 3.5e6, 2.5e6, 3e6, 0.01, 0.01])
243 u_seq_init = np.array([
250 F_ref = np.ones(cfg_simple.N) * 5000.0
251 MR_ref = np.ones(cfg_simple.N) * 2.4
253 params = DynamicsParams.from_config(cfg_simple)
256 solution = solve_ddp(
258 u_seq_init=u_seq_init,
262 dynamics_params=params,
264 w_bar=np.zeros(N_STATE),
265 use_robustification=
False,
269 self.assertTrue(np.all(solution.u_seq >= 0.0))
270 self.assertTrue(np.all(solution.u_seq <= 1.0))
273 self.assertTrue(np.all(solution.x_seq >= 0.0))
276 """Test backward pass produces correct structure."""
286 x0 = np.array([30e6, 24e6, 3e6, 3.5e6, 2.5e6, 3e6, 0.01, 0.01])
287 u_seq = np.ones((cfg_simple.N, N_CONTROL)) * 0.5
288 F_ref = np.ones(cfg_simple.N) * 5000.0
289 MR_ref = np.ones(cfg_simple.N) * 2.4
291 params = DynamicsParams.from_config(cfg_simple)
294 x_seq, eng_estimates, _, _ = forward_rollout(
295 x0, u_seq, F_ref, MR_ref, cfg_simple, params,
300 k_seq, K_seq, Vx, Vxx = backward_pass(
301 x_seq, u_seq, eng_estimates, F_ref, MR_ref, cfg_simple, params,
303 w_bar=np.zeros(N_STATE), use_robustification=
False, gamma_robust=1.0
307 self.assertEqual(k_seq.shape, (cfg_simple.N, N_CONTROL))
308 self.assertEqual(K_seq.shape, (cfg_simple.N, N_CONTROL, N_STATE))
309 self.assertEqual(Vx.shape, (N_STATE,))
310 self.assertEqual(Vxx.shape, (N_STATE, N_STATE))
313 np.testing.assert_array_almost_equal(Vxx, Vxx.T, decimal=10)
316 eigenvals = np.linalg.eigvals(Vxx)
317 self.assertTrue(np.all(eigenvals >= -1e-10))
320 """Test forward line search."""
330 x0 = np.array([30e6, 24e6, 3e6, 3.5e6, 2.5e6, 3e6, 0.01, 0.01])
331 u_seq = np.ones((cfg_simple.N, N_CONTROL)) * 0.5
332 F_ref = np.ones(cfg_simple.N) * 5000.0
333 MR_ref = np.ones(cfg_simple.N) * 2.4
335 params = DynamicsParams.from_config(cfg_simple)
338 x_seq_nom, eng_estimates, _, _ = forward_rollout(
339 x0, u_seq, F_ref, MR_ref, cfg_simple, params,
344 k_seq, K_seq, _, _ = backward_pass(
345 x_seq_nom, u_seq, eng_estimates, F_ref, MR_ref, cfg_simple, params,
347 w_bar=np.zeros(N_STATE), use_robustification=
False, gamma_robust=1.0
351 u_seq_new, alpha, cost_new = forward_line_search(
352 x0, u_seq, k_seq, K_seq, x_seq_nom, F_ref, MR_ref, cfg_simple, params,
353 self.
engine_wrapper, cfg_simple.dt, alpha_init=1.0, alpha_min=1e-4
357 self.assertEqual(u_seq_new.shape, u_seq.shape)
358 self.assertGreaterEqual(alpha, 1e-4)
359 self.assertLessEqual(alpha, 1.0)
360 self.assertTrue(np.isfinite(cost_new))
363 self.assertTrue(np.all(u_seq_new >= 0.0))
364 self.assertTrue(np.all(u_seq_new <= 1.0))