Skip to content

ENH: seed sensor measurement noise per instance#1052

Open
thc1006 wants to merge 5 commits into
RocketPy-Team:developfrom
thc1006:enh/seed-sensor-noise
Open

ENH: seed sensor measurement noise per instance#1052
thc1006 wants to merge 5 commits into
RocketPy-Team:developfrom
thc1006:enh/seed-sensor-noise

Conversation

@thc1006

@thc1006 thc1006 commented Jul 6, 2026

Copy link
Copy Markdown

Pull request type

  • Code changes (bugfix, features)

Checklist

  • Tests for the changes have been added
  • Lint passes locally (ruff check and ruff format --check; pylint 10/10 on the touched files)
  • Sensor unit + integration tests pass locally (50 passing); relying on CI for the full slow suite
  • CHANGELOG.md updated

Current behavior

Sensor measurement noise is drawn from the process-global NumPy RNG (np.random.normal), so there's no way to seed it. Noisy-sensor runs aren't reproducible, and the noise is fragile under parallel or forked execution where workers share or reset the global RNG. This is #1042.

New behavior

Adds an optional seed argument to the sensors, threaded through Sensor, InertialSensor, ScalarSensor and the four concrete sensors. Each sensor builds its own numpy.random.Generator from np.random.default_rng(seed) and draws its white noise and random walk from that instead of np.random.

seed=None (the default) keeps the noise random but per-instance, so existing behavior is unchanged unless you pass a seed. With a seed, the noise is reproducible and independent of the global RNG state.

_reset is left alone on purpose (no re-seed): re-seeding on every reset would make each run in a repeated or Monte Carlo loop draw identical noise, which isn't the goal.

tests/unit/sensors/test_sensor_seeding.py covers same-seed reproducibility, decorrelation across seeds, independence from the global RNG (and not consuming it, which is the regression guard for the original bug), and the GnssReceiver path.

Breaking change

  • No

With seed=None the output is identical to before; the argument is opt-in.

Additional information

I kept seed out of the docstrings and to_dict/from_dict to keep the change minimal and consistent with the existing style (a couple of the sensor __init__s don't document every parameter either). Can add either if you'd rather.

Closes #1042

Sensor noise (Accelerometer, Gyroscope, Barometer, GnssReceiver) was drawn from
the process-global NumPy RNG, so it could not be seeded or reproduced, and it was
unsafe under parallel or forked execution where workers share or reset the global
RNG (issue RocketPy-Team#1042).

Thread an optional seed argument through the Sensor base classes. Each sensor now
owns a numpy.random.Generator from np.random.default_rng(seed) and draws its white
noise and random walk from it instead of np.random. seed=None keeps the noise
random but per-instance, so existing behaviour is unchanged unless a seed is given.

Also adds tests/unit/sensors/test_sensor_seeding.py covering reproducibility,
decorrelation across seeds, independence from the global RNG, and the GnssReceiver
path.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006 thc1006 requested a review from a team as a code owner July 6, 2026 21:55
Copilot AI review requested due to automatic review settings July 6, 2026 21:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

thc1006 added 2 commits July 7, 2026 05:56
Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
Sensor noise now comes from each sensor's own Generator, so the autouse
`_seed_rng` fixture that seeded the global `np.random` no longer made the noisy
assertions deterministic; they would flake on CI. Pass `seed=42` to the noisy
accelerometer, gyroscope, barometer, and gnss fixtures so their noise is
reproducible through the new per-instance seeding, and drop the now-defunct
`_seed_rng` fixture.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>

@MateusStano MateusStano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept seed out of the docstrings and to_dict/from_dict to keep the change minimal and consistent with the existing style (a couple of the sensor __init__s don't document every parameter either). Can add either if you'd rather.

Please do add it to the docstrings! If there are any parameters from __init__ missing in its docstrings than there is something wrong there!

Also add seed to to_dict and from_dict, please.

Otherwise this is a great PR!

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.79%. Comparing base (7e37cb0) to head (85579c1).
⚠️ Report is 9 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1052      +/-   ##
===========================================
+ Coverage    81.75%   81.79%   +0.03%     
===========================================
  Files          119      119              
  Lines        15192    15202      +10     
===========================================
+ Hits         12420    12434      +14     
+ Misses        2772     2768       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Address review feedback: document the seed argument in every sensor __init__
(this also fills in the name entry that was missing from Gyroscope's docstring),
and carry seed through to_dict/from_dict so it survives serialization, including
GnssReceiver's custom to_dict. from_dict reads it with data.get("seed") so dicts
saved before this change still load, defaulting to None.

Add serialization tests: the seed round-trips through the JSON encoder for every
sensor type, and from_dict defaults the seed to None when the key is absent.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006

thc1006 commented Jul 7, 2026

Copy link
Copy Markdown
Author

Thanks, done. I added the seed docstring to every sensor __init__ (and while I was in Gyroscope I filled in the name entry that was missing there too), and threaded seed through to_dict/from_dict, including GnssReceiver's custom to_dict. from_dict reads it with data.get("seed") so dicts saved before this change still load, defaulting to None.

Also added tests: the seed round-trips through the JSON encoder for every sensor type, plus a case for the missing-key default. I went through the encoder rather than a bare from_dict(to_dict()) because the inertial sensors' vectorized noise fields come back as lists that way, which is how the library actually saves and loads them.

@thc1006 thc1006 requested a review from MateusStano July 7, 2026 00:37

@Gui-FernandesBR Gui-FernandesBR left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Add tests for the argument-validation error paths (measurement range,
orientation, vectorized inputs, export file_format) and the __repr__ / __call__
helpers on Sensor and InertialSensor. These paths were untested; with them
rocketpy/sensors reaches full statement coverage.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants