From 34353ee23f5a678c929b13890b25c334bef70478 Mon Sep 17 00:00:00 2001 From: thc1006 <84045975+thc1006@users.noreply.github.com> Date: Sat, 8 Aug 2026 20:00:59 +0800 Subject: [PATCH] The grain's outer radius was smaller than its bore grain_outer_radius was 0.00843 where the official scenario has 0.0843. A hollow grain's mass goes as (outer^2 - inner^2), so an outer radius under the inner one is a negative mass, and RocketPy 1.13 builds it without a word: solid grain mass -0.623 kg -> 4.861 kg total propellant 15.407 kg -> 20.891 kg apogee 199.8 m -> 130.5 m The apogee was 53% high because the rocket was carrying negative fuel. Since nothing downstream will catch this, the radii are now named constants with a check in front of them. It rejects an inner radius that is zero, negative, equal to the outer one, or larger. Also center_of_dry_mass_position, 0.015 where the scenario has 0.15. That one is inert today because dry_mass is 0 and it is a mass-weighted position, so it changes no number in this diff. Aligned anyway: it is the same decimal slip, and it stops being inert the day dry_mass does not stay zero. Every other parameter in this file already matches the official scenario. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> --- custom_rocket.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/custom_rocket.py b/custom_rocket.py index c8d3819..afbf653 100644 --- a/custom_rocket.py +++ b/custom_rocket.py @@ -3,12 +3,30 @@ from rocketpy.motors.tank import MassFlowRateBasedTank from rocketpy.sensors import Accelerometer, Barometer, GnssReceiver, Gyroscope +# A hollow grain's mass goes as (outer^2 - inner^2), so an outer radius under +# the inner one is a negative mass. RocketPy 1.13 accepts it in silence: the +# motor carried -0.623 kg of fuel and the flight apogeed at 200 m, not 131 m. +GRAIN_OUTER_RADIUS = 0.0843 +GRAIN_INITIAL_INNER_RADIUS = 0.0295 + + +def _check_grain_geometry(outer_radius, inner_radius): + """A hollow grain needs a positive bore strictly inside the outer wall.""" + if not 0 < inner_radius < outer_radius: + raise ValueError( + "grain geometry is impossible: need " + "0 < grain_initial_inner_radius < grain_outer_radius, got " + f"inner={inner_radius}, outer={outer_radius}" + ) + def create_custom_rocket(): """ Create a custom rocket with a hybrid motor and an oxidizer tank. """ + _check_grain_geometry(GRAIN_OUTER_RADIUS, GRAIN_INITIAL_INNER_RADIUS) + tank_shape = CylindricalTank(0.133, height=0.83) oxidizer_tank = MassFlowRateBasedTank( name="oxidizer_tank", @@ -28,13 +46,16 @@ def create_custom_rocket(): thrust_source=1080, dry_mass=0, dry_inertia=(0, 0, 0), - center_of_dry_mass_position=0.015, + # 0.15 in the official scenario, not 0.015. Inert while dry_mass is 0, + # since this is a mass-weighted position, but a 10x error waiting for + # the day that mass stops being zero. + center_of_dry_mass_position=0.15, burn_time=(0, 30), reshape_thrust_curve=False, grain_number=1, grain_separation=0, - grain_outer_radius=0.00843, - grain_initial_inner_radius=0.0295, + grain_outer_radius=GRAIN_OUTER_RADIUS, + grain_initial_inner_radius=GRAIN_INITIAL_INNER_RADIUS, grain_initial_height=0.2757, grain_density=900, nozzle_radius=0.04425,