BUG: serialize numpy SeedSequence for sensor seeds (#1087) - #1124
Merged
Gui-FernandesBR merged 2 commits intoAug 15, 2026
Merged
Gui-FernandesBR merged 2 commits into
Gui-FernandesBR merged 2 commits into
Conversation
Gui-FernandesBR
force-pushed
the
bug/1087-sensor-seedsequence-serialize
branch
from
August 11, 2026 01:48
c586fd2 to
118d866
Compare
Gui-FernandesBR
approved these changes
Aug 11, 2026
Gui-FernandesBR
force-pushed
the
bug/1087-sensor-seedsequence-serialize
branch
from
August 15, 2026 01:50
118d866 to
34679a2
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #1124 +/- ##
========================================
Coverage 84.33% 84.33%
========================================
Files 130 130
Lines 17258 17266 +8
========================================
+ Hits 14554 14562 +8
Misses 2704 2704 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Gui-FernandesBR
force-pushed
the
bug/1087-sensor-seedsequence-serialize
branch
from
August 15, 2026 02:28
34679a2 to
ee699b6
Compare
The SeedSequence branch pushed object_hook to 26 statements, one over pylint's max-statements, failing the Linters job. Extract the Flight rebuild into a module-level helper next to set_minimal_flight_attributes, matching how that path is already factored. Pure code move. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 16, 2026
myungjunlee
added a commit
to myungjunlee/RocketPy
that referenced
this pull request
Aug 16, 2026
`Sensor.__init__` passes the seed straight to `numpy.random.default_rng`, which also accepts `Generator` and `BitGenerator` objects. The sensor then constructs successfully and stores the object on `self._seed`, where `to_dict()` emits it verbatim, so the failure only surfaces later at `json.dumps()`, far from the call that caused it. RocketPy-Team#1124 closed the `SeedSequence` case in RocketPy-Team#1087 by teaching `RocketPyEncoder` to write one out. That works because a `SeedSequence` is defined by its entropy and spawn key, so it still describes the stream after a round trip. A `Generator` has no such description: its state advances on every draw, so whatever `to_dict()` wrote would depend on when it ran, and restoring it would not reproduce the stream the sensor actually used. Reject those two in the constructor instead, so the failure stays at the call that caused it. Ints, numpy ints, `SeedSequence` and `None` are untouched, as are the sequences of ints `default_rng` accepts and the encoder already serializes, so no seed that works today is rejected. Annotate `seed` on every constructor that takes one, with the type the issue itself names, so the contract is stated where the argument is declared rather than only in the docstring.
Gui-FernandesBR
pushed a commit
to myungjunlee/RocketPy
that referenced
this pull request
Sep 9, 2026
`Sensor.__init__` passes the seed straight to `numpy.random.default_rng`, which also accepts `Generator` and `BitGenerator` objects. The sensor then constructs successfully and stores the object on `self._seed`, where `to_dict()` emits it verbatim, so the failure only surfaces later at `json.dumps()`, far from the call that caused it. RocketPy-Team#1124 closed the `SeedSequence` case in RocketPy-Team#1087 by teaching `RocketPyEncoder` to write one out. That works because a `SeedSequence` is defined by its entropy and spawn key, so it still describes the stream after a round trip. A `Generator` has no such description: its state advances on every draw, so whatever `to_dict()` wrote would depend on when it ran, and restoring it would not reproduce the stream the sensor actually used. Reject those two in the constructor instead, so the failure stays at the call that caused it. Ints, numpy ints, `SeedSequence` and `None` are untouched, as are the sequences of ints `default_rng` accepts and the encoder already serializes, so no seed that works today is rejected. Annotate `seed` on every constructor that takes one, with the type the issue itself names, so the contract is stated where the argument is declared rather than only in the docstring.
Gui-FernandesBR
pushed a commit
that referenced
this pull request
Sep 9, 2026
* BUG: reject live RNG objects as Sensor seeds `Sensor.__init__` passes the seed straight to `numpy.random.default_rng`, which also accepts `Generator` and `BitGenerator` objects. The sensor then constructs successfully and stores the object on `self._seed`, where `to_dict()` emits it verbatim, so the failure only surfaces later at `json.dumps()`, far from the call that caused it. #1124 closed the `SeedSequence` case in #1087 by teaching `RocketPyEncoder` to write one out. That works because a `SeedSequence` is defined by its entropy and spawn key, so it still describes the stream after a round trip. A `Generator` has no such description: its state advances on every draw, so whatever `to_dict()` wrote would depend on when it ran, and restoring it would not reproduce the stream the sensor actually used. Reject those two in the constructor instead, so the failure stays at the call that caused it. Ints, numpy ints, `SeedSequence` and `None` are untouched, as are the sequences of ints `default_rng` accepts and the encoder already serializes, so no seed that works today is rejected. Annotate `seed` on every constructor that takes one, with the type the issue itself names, so the contract is stated where the argument is declared rather than only in the docstring. * BUG: reject RandomState and other non-descriptor Sensor seeds default_rng also accepts RandomState from NumPy 2.2 on, and RocketPy pins no upper bound on numpy, so the previous isinstance list let it through to the same late TypeError at json.dumps() that #1087 reported. Check the stable half of the contract instead of enumerating the live types: accept ints, array_like of ints and SeedSequence, and refuse the rest. A seed kind numpy starts accepting later is now refused at construction rather than reaching serialization. Widen the annotation to the array_like integer contract the check actually takes. It goes through a SeedLike union so the seven signatures stay inside the line limit while help() and inspect.signature() still expand the members. * TST: compare the noise stream across a seed round trip The existing round-trip tests assert on the stored seed value, which would still pass for a seed that survives JSON without naming the stream the original sensor used. Draw from the restored sensor instead and compare it against a fresh one built from the same seed, across the four descriptor kinds the constructor accepts. * BUG: refuse the seed shapes SeedSequence cannot take either The seed check accepted a nested sequence of ints, on the stated grounds that "numpy accepts as entropy just the same". It does not: SeedSequence raises TypeError for a nested sequence and ValueError for an array of two or more dimensions. Older NumPy let the nested form through, which is why this passed under Python 3.10 and failed under 3.14, where a newer NumPy is resolved -- the acceptance was never portable, and RocketPy sets no upper bound on the dependency. So _is_int_array_like now takes a flat sequence only, and an ndarray only at ndim <= 1. Both refusals name the seed, where the NumPy messages they replace name neither it nor the argument that carried it. The nested case moves out of test_int_array_like_seeds_are_accepted and into a rejection test alongside a 2-D array, which the check would have admitted for the same reason. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Gui-FernandesBR <guilherme.fernandes@maggu.ai> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
RocketPyEncoder/RocketPyDecoderto round-tripnumpy.random.SeedSequencevia its reconstructible state (entropy,spawn_key,n_children_spawned,pool_size) plus a class signature.TypeError: Object of type SeedSequence is not JSON serializablewhen dumping sensors (or any object) that hold aSeedSequenceseed, including spawned child sequences used for parallel Monte Carlo.Fixes #1087
Test plan
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 PYTHONPATH=. pytest -p pytest_cov tests/unit/sensors/test_sensor_seeding.py -q(8 passed)Accelerometer(..., seed=SeedSequence(0).spawn(1)[0])thenjson.dumps(sensor.to_dict(), cls=RocketPyEncoder)succeeds and decoder restores matching.state