40def _trace(n: int, dt: float, thrust: float, mo: float, mf: float, ox0: float, fu0: float):
41 """A constant burn: ``n`` firing steps, each burning ``mo``/``mf`` for ``dt``."""
42 t = [dt * (k + 1)
for k
in range(n)]
43 ox = [
max(ox0 - mo * dt * (k + 1), 0.0)
for k
in range(n)]
44 fu = [
max(fu0 - mf * dt * (k + 1), 0.0)
for k
in range(n)]
45 ch = {
"thrust_N": [thrust] * n,
"mdot_oxidiser": [mo] * n,
"mdot_fuel": [mf] * n}
46 trace = SimpleNamespace(t=t, tank={
"OX": {
"liquid_mass_kg": ox},
"FU": {
"liquid_mass_kg": fu}})
47 return trace, list(range(n)), ch
51 """2 kg/s LOX, 1.25 kg/s fuel (O/F 1.6), 7 kN, 50 ms steps; 6.61 kg LOX, 4.40 kg fuel. LOX runs
52 out first, at 3.305 s. Up to the last full step the burn makes 7000/3.25 N·s per kg; the load
53 it can burn is all the LOX and 6.61/1.6 kg of fuel."""
54 dt, mo, mf = 0.05, 2.0, 1.25
55 trace, fire, ch =
_trace(67, dt, 7000.0, mo, mf, 6.61, 4.40)
56 impulse, t_dep = _impulse_to_depletion(trace, fire, dt,
"OX",
"FU", ch)
57 burnable = 6.61 * (1.0 + 1.0 / 1.6)
58 assert impulse == pytest.approx(7000.0 / 3.25 * burnable, rel=1e-12)
59 assert t_dep == pytest.approx(6.61 / 2.0, rel=1e-12)
63 """Slide the load so the depletion crosses a step boundary: the step-summed impulse jumps by a
64 step's worth, the impulse to depletion moves by exactly the extra propellant's share."""
65 dt, mo, mf = 0.05, 2.0, 1.25
67 for ox0
in (6.598, 6.602):
68 n = math.ceil(ox0 / (mo * dt) - 1e-9)
69 trace, fire, ch =
_trace(n, dt, 7000.0, mo, mf, ox0, 4.40)
70 stepped = 7000.0 * dt * n
71 to_dep, _ = _impulse_to_depletion(trace, fire, dt,
"OX",
"FU", ch)
72 out.append((stepped, to_dep))
73 (s1, d1), (s2, d2) = out
74 assert s2 - s1 == pytest.approx(7000.0 * dt)
75 assert d2 - d1 == pytest.approx(7000.0 / 3.25 * 0.004 * (1 + 1 / 1.6), rel=1e-9)
89 assert ok[
"violation"] == 0.0
and ok[
"objective"] == 24000.0
91 short =
_graded(copv_end_psia=578.0 + 50.0)
92 assert short[
"violation"] == pytest.approx(0.5)
94 soft =
_graded(fuel_stiffness_min=0.15)
95 assert soft[
"violation"] == pytest.approx(0.25)
98 assert rich[
"violation"] == pytest.approx(0.5, rel=1e-9)
99 both =
_graded(copv_end_psia=628.0, fuel_stiffness_min=0.15)
100 assert both[
"violation"] == pytest.approx(0.75)
102 better_but_short =
_graded(copv_end_psia=628.0, total_impulse_Ns=30000.0)
103 assert opt._rank(ok) < opt._rank(better_but_short)
104 assert opt._rank(
_graded(total_impulse_Ns=24100.0)) < opt._rank(ok)
106 failed = opt.grade({
"ok":
True,
"preflight": [
"MAWP"],
"figures":
None,
"x": {}},
107 opt.OptimizeRequest(), {},
None)
108 assert opt._rank(ok) < opt._rank(failed)
111@pytest.fixture(scope="module")
123 prep = prepare(config,
None, drawing,
LayerXSettings(drawing_id=drawing.id, tank_pressure_psia=STUDY_LOCKUP_PSIA, ack_gn2_condensation=
True))
124 v = {x.key: x
for x
in opt.default_variables(prep, config)}
125 lock = v[
"lockup_psia"]
128 assert lock.hi == pytest.approx(600.0)
and "max_lox_tank_pressure_psi" in lock.basis
129 assert lock.lo == round(prep.derived[
"target_lockup_psia"] * 0.85)
130 assert v[
"copv_psig"].hi == pytest.approx(4500.0)
and "the bottle pressure the drawing states" in v[
"copv_psig"].basis
131 assert "assumed" in lock.basis
and "assumed" in v[
"copv_psig"].basis
132 assert not v[
"copv_volume_L"].enabled
137 """Lockup only, 550 to 600 psia, a handful of burns. Every burn's impulse to depletion rises
138 with its lockup, and the search ends on the cap and says so."""
139 req = opt.OptimizeRequest(objective=
"impulse", max_evaluations=6, verify=
False,
140 variables={
"lockup_psia": {
"lo": 550.0,
"hi": 600.0},
141 "copv_psig": {
"enabled":
False}})
142 res = opt.run_optimize(config, drawing,
LayerXSettings(drawing_id=drawing.id, tank_pressure_psia=STUDY_LOCKUP_PSIA, ack_gn2_condensation=
True), [], req, workers=2)
143 pts = sorted((e[
"x"][
"lockup_psia"], e[
"objective"])
for e
in res[
"history"]
if e[
"objective"]
is not None)
145 assert all(b[1] > a[1]
for a, b
in zip(pts, pts[1:])), pts
146 assert res[
"best"][
"x"][
"lockup_psia"] == pytest.approx(600.0)
147 assert any(
"finished on a bound" in n
for n
in res[
"notes"])
148 assert res[
"best"][
"violation"] == 0.0