diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index 55ca3486f..4fd0bcafc 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -708,71 +708,6 @@ def __simulate(self, verbose): self.__process_sensors_and_controllers_at_current_node(node, phase) - for parachute in node.parachutes: - # Calculate and save pressure signal - ( - noisy_pressure, - height_above_ground_level, - ) = self.__calculate_and_save_pressure_signals( - parachute, node.t, self.y_sol[2] - ) - if self._evaluate_parachute_trigger( - parachute, - noisy_pressure, - height_above_ground_level, - self.y_sol, - self.sensors, - phase.derivative, - self.t, - ): - # Remove parachute from flight parachutes - self.parachutes.remove(parachute) - # Create phase for time after detection and before inflation - # Must only be created if parachute has any lag - i = 1 - if parachute.lag != 0: - self.flight_phases.add_phase( - node.t, - phase.derivative, - clear=True, - index=phase_index + i, - ) - i += 1 - # Create flight phase for time after inflation - callbacks = [ - lambda self, parachute_cd_s=parachute.cd_s: setattr( - self, "parachute_cd_s", parachute_cd_s - ), - lambda self, parachute_radius=parachute.radius: setattr( - self, "parachute_radius", parachute_radius - ), - lambda self, parachute_height=parachute.height: setattr( - self, "parachute_height", parachute_height - ), - lambda self, parachute_porosity=parachute.porosity: setattr( - self, "parachute_porosity", parachute_porosity - ), - lambda self, added_mass_coefficient=parachute.added_mass_coefficient: ( - setattr( - self, - "parachute_added_mass_coefficient", - added_mass_coefficient, - ) - ), - ] - self.flight_phases.add_phase( - node.t + parachute.lag, - self.u_dot_parachute, - callbacks, - clear=False, - index=phase_index + i, - ) - # Prepare to leave loops and start new flight phase - phase.time_nodes.flush_after(node_index) - phase.time_nodes.add_node(self.t, [], [], []) - phase.solver.status = "finished" - # Save parachute event - self.parachute_events.append([self.t, parachute]) if self.__check_and_handle_parachute_triggers( node, phase, phase_index, node_index ): diff --git a/tests/unit/test_parachute_triggers.py b/tests/unit/test_parachute_triggers.py index 907b16268..e96d55cb8 100644 --- a/tests/unit/test_parachute_triggers.py +++ b/tests/unit/test_parachute_triggers.py @@ -108,3 +108,41 @@ def basic_trigger(_p, _h, _y): assert res is True assert called.get("ok", False) is True + + +def test_parachute_trigger_evaluated_once_per_node(calisto_robust, example_plain_env): + """Regression for #1086: each parachute trigger must run once per time node. + + A never-deploying counter trigger records heights; duplicate evaluations at + the same node would produce duplicate rounded heights in ``calls``. + """ + calls = [] + + def counting_trigger(_p, h, _y): + calls.append(round(float(h), 6)) + return False + + calisto_robust.parachutes.clear() + calisto_robust.add_parachute( + name="counter", + cd_s=10.0, + trigger=counting_trigger, + sampling_rate=10, + lag=0, + ) + + Flight( + rocket=calisto_robust, + environment=example_plain_env, + rail_length=5.2, + inclination=85, + heading=0, + time_overshoot=False, + max_time=30, + ) + + assert calls, "expected parachute trigger to be sampled during flight" + assert len(calls) == len(set(calls)), ( + "parachute trigger evaluated more than once at some height/node; " + f"duplicates among {len(calls)} calls" + )