STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_layer2_of_pointwise.py
Go to the documentation of this file.
2import numpy as np
3import sys
4import os
5from unittest.mock import MagicMock, patch
6
7# Add project root to path
8sys.path.append(os.getcwd())
9
10from engine.optimizer.layers.layer2_pressure import run_layer2a_minimum_pressures, run_layer2_pressure
11from engine.pipeline.config_schemas import PintleEngineConfig
12
14 print("\n=== Testing Layer 2a Pointwise MR Check ===")
15
16 config_mock = MagicMock(spec=PintleEngineConfig)
17 config_mock.ablative_cooling = None
18 config_mock.graphite_insert = None
19
20 target_of = 2.0
21
22 # Mock results with a spike in MR
23 # Average error: (0*19 + 0.3*1)/20 = 0.015 (1.5% average error, well within 20% limit)
24 # Pointwise max error: 0.3 (30%, exceeds 20% limit)
25 mr_spike = np.full(20, target_of)
26 mr_spike[10] = target_of * 1.3
27
28 mock_results = {
29 "F": np.full(20, 1000.0),
30 "mdot_O": np.full(20, 1.0),
31 "mdot_F": np.full(20, 0.5),
32 "MR": mr_spike,
33 "chugging_stability_margin": np.full(20, 1.0)
34 }
35
36 with patch("engine.optimizer.layers.layer2_pressure.PintleEngineRunner") as MockRunner:
37 runner_instance = MockRunner.return_value
38 runner_instance.evaluate_arrays_with_time.return_value = mock_results
39
40 # We expect this to fail because of the max pointwise error (30% > 20%)
41 # but the average error is only 1.5%
42 lox_min, fuel_min, summary, success = run_layer2a_minimum_pressures(
43 optimized_config=config_mock,
44 initial_lox_pressure_pa=3e6,
45 initial_fuel_pressure_pa=2e6,
46 peak_thrust=1000.0,
47 target_apogee_m=1000.0,
48 rocket_dry_mass_kg=100.0,
49 max_lox_tank_capacity_kg=50.0,
50 max_fuel_tank_capacity_kg=50.0,
51 target_burn_time=10.0,
52 n_time_points=20,
53 optimal_of_ratio=target_of
54 )
55
56 if not success:
57 print("PASS: run_layer2a_minimum_pressures correctly rejected spiked MR.")
58 else:
59 print("FAIL: run_layer2a_minimum_pressures accepted spiked MR despite 30% spike.")
60
62 print("\n=== Testing Layer 2 Objective Weighted MR Penalty ===")
63
64 config_mock = MagicMock(spec=PintleEngineConfig)
65 config_mock.ablative_cooling = None
66 config_mock.graphite_insert = None
67
68 target_of = 2.0
69
70 # Case A: Flat MR with 10% error everywhere
71 mr_flat = np.full(20, target_of * 1.1)
72
73 # Case B: Spiked MR with 20% error at one point, 0% elsewhere
74 # Mean error: (0.2 * 1 + 0 * 19) / 20 = 0.01 (1%)
75 # Max error: 0.2 (20%)
76 mr_spiked = np.full(20, target_of)
77 mr_spiked[10] = target_of * 1.2
78
79 # Case C: Even larger spike
80 # Mean error: (0.4 * 1 + 0 * 19) / 20 = 0.02 (2%)
81 # Max error: 0.4 (40%)
82 mr_very_spiked = np.full(20, target_of)
83 mr_very_spiked[10] = target_of * 1.4
84
85 with patch("engine.optimizer.layers.layer2_pressure.PintleEngineRunner") as MockRunner:
86 runner_instance = MockRunner.return_value
87
88 # Helper to get objective for a given MR history
89 def get_obj(mr_hist):
90 runner_instance.evaluate_arrays_with_time.return_value = {
91 "F": np.full(20, 1000.0),
92 "mdot_O": np.full(20, 1.0),
93 "mdot_F": np.full(20, 0.5),
94 "MR": mr_hist,
95 "chugging_stability_margin": np.full(20, 1.0),
96 "Pc": np.full(20, 2.5e6),
97 "press_tank": MagicMock()
98 }
99 runner_instance.evaluate.return_value = {"Pc": 3e6}
100
101 objs = []
102 def capture_obj(it, obj, best):
103 objs.append(obj)
104
105 # Run one iteration of DE
106 # We use a dummy x vector [length, ratio, k, factor, k] * N_SEGMENTS
107 x = np.array([0.1, 0.9, 0.3, 1.0, 0.3] * 8)
108
109 run_layer2_pressure(
110 optimized_config=config_mock,
111 initial_lox_pressure_pa=3e6,
112 initial_fuel_pressure_pa=2e6,
113 peak_thrust=1000.0,
114 target_apogee_m=1000.0,
115 rocket_dry_mass_kg=100.0,
116 max_lox_tank_capacity_kg=100.0,
117 max_fuel_tank_capacity_kg=100.0,
118 target_burn_time=10.0,
119 n_time_points=20,
120 max_iterations=1,
121 optimal_of_ratio=target_of,
122 objective_callback=capture_obj,
123 de_popsize=1,
124 de_maxiter=1
125 )
126 return objs[0] if objs else 1e9
127
128 print("Calculating objective for flat MR (10% error everywhere)...")
129 obj_flat = get_obj(mr_flat)
130 print(f"Flat Objective: {obj_flat:.2f}")
131
132 print("Calculating objective for spiked MR (20% max, 1% mean error)...")
133 obj_spiked = get_obj(mr_spiked)
134 print(f"Spiked Objective: {obj_spiked:.2f}")
135
136 print("Calculating objective for very spiked MR (40% max, 2% mean error)...")
137 obj_very_spiked = get_obj(mr_very_spiked)
138 print(f"Very Spiked Objective: {obj_very_spiked:.2f}")
139
140 # Before my changes, obj_spiked would have been MUCH smaller than obj_flat
141 # because the mean error was 1% vs 10%.
142 # Now, Case B (20% max error) should have a significant penalty.
143 # Case C (40% max error) should be even higher due to the weighted max component.
144
145 if obj_very_spiked > obj_spiked:
146 print("PASS: Objective correctly penalizes larger spikes more heavily.")
147 else:
148 print("FAIL: Objective did not penalize larger spike correctly.")
149
150 # We can't easily compare to obj_flat without knowing exact base penalties,
151 # but the jump from spiked to very spiked confirms the max-weight logic.
152
153if __name__ == "__main__":