diff --git a/rocketpy/stochastic/stochastic_flight.py b/rocketpy/stochastic/stochastic_flight.py index 525526798..85187e286 100644 --- a/rocketpy/stochastic/stochastic_flight.py +++ b/rocketpy/stochastic/stochastic_flight.py @@ -94,6 +94,7 @@ def __init__( heading=heading, ) + self._validate_initial_solution(initial_solution) self.initial_solution = initial_solution self.terminate_on_apogee = terminate_on_apogee if max_time is None: diff --git a/rocketpy/stochastic/stochastic_model.py b/rocketpy/stochastic/stochastic_model.py index be2438a0c..d73bf07f8 100644 --- a/rocketpy/stochastic/stochastic_model.py +++ b/rocketpy/stochastic/stochastic_model.py @@ -621,13 +621,19 @@ def dict_generator(self): Notes ----- - 1. The dictionary is generated by iterating over the class attributes and: + 1. The dictionary is generated by iterating over the *declared* + stochastic inputs (constructor kwargs), not every attribute on + ``self``. This avoids treating opaque tuples such as + ``initial_solution`` as ``(nominal, spread, sampler)`` triples. a. If the attribute is a tuple, the value is generated using the\ distribution function specified in the tuple. b. If the attribute is a list, the value is randomly chosen from the list. """ generated_dict = {} - for arg, value in self.__dict__.items(): + for arg in self.__stochastic_dict: + if not hasattr(self, arg): + continue + value = getattr(self, arg) if isinstance(value, tuple): dist_sampler = value[-1] generated_dict[arg] = dist_sampler(value[0], value[1]) diff --git a/tests/unit/stochastic/test_stochastic_flight.py b/tests/unit/stochastic/test_stochastic_flight.py index e03917475..233800701 100644 --- a/tests/unit/stochastic/test_stochastic_flight.py +++ b/tests/unit/stochastic/test_stochastic_flight.py @@ -45,3 +45,29 @@ def test_stochastic_flight_optional_attributes(flight_calisto_robust): assert obj.terminate_on_apogee is True assert obj.time_overshoot is True assert obj.max_time == 987.6 + + +def test_dict_generator_skips_initial_solution_tuple(flight_calisto_robust): + """Regression for #1109: tuple initial_solution must not be sampled.""" + initial_solution = tuple(float(i) for i in range(14)) + stochastic_flight = StochasticFlight( + flight=flight_calisto_robust, + initial_solution=initial_solution, + rail_length=(5.2, 0.1), + ) + generated = next(stochastic_flight.dict_generator()) + assert "initial_solution" not in generated + assert stochastic_flight.initial_solution == initial_solution + + +def test_dict_generator_skips_initial_solution_list(flight_calisto_robust): + """List-form initial_solution must not be randomly subset-sampled.""" + initial_solution = [float(i) for i in range(14)] + stochastic_flight = StochasticFlight( + flight=flight_calisto_robust, + initial_solution=initial_solution, + inclination=[85, 86, 87], + ) + generated = next(stochastic_flight.dict_generator()) + assert "initial_solution" not in generated + assert stochastic_flight.initial_solution == initial_solution