From 5655c7cfc12391bdadf3f2ba09706d9651670acf Mon Sep 17 00:00:00 2001 From: Joshua Terranova Date: Mon, 10 Aug 2026 18:22:12 -0700 Subject: [PATCH] BUG: accept numpy integer parachute height triggers (#1106) Use numbers.Real (excluding bool) so numpy.int* altitude triggers work like python ints/floats. --- rocketpy/rocket/parachute.py | 9 +++++---- tests/unit/rocket/test_parachute.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/rocketpy/rocket/parachute.py b/rocketpy/rocket/parachute.py index da99743ce..377154030 100644 --- a/rocketpy/rocket/parachute.py +++ b/rocketpy/rocket/parachute.py @@ -1,4 +1,5 @@ from inspect import Parameter, signature +from numbers import Real import numpy as np @@ -350,8 +351,8 @@ def wrapper(p, h, y, sensors, u_dot): self.triggerfunc = _make_wrapper(trigger) return - # Numeric altitude trigger - if isinstance(trigger, (int, float)): + # Numeric altitude trigger (accept numpy integers/floats; reject bool) + if isinstance(trigger, Real) and not isinstance(trigger, bool): self._trigger_falling_only = True def triggerfunc(p, h, y, sensors, u_dot): # pylint: disable=unused-argument @@ -379,8 +380,8 @@ def triggerfunc(p, h, y, sensors, u_dot): # pylint: disable=unused-argument # If we reach this point, the trigger is invalid raise ValueError( f"Unable to set the trigger function for parachute '{self.name}'. " - + "Trigger must be a callable, a float value or one of the strings " - + "('apogee'). " + + "Trigger must be a callable, a real number (height in meters), " + + "or one of the strings ('apogee'). " + "See the Parachute class documentation for more information." ) diff --git a/tests/unit/rocket/test_parachute.py b/tests/unit/rocket/test_parachute.py index 7a61c2349..35d731269 100644 --- a/tests/unit/rocket/test_parachute.py +++ b/tests/unit/rocket/test_parachute.py @@ -130,3 +130,32 @@ def test_callable_trigger_arities_route_arguments(trigger, expects_udot): result = parachute.triggerfunc(800.0, 500.0, [0.0] * 6, [], [1.0] * 6) assert result is True assert parachute.triggerfunc._expects_udot is expects_udot + + +class TestParachuteNumericTrigger: + """Numeric height triggers must accept numpy scalar integers/floats and + reject bool (which is a Real subclass in Python).""" + + @pytest.mark.parametrize( + "trigger", + [ + 800, + 800.0, + np.int32(800), + np.int64(800), + np.float64(800.0), + ], + ) + def test_numeric_height_trigger_accepted(self, trigger): + parachute = _make_parachute(trigger=trigger) + assert callable(parachute.triggerfunc) + # Falling below trigger height should fire + y = np.zeros(13) + y[5] = -1.0 + assert bool(parachute.triggerfunc(101325.0, 799.0, y, [], None)) is True + assert bool(parachute.triggerfunc(101325.0, 801.0, y, [], None)) is False + + @pytest.mark.parametrize("trigger", [True, False]) + def test_bool_trigger_rejected(self, trigger): + with pytest.raises(ValueError, match="real number"): + _make_parachute(trigger=trigger)