STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_layer1_geometry_wiring.py
Go to the documentation of this file.
1"""Layer-1 geometry DOFs: configurable theta, hard geometric constraints, priced diameter.
2
3Each test breaks the thing it guards and asserts the guard fires.
4"""
5import math
6import numpy as np
7import pytest
8
10 _layer1_contraction_theta,
11 _layer1_geometry_infeasibility,
12 _layer1_chamber_mass_kg,
13 DEFAULT_CONTRACTION_HALF_ANGLE_DEG,
14)
16 chamber_length_calc, contraction_length_horizontal_calc,
17)
18
19A_T = 0.0018729167346808366
20PC = 2.9648e6
21
22
25 for req in ({}, None, {"layer1_contraction_half_angle_deg": None}):
26 assert math.degrees(_layer1_contraction_theta(req)) == pytest.approx(
27 DEFAULT_CONTRACTION_HALF_ANGLE_DEG)
28
30 for deg in (25.0, 30.0, 37.5, 45.0):
31 got = math.degrees(_layer1_contraction_theta(
32 {"layer1_contraction_half_angle_deg": deg}))
33 assert got == pytest.approx(deg)
34
36 for bad in ("banana", float("nan"), float("inf")):
37 got = math.degrees(_layer1_contraction_theta(
38 {"layer1_contraction_half_angle_deg": bad}))
39 assert got == pytest.approx(DEFAULT_CONTRACTION_HALF_ANGLE_DEG)
40
42 """Not just plumbing -- a different angle must move the chamber."""
43 A_c = math.pi / 4 * 0.127 ** 2
44 R_t = math.sqrt(A_T / math.pi)
45 out = {}
46 for deg in (25.0, 45.0):
47 th = _layer1_contraction_theta({"layer1_contraction_half_angle_deg": deg})
48 out[deg] = (chamber_length_calc(1.0 * A_T, A_T, A_c / A_T, th),
49 contraction_length_horizontal_calc(A_c, R_t, th))
50 assert out[25.0] != out[45.0]
51 # shallower cone is longer and takes more of the volume
52 assert out[25.0][1] > out[45.0][1]
53 assert out[25.0][0] < out[45.0][0]
54
55
57 A_C = math.pi / 4 * 0.127 ** 2
58
60 assert _layer1_geometry_infeasibility(
61 {}, L_cylindrical=0.09, D_chamber_inner=0.127,
62 A_chamber=self.A_C, n_elements=26) == 0.0
63
65 ok = _layer1_geometry_infeasibility(
66 {"layer1_min_Lcyl_over_D": 0.6}, L_cylindrical=0.09,
67 D_chamber_inner=0.127, A_chamber=self.A_C, n_elements=26)
68 bad = _layer1_geometry_infeasibility(
69 {"layer1_min_Lcyl_over_D": 0.9}, L_cylindrical=0.09,
70 D_chamber_inner=0.127, A_chamber=self.A_C, n_elements=26)
71 assert ok == 0.0
72 assert bad > 0.0
73
75 ok = _layer1_geometry_infeasibility(
76 {"layer1_max_element_pitch_m": 0.030}, L_cylindrical=0.09,
77 D_chamber_inner=0.127, A_chamber=self.A_C, n_elements=26)
78 bad = _layer1_geometry_infeasibility(
79 {"layer1_max_element_pitch_m": 0.018}, L_cylindrical=0.09,
80 D_chamber_inner=0.127, A_chamber=self.A_C, n_elements=26)
81 assert ok == 0.0
82 assert bad > 0.0
83
85 """Spreading a fixed element count over a bigger face must cost more."""
86 prev = -1.0
87 for D in (0.127, 0.1397, 0.1524, 0.1651):
88 v = _layer1_geometry_infeasibility(
89 {"layer1_max_element_pitch_m": 0.020}, L_cylindrical=0.09,
90 D_chamber_inner=D, A_chamber=math.pi / 4 * D ** 2, n_elements=26)
91 assert v > prev
92 prev = v
93
94
97 A_c = math.pi / 4 * 0.127 ** 2
98 assert (_layer1_chamber_mass_kg(A_c, 0.15, 0.0381, 2000.0)
99 == pytest.approx(_layer1_chamber_mass_kg(A_c, 0.15, 0.0381, 2000.0, Pc_pa=0.0)))
100
102 """Barrel-only keeps rewarding a fatter chamber; the closure must turn it over.
103
104 This is the documented reason a mass penalty was abandoned in Layer 1:
105 mass/volume ~ 4t/D, so charging barrel mass alone buys a short fat chamber.
106 """
107 th = math.radians(45.0)
108 R_t = math.sqrt(A_T / math.pi)
109 bores = (0.1143, 0.127, 0.1397, 0.1524, 0.1651)
110 barrel, full = [], []
111 for D in bores:
112 A_c = math.pi / 4 * D ** 2
113 L = (chamber_length_calc(1.0 * A_T, A_T, A_c / A_T, th)
114 + contraction_length_horizontal_calc(A_c, R_t, th))
115 barrel.append(_layer1_chamber_mass_kg(A_c, L, 0.0381, 2000.0))
116 full.append(_layer1_chamber_mass_kg(A_c, L, 0.0381, 2000.0, Pc_pa=PC))
117 # barrel-only is still falling at the fattest bore -> unbounded preference
118 assert barrel[-1] == min(barrel)
119 # with the closure the optimum is interior
120 assert full.index(min(full)) < len(bores) - 1
121
122
124 """constants_dict is curated -- a key absent from it never reaches the worker objective.
125
126 This is how the first wiring attempt failed silently: theta was honoured by
127 _layer1_apply_chamber_geometry_to_config (which reads the config) but defaulted to
128 45 deg inside the objective, so the optimiser scored a different chamber than it built,
129 and layer1_min_Lcyl_over_D was never enforced at all -- Layer 1 returned a design at
130 L_cyl/D = 0.337 against a configured floor of 0.55 and called it valid.
131 """
132
133 KEYS = ("layer1_contraction_half_angle_deg",
134 "layer1_min_Lcyl_over_D",
135 "layer1_max_element_pitch_m")
136
138 import inspect
139 from engine.optimizer.layers import layer1_static_optimization as L1
140 src = inspect.getsource(L1)
141 start = src.index("constants_dict = {")
142 end = src.index("}", src.index("'target_thrust'", start))
143 block = src[start:end]
144 for k in self.KEYS:
145 assert f"'{k}'" in block, (
146 f"{k} is missing from constants_dict: the worker objective will "
147 f"silently use its default while the applied geometry uses the config value")
148
150 """constants_dict is a plain dict; the config object is a pydantic model.
151
152 Both must work -- a bare .get() broke the model path, a bare getattr breaks the dict.
153 """
155 _layer1_contraction_theta, _layer1_geometry_infeasibility)
156 import math
157
158 class _Model: # stands in for DesignRequirementsConfig
159 layer1_contraction_half_angle_deg = 30.0
160 layer1_min_Lcyl_over_D = 0.55
161 layer1_max_element_pitch_m = 0.024
162
163 as_dict = {"layer1_contraction_half_angle_deg": 30.0,
164 "layer1_min_Lcyl_over_D": 0.55,
165 "layer1_max_element_pitch_m": 0.024}
166 A_c = math.pi / 4 * 0.127 ** 2
167 for src in (as_dict, _Model()):
168 assert math.degrees(_layer1_contraction_theta(src)) == pytest.approx(30.0)
169 # L_cyl/D = 0.337, below the 0.55 floor -> must score infeasible from BOTH shapes
170 assert _layer1_geometry_infeasibility(
171 src, L_cylindrical=0.0428, D_chamber_inner=0.127,
172 A_chamber=A_c, n_elements=27) > 0.0
#define min(a, b)
Definition Arduino.h:42