Skip to content

Parachute pressure noise is outside the Monte Carlo seed tree #1091

Description

@thc1006

Describe the bug

Parachute takes its pressure noise from the process-global NumPy RNG:

# rocketpy/rocket/parachute.py
self.noise_signal = [[-1e-6, np.random.normal(noise[0], noise[1])]]   # line 270
...
+ beta * np.random.normal(noise[0], noise[1])                          # line 285

__init_noise draws once when the object is built, and noise_function keeps drawing from that same global state every time Flight samples the trigger. Nothing in StochasticParachute reaches it. The stochastic model seeds cd_s, lag, the sampling rate and the other constructor parameters, but the noise generator is not part of that.

Flight adds this noise to the pressure it hands the trigger, and the trigger height is computed from the noisy pressure, so it can move the deployment time and with it the descent, the impact point and the flight time.

There is a second copy of the problem in StochasticRocket.create_object(). It builds one Parachute through stochastic_parachute.create_object() (line 749) to read the parameters off, then builds another through rocket.add_parachute(...) (line 841). Both constructions draw from the global RNG and the first object is thrown away.

To Reproduce

The shared fixtures already use non-zero noise, so nothing special is needed to reach this:

# tests/fixtures/parachutes/parachute_fixtures.py
noise=(0, 8.3, 0.5)

Run the same seed twice in one process, or the same simulation index under different worker counts, and compare parachute events or trajectory outputs rather than .inputs.txt. Under fork the workers inherit one global state, so their first simulations can share a noise stream. Under spawn and forkserver each process starts from unrelated entropy, so the same index can get different noise depending on which worker picked it up.

Expected behavior

A run identified by a root seed and a simulation index should fly the same trajectory, not only record the same sampled parameters.

Give Parachute its own generator and take the noise from it:

class Parachute:
    def __init__(self, ..., rng=None):
        self.rng = np.random.default_rng() if rng is None else rng

then derive a child seed for the runtime noise alongside the existing parameter seed, and make sure the Parachute that ends up on the Rocket is the one holding it. Removing the double construction would be worth doing at the same time, since it is what makes "which object got which stream" ambiguous.

Additional context

#1054 builds a per-index seed tree over the stochastic parameter objects. This is the largest built-in random source that tree does not reach, which is why that PR is scoped to reproducible sampled inputs rather than reproducible trajectories. Closing this gap is what would let the guarantee be stated in terms of results.

Worth auditing the other runtime random sources at the same time, sensors in particular, so the boundary can be stated once instead of per component.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions