Skip to content

Commit eb2d1e7

Browse files
committed
BUG: draw the eccentricities again, whichever way they were added
#1122 made dict_generator walk the inputs a model declared instead of every attribute on it, which is the right shape for #1109. add_cp_eccentricity and add_thrust_eccentricity run after __init__ has built that list, so their distributions stopped being drawn from: the value was set on the instance and every simulation used the same one, with nothing to say so. Bisected: at 3e16c9f all four eccentricities appear in the generated dictionary, at 5a71eb9 none of them do. An add_* method now declares what it installed, with the argument as given rather than the validated form, so _set_stochastic validates it again on each reseed and binds the distribution to the generator that is live then. ensemble_member was already fine, since StochasticEnvironment passes it through the constructor and the hasattr guard covers it not being set yet. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
1 parent be195d4 commit eb2d1e7

3 files changed

Lines changed: 65 additions & 0 deletions

File tree

‎rocketpy/stochastic/stochastic_model.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ def __init__(self, obj, seed=None, **kwargs):
124124
self.__stochastic_dict = kwargs
125125
self._set_stochastic(seed)
126126

127+
def _declare_stochastic_input(self, input_name, input_value):
128+
"""Declare an input that an ``add_*`` method installs after ``__init__``.
129+
130+
``dict_generator`` walks the inputs a model declared rather than every
131+
attribute on it (#1109), and that list is built in ``__init__``. Anything
132+
added afterwards is set on the instance and never drawn from unless it
133+
says so here.
134+
135+
The value is the argument as given, not the validated form, because
136+
``_set_stochastic`` validates it again on every reseed and binds the
137+
distribution to the generator that is live then.
138+
"""
139+
if input_value is None:
140+
return
141+
self.__stochastic_dict[input_name] = input_value
142+
127143
def _set_stochastic(self, seed=None):
128144
"""Set the stochastic attributes from the input dictionary.
129145
This method is useful to reset or reseed the attributes of the instance.

‎rocketpy/stochastic/stochastic_rocket.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,9 @@ def add_cp_eccentricity(self, x=None, y=None):
457457
Object of the StochasticRocket class.
458458
"""
459459
self.cp_eccentricity_x = self._validate_eccentricity("cp_eccentricity_x", x)
460+
self._declare_stochastic_input("cp_eccentricity_x", x)
460461
self.cp_eccentricity_y = self._validate_eccentricity("cp_eccentricity_y", y)
462+
self._declare_stochastic_input("cp_eccentricity_y", y)
461463
return self
462464

463465
def add_thrust_eccentricity(self, x=None, y=None):
@@ -485,9 +487,11 @@ def add_thrust_eccentricity(self, x=None, y=None):
485487
self.thrust_eccentricity_x = self._validate_eccentricity(
486488
"thrust_eccentricity_x", x
487489
)
490+
self._declare_stochastic_input("thrust_eccentricity_x", x)
488491
self.thrust_eccentricity_y = self._validate_eccentricity(
489492
"thrust_eccentricity_y", y
490493
)
494+
self._declare_stochastic_input("thrust_eccentricity_y", y)
491495
return self
492496

493497
def _validate_eccentricity(self, eccentricity, position):

‎tests/unit/stochastic/test_stochastic_rocket.py‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from numbers import Real
2+
13
import numpy as np
24
import pytest
35

@@ -191,3 +193,46 @@ def test_add_free_form_fins_wraps_a_deterministic_fin_set(calisto_robust):
191193
added = stochastic.aerodynamic_surfaces.get_tuple_by_type(StochasticFreeFormFins)
192194
assert len(added) == 1
193195
assert added[0].component.obj is fins
196+
197+
198+
@pytest.mark.parametrize(
199+
"add_them, names",
200+
[
201+
(
202+
"add_cp_eccentricity",
203+
("cp_eccentricity_x", "cp_eccentricity_y"),
204+
),
205+
(
206+
"add_thrust_eccentricity",
207+
("thrust_eccentricity_x", "thrust_eccentricity_y"),
208+
),
209+
],
210+
)
211+
def test_an_eccentricity_added_after_init_is_still_drawn(calisto, add_them, names):
212+
"""``dict_generator`` walks the declared inputs, and these arrive later.
213+
214+
The list is built in ``__init__``, so a distribution installed by an
215+
``add_*`` method afterwards was set on the instance and never drawn from:
216+
every simulation used the same value, with nothing to say so.
217+
"""
218+
stochastic = StochasticRocket(rocket=calisto, radius=0.0127 / 2)
219+
getattr(stochastic, add_them)(x=(0.0, 0.001), y=(0.0, 0.001))
220+
stochastic._set_stochastic(42)
221+
222+
generated = next(stochastic.dict_generator())
223+
224+
assert set(names) <= set(generated), f"{add_them} was set but never sampled"
225+
assert all(isinstance(generated[name], Real) for name in names)
226+
227+
228+
def test_two_seeds_move_an_eccentricity_that_was_added_late(calisto):
229+
"""Being present is not enough; it has to follow the seed."""
230+
stochastic = StochasticRocket(rocket=calisto, radius=0.0127 / 2)
231+
stochastic.add_cp_eccentricity(x=(0.0, 0.01), y=(0.0, 0.01))
232+
233+
def drawn(seed):
234+
stochastic._set_stochastic(seed)
235+
return next(stochastic.dict_generator())["cp_eccentricity_x"]
236+
237+
assert drawn(7) == drawn(7)
238+
assert drawn(7) != drawn(8)

0 commit comments

Comments
 (0)