Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions rocketpy/stochastic/stochastic_rocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,10 +734,16 @@ def _create_parachute(self, stochastic_parachute):
return parachute

def _create_eccentricities(self, stochastic_x, stochastic_y, eccentricity):
x_rnd = self._randomize_position(stochastic_x)
self.last_rnd_dict[eccentricity + "_x"] = x_rnd
y_rnd = self._randomize_position(stochastic_y)
self.last_rnd_dict[eccentricity + "_y"] = y_rnd
# A half that was given is a declared input, so dict_generator has drawn
# it already; drawing again would spend a second value out of the same
# stream and move every component position that follows.
def drawn_once(name, stochastic):
if name not in self.last_rnd_dict:
self.last_rnd_dict[name] = self._randomize_position(stochastic)
return self.last_rnd_dict[name]

x_rnd = drawn_once(eccentricity + "_x", stochastic_x)
y_rnd = drawn_once(eccentricity + "_y", stochastic_y)
return x_rnd, y_rnd

def create_object(self):
Expand Down
48 changes: 46 additions & 2 deletions tests/unit/stochastic/test_stochastic_rocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ def test_an_eccentricity_added_after_init_is_still_drawn(calisto, add_them, name
"""``dict_generator`` walks the declared inputs, and these arrive later.

The list is built in ``__init__``, so a distribution installed by an
``add_*`` method afterwards was set on the instance and never drawn from:
every simulation used the same value, with nothing to say so.
``add_*`` method afterwards was never re-validated on a reseed and stayed
bound to the unseeded generator: a fixed seed did not reproduce it.
"""
stochastic = StochasticRocket(rocket=calisto, radius=0.0127 / 2)
getattr(stochastic, add_them)(x=(0.0, 0.001), y=(0.0, 0.001))
Expand All @@ -236,3 +236,47 @@ def drawn(seed):

assert drawn(7) == drawn(7)
assert drawn(7) != drawn(8)


def test_a_declared_eccentricity_is_not_drawn_a_second_time(calisto):
"""``create_object`` applies the draw ``dict_generator`` already made.

A second draw spends another value out of the same stream, which moves
every component position ``create_object`` places after it.
"""
stochastic = StochasticRocket(rocket=calisto, radius=0.0127 / 2)
stochastic.add_cp_eccentricity(x=(0.0, 0.01), y=(0.0, 0.01))
stochastic.add_thrust_eccentricity(x=(0.0, 0.01), y=(0.0, 0.01))

stochastic._set_stochastic(42)
declared = next(stochastic.dict_generator())
expected = {name: declared[name] for name in declared if "eccentricity" in name}
assert len(expected) == 4

stochastic._set_stochastic(42)
rocket = stochastic.create_object()

applied = {
"cp_eccentricity_x": rocket.cp_eccentricity_x,
"cp_eccentricity_y": rocket.cp_eccentricity_y,
"thrust_eccentricity_x": rocket.thrust_eccentricity_x,
"thrust_eccentricity_y": rocket.thrust_eccentricity_y,
}
assert applied == expected
assert {name: stochastic.last_rnd_dict[name] for name in expected} == expected


def test_an_eccentricity_half_that_was_left_out_is_still_drawn(calisto):
"""Only a half that was given is a declared input, so the other is not.

``create_object`` has to keep drawing it, and keep reporting it, or the
inputs it writes stop describing the rocket it built.
"""
stochastic = StochasticRocket(rocket=calisto, radius=0.0127 / 2)
stochastic.add_cp_eccentricity(x=(0.0, 0.01))

stochastic._set_stochastic(42)
rocket = stochastic.create_object()

assert "cp_eccentricity_y" in stochastic.last_rnd_dict
assert stochastic.last_rnd_dict["cp_eccentricity_y"] == rocket.cp_eccentricity_y
Loading