fix: accept numpy integer types as Parachute trigger - #1116
Conversation
Gui-FernandesBR
left a comment
There was a problem hiding this comment.
I suggest using "NUMERICAL_TYPES" constant from the Function module
|
I exercised the trigger-type boundary on head
Using Two changes still look necessary before review:
No branch changes were made. Environment: Python 3.12.6; NumPy 2.5.2; RocketPy PR head above; macOS 26.5.2 arm64. |
|
I understand this PR still needs some fixes.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #1116 +/- ##
===========================================
+ Coverage 84.33% 84.45% +0.11%
===========================================
Files 130 131 +1
Lines 17266 17495 +229
===========================================
+ Hits 14562 14775 +213
- Misses 2704 2720 +16 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
isinstance(trigger, (int, float)) rejects numpy integer types (np.int64, np.int32) because they don't subclass Python's int or float. Replace with isinstance(trigger, numbers.Real) which covers all numeric types (int, float, numpy scalars) while excluding bool. Fixes RocketPy-Team#1106
e3d2f0f to
7bb58b7
Compare
|
Thanks for the boundary table — that settles the NUMERICAL_TYPES = (float, int, complex, np.integer, np.floating, np.complexfloating)so it would accept What is still missing is item 1, the tests. The diff is still only the one-line predicate change in
That way the boundary you measured by hand is the boundary CI enforces from now on. With those in, this is good to go. One note: the workflow runs on this PR had been sitting in |
|
Correcting my previous comment: I said "with tests, this is good to go" before CI had run. Now that the runs are approved and have actually executed, this is red on all six Pytest legs, and the reason is more interesting than a missing test. That test came in with #1111 and it deliberately pins the asymmetry you are removing. Its docstring even points at this PR as the right fix:
So you are doing exactly what that note asked for — but the change is only half applied, and that is what makes the suite red: 1. return isinstance(member, (int, float)) and not isinstance(member, bool)Because 2. 3. Plus the tests I asked for in my previous comment, covering the boundary table you measured. Sorry for the premature green light — the checks were sitting unapproved, so there was nothing to read. |
This PR widened Parachute's height check from `(int, float)` to `numbers.Real`, so a height read out of a NumPy array is accepted. StochasticParachute validates the same triggers before a Parachute is ever built, and its copy of the check was still spelled `(int, float)`. A `numpy.int64` height was therefore still refused there, even though the Parachute it would have built accepts it. That mismatch is what turned all six Pytest legs red: `test_a_numpy_integer_is_refused_here_because_parachute_refuses_it` pinned the old asymmetry, and its own docstring said the fix belonged in Parachute. Rather than restate the predicate a second time, Parachute now exposes it as `_is_a_height_trigger` and stochastic/ calls that. The two spellings drifted apart once already; sharing one definition is what stops it happening again. Tests: - the NumPy integers move into `test_what_this_accepts_is_what_a_parachute_accepts`, where they now belong, joined by `numpy.float32` - the refusal test is rewritten around what is still refused by both: `numpy.bool_` and the complex types, none of which are `Real` - a new test asserts the agreement itself over the whole boundary, so changing one side alone fails with both verdicts printed - Parachute gains boundary tests of its own, including that `True` is still refused rather than read as a height of one metre Verified: reverting either side alone turns these red (the drift guard reports `np.float32(800.0): stochastic/ says False, Parachute says True`); ruff check and format clean; pylint 10.00/10; tests/unit 2098 passed, 16 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
I pushed the rest of this to your branch ( The missing half was that Rather than write the predicate out a second time, On tests, I did four things:
Your Verified before pushing: Thanks for the boundary table in your earlier comment — it was the thing that made the right predicate obvious, and it's now encoded in the suite. |
RocketPy-Team#1116 widened the height check to numbers.Real and, to stop the two copies of that check drifting apart, gave Parachute a single _is_a_height_trigger that stochastic/ now calls. This branch had edited the same predicate and the same two test modules, so it conflicted in three files. The two changes compose better than either alone: - _is_a_trigger keeps its ("time", t_deploy) branch, and the height case defers to _is_a_height_trigger as RocketPy-Team#1116 intended. - The time delay now uses that same predicate instead of a float() coercion wrapped in try/except. That was a real disagreement waiting to happen: float("3.0") succeeds, so ("time", "3.0") was accepted by Parachute while the stochastic check refused it. A string delay is now refused on both sides rather than quietly coerced. - RocketPy-Team#1116's drift guard grew the time forms, so it now covers this shape too. Reverting the Parachute side to the lenient coercion makes it fail with ('time', '3.0'): stochastic/ says False, Parachute says True. - Both test modules had appended to the same lists and to the end of the same files; kept both sides throughout, and added ("time", "3.0") to the refused-trigger cases. Verified: tests/unit 2154 passed, 17 skipped; the parachute unit modules and the new integration test give 89 passed; ruff check and format clean; pylint 10.00/10. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks for digging into this and carrying the fix through! Glad the boundary testing was useful. |
Summary
Replace
isinstance(trigger, (int, float))withisinstance(trigger, Real) and not isinstance(trigger, bool)inParachute.__init__().numpy.int64andnumpy.int32don't subclass Python'sintorfloat, so they were rejected with aValueErroreven though they represent valid numeric altitude values.numbers.Realcovers all numeric types (int, float, numpy scalars).Also excludes
bool— currentlytrigger=Trueis silently accepted as a 1m height, which is never intentional.Changes
rocketpy/rocket/parachute.py: ImportRealfromnumbers, replaceisinstance(trigger, (int, float))withisinstance(trigger, Real) and not isinstance(trigger, bool)Test plan
int 800→ accepted ✓float 800.0→ accepted ✓np.float64(800)→ accepted ✓np.int64(800)→ accepted ✓np.int32(800)→ accepted ✓True/False→ rejected ✓ (was previously accepted as height 1m)AST verified: no old pattern remains, import confirmed.
Closes #1106