STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_convergent_angle_tangency.py
Go to the documentation of this file.
1"""The convergent cone must meet the 1.5-Rt entrance arc tangentially at ANY angle.
2
3The entrance arc used to be hardcoded ``linspace(-135 deg, -90 deg)``. Its wall slope at
4parameter t is -cot(t), so -135 deg is tangency with a 45 deg cone and only a 45 deg cone.
5Any other convergent angle met the arc at a corner -- 15 deg of it at theta = 30 -- right
6where the gas accelerates hardest into the throat.
7
8``layer1_contraction_half_angle_deg`` made that angle configurable in Layer 1's arithmetic
9while the built contour stayed at 45 deg, so the optimiser was scoring a chamber nobody
10would build. These tests pin both halves: the default is unchanged, and a non-default angle
11produces a tangent wall.
12"""
13from __future__ import annotations
14
15import numpy as np
16import pytest
17
19 area_exit_calc,
20 area_throat_calc,
21 chamber_geometry_calc,
22 force_coeffcient_default,
23 theta_default,
24)
25from engine.core.nozzle_solver import rao
26
27PC, THRUST, D_EXIT = 2.965e6, 8000.0, 0.1165
28ANGLES_DEG = [45.0, 40.0, 35.0, 30.0, 25.0]
29
30
31def _arc_start_slope_deg(theta_rad: float) -> float:
32 """Wall angle of the entrance arc's first segment, in degrees (negative = converging)."""
33 a_t = area_throat_calc(PC, THRUST, force_coeffcient_default)
34 pts, _, _ = rao(a_t, area_exit_calc(D_EXIT), method="top", do_plot=False,
35 steps=3000, convergent_half_angle_rad=theta_rad)
36 p = np.asarray(pts)
37 d = p[1] - p[0]
38 return float(np.degrees(np.arctan2(d[1], d[0])))
39
40
41@pytest.mark.parametrize("deg", ANGLES_DEG)
43 """No corner in the wall at the cone -> arc junction, for any convergent angle."""
44 kink = abs(_arc_start_slope_deg(np.deg2rad(deg)) - (-deg))
45 assert kink < 0.05, f"{deg} deg cone meets the arc with a {kink:.2f} deg corner"
46
47
49 """Guard the guard: pinning the arc at 45 deg must reintroduce the corner.
50
51 Without this, the tangency test above would still pass if someone re-hardcoded the
52 arc and every caller happened to ask for 45 deg.
53 """
54 kink = abs(_arc_start_slope_deg(np.pi / 4.0) - (-30.0))
55 assert kink > 14.0, (
56 "a 45 deg arc against a 30 deg cone should show ~15 deg of corner; "
57 f"got {kink:.2f} deg -- the tangency test is no longer discriminating"
58 )
59
60
61@pytest.mark.parametrize("deg", ANGLES_DEG)
63 """The straight convergent section is actually drawn at the requested half-angle."""
64 theta = np.deg2rad(deg)
65 pts, _, _ = chamber_geometry_calc(
66 pc_design=PC, thrust_design=THRUST, diameter_inner=0.1651,
67 diameter_exit=D_EXIT, l_star=0.85, steps=600, theta=theta)
68 p = np.asarray(pts)
69 throat = int(np.argmin(p[:, 1]))
70 d = np.diff(p[:throat + 1], axis=0)
71 keep = np.abs(d[:, 0]) > 1e-12
72 slopes = d[keep, 1] / d[keep, 0]
73 # The cone is the longest run of constant negative slope before the arc.
74 target = -np.tan(theta)
75 on_cone = np.abs(slopes - target) < 1e-6
76 assert on_cone.sum() > 10, f"no straight {deg} deg convergent run found in the contour"
77
78
80 """theta_default is load-bearing: it is the value every existing config inherits."""
81 assert theta_default == pytest.approx(np.pi / 4.0)
82
83
85 """Omitting theta must be identical to passing 45 deg -- no silent behaviour change."""
86 kw = dict(pc_design=PC, thrust_design=THRUST, diameter_inner=0.1651,
87 diameter_exit=D_EXIT, l_star=0.85, steps=200)
88 a, _, la = chamber_geometry_calc(**kw)
89 b, _, lb = chamber_geometry_calc(**kw, theta=np.pi / 4.0)
90 assert np.array_equal(np.asarray(a), np.asarray(b))
91 assert la == lb