Skip to content

Sampled parachute geometry never reaches the rocket StochasticRocket builds #1094

Description

@thc1006

Describe the bug

StochasticParachute.create_object() builds a Parachute from a complete draw:

generated_dict = next(self.dict_generator())
return Parachute(**generated_dict)

StochasticRocket.create_object() then throws that object away and builds a second one from six of its ten fields:

parachute = self._create_parachute(parachute)
rocket.add_parachute(
    name=parachute.name,
    cd_s=parachute.cd_s,
    trigger=parachute.trigger,
    sampling_rate=parachute.sampling_rate,
    lag=parachute.lag,
    noise=parachute.noise,
)

radius, height, porosity and drag_coefficient are not passed. Parachute.__init__ accepts all four and Rocket.add_parachute forwards all four, so the omission is silent: the second parachute re-derives radius from cd_s and the default drag coefficient, and height falls back to that radius.

A study that randomizes canopy geometry therefore flies the nominal geometry, while last_rnd_dict records the sampled values it did not use. The inputs log and the simulation disagree, and nothing reports it.

To Reproduce

stochastic_rocket.add_parachute(
    StochasticParachute(
        chute, radius=(2.0, 0.3), height=(1.5, 0.2),
        porosity=(0.05, 0.01), drag_coefficient=(1.4, 0.2),
    )
)
stochastic_rocket._set_stochastic(42)
built = stochastic_rocket.create_object()
logged = stochastic_rocket.last_rnd_dict["parachutes"][0]

Comparing what was drawn against what reached the rocket:

                 sampled                on rocket
cd_s             10.0                   10.0
radius            2.0914151239263292     1.5078600877302688
height            1.6500902391612915     1.5078600877302688
porosity          0.05940564716391214    0.0432
drag_coefficient  1.1920031787519008     1.4

radius and height come out equal because both were re-derived. sqrt(cd_s / 1.4 / pi) is 1.5078600877303, which is the value in both rows: the derivation used the default drag coefficient, not the sampled one.

Expected behavior

The sampled parachute reaches the rocket. _create_parachute has already built exactly the right object, so attaching it rather than rebuilding it would be enough:

rocket.parachutes.append(self._create_parachute(parachute))

That also stops Parachute.__init__ running twice per parachute per simulation, which matters for #1091, since the second run re-initializes the pressure noise from the global NumPy RNG.

A public Rocket.add_parachute_object() would be tidier than reaching into the list, if that is preferred.

Additional context

Not a regression, and not touched by #1054. Found while reviewing that PR.

Related: #1091 (parachute pressure noise outside the seed tree). This bug is upstream of that one: the noise is initialized twice today, so fixing the seed tree without fixing this leaves the second initialization to undo the first.

Verified on develop at 1691119, NumPy 2.5.1, Python 3.12.13.

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

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