X Tutup
Skip to content
Open
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Merge branch 'develop' into enh/acceleration-data-to-trigger-parachutes
  • Loading branch information
ViniciusCMB authored Dec 9, 2025
commit e84df408af795de30f97d5c957e5a1829c7ae68e
265 changes: 1 addition & 264 deletions rocketpy/simulation/flight.py
Original file line number Diff line number Diff line change
Expand Up @@ -822,270 +822,7 @@ def __simulate(self, verbose):
if self.time_overshoot and self.__process_overshootable_nodes(
phase, phase_index, node_index
):
# Check exactly when it went out using root finding
# Disconsider elevation
self.solution[-2][3] -= self.env.elevation
self.solution[-1][3] -= self.env.elevation
# Get points
y0 = (
sum(self.solution[-2][i] ** 2 for i in [1, 2, 3])
- self.effective_1rl**2
)
yp0 = 2 * sum(
self.solution[-2][i] * self.solution[-2][i + 3]
for i in [1, 2, 3]
)
t1 = self.solution[-1][0] - self.solution[-2][0]
y1 = (
sum(self.solution[-1][i] ** 2 for i in [1, 2, 3])
- self.effective_1rl**2
)
yp1 = 2 * sum(
self.solution[-1][i] * self.solution[-1][i + 3]
for i in [1, 2, 3]
)
# Put elevation back
self.solution[-2][3] += self.env.elevation
self.solution[-1][3] += self.env.elevation
# Cubic Hermite interpolation (ax**3 + bx**2 + cx + d)
a, b, c, d = calculate_cubic_hermite_coefficients(
0,
float(phase.solver.step_size),
y0,
yp0,
y1,
yp1,
)
a += 1e-5 # TODO: why??
# Find roots
t_roots = find_roots_cubic_function(a, b, c, d)
# Find correct root
valid_t_root = [
t_root.real
for t_root in t_roots
if 0 < t_root.real < t1 and abs(t_root.imag) < 0.001
]
if len(valid_t_root) > 1: # pragma: no cover
raise ValueError(
"Multiple roots found when solving for rail exit time."
)
if len(valid_t_root) == 0: # pragma: no cover
raise ValueError(
"No valid roots found when solving for rail exit time."
)
# Determine final state when upper button is going out of rail
self.t = valid_t_root[0] + self.solution[-2][0]
interpolator = phase.solver.dense_output()
self.y_sol = interpolator(self.t)
self.solution[-1] = [self.t, *self.y_sol]
self.out_of_rail_time = self.t
self.out_of_rail_time_index = len(self.solution) - 1
self.out_of_rail_state = self.y_sol
# Create new flight phase
self.flight_phases.add_phase(
self.t,
self.u_dot_generalized,
index=phase_index + 1,
)
# 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"

# Check for apogee event
# TODO: negative vz doesn't really mean apogee. Improve this.
if len(self.apogee_state) == 1 and self.y_sol[5] < 0:
# Assume linear vz(t) to detect when vz = 0
t0, vz0 = self.solution[-2][0], self.solution[-2][6]
t1, vz1 = self.solution[-1][0], self.solution[-1][6]
t_root = find_root_linear_interpolation(t0, t1, vz0, vz1, 0)
# Fetch state at t_root
interpolator = phase.solver.dense_output()
self.apogee_state = interpolator(t_root)
# Store apogee data
self.apogee_time = t_root
self.apogee_x = self.apogee_state[0]
self.apogee_y = self.apogee_state[1]
self.apogee = self.apogee_state[2]

if self.terminate_on_apogee:
self.t = self.t_final = t_root
# Roll back solution
self.solution[-1] = [self.t, *self.apogee_state]
# Set last flight phase
self.flight_phases.flush_after(phase_index)
self.flight_phases.add_phase(self.t)
# 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"
elif len(self.solution) > 2:
# adding the apogee state to solution increases accuracy
# we can only do this if the apogee is not the first state
self.solution.insert(-1, [t_root, *self.apogee_state])
# Check for impact event
if self.y_sol[2] < self.env.elevation:
# Check exactly when it happened using root finding
# Cubic Hermite interpolation (ax**3 + bx**2 + cx + d)
a, b, c, d = calculate_cubic_hermite_coefficients(
x0=0, # t0
x1=float(phase.solver.step_size), # t1 - t0
y0=float(self.solution[-2][3] - self.env.elevation), # z0
yp0=float(self.solution[-2][6]), # vz0
y1=float(self.solution[-1][3] - self.env.elevation), # z1
yp1=float(self.solution[-1][6]), # vz1
)
# Find roots
t_roots = find_roots_cubic_function(a, b, c, d)
# Find correct root
t1 = self.solution[-1][0] - self.solution[-2][0]
valid_t_root = [
t_root.real
for t_root in t_roots
if abs(t_root.imag) < 0.001 and 0 < t_root.real < t1
]
if len(valid_t_root) > 1: # pragma: no cover
raise ValueError(
"Multiple roots found when solving for impact time."
)
# Determine impact state at t_root
self.t = self.t_final = valid_t_root[0] + self.solution[-2][0]
interpolator = phase.solver.dense_output()
self.y_sol = self.impact_state = interpolator(self.t)
# Roll back solution
self.solution[-1] = [self.t, *self.y_sol]
# Save impact state
self.x_impact = self.impact_state[0]
self.y_impact = self.impact_state[1]
self.z_impact = self.impact_state[2]
self.impact_velocity = self.impact_state[5]
# Set last flight phase
self.flight_phases.flush_after(phase_index)
self.flight_phases.add_phase(self.t)
# 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"

# List and feed overshootable time nodes
if self.time_overshoot:
# Initialize phase overshootable time nodes
overshootable_nodes = self.TimeNodes()
# Add overshootable parachute time nodes
overshootable_nodes.add_parachutes(
self.parachutes, self.solution[-2][0], self.t
)
# Add last time node (always skipped)
overshootable_nodes.add_node(self.t, [], [], [])
if len(overshootable_nodes) > 1:
# Sort and merge equal overshootable time nodes
overshootable_nodes.sort()
overshootable_nodes.merge()
# Clear if necessary
if overshootable_nodes[0].t == phase.t and phase.clear:
overshootable_nodes[0].parachutes = []
overshootable_nodes[0].callbacks = []
# Feed overshootable time nodes trigger
interpolator = phase.solver.dense_output()
for (
overshootable_index,
overshootable_node,
) in self.time_iterator(overshootable_nodes):
# Calculate state at node time
overshootable_node.y_sol = interpolator(
overshootable_node.t
)
for parachute in overshootable_node.parachutes:
# Calculate and save pressure signal
(
noisy_pressure,
height_above_ground_level,
) = self.__calculate_and_save_pressure_signals(
parachute,
overshootable_node.t,
overshootable_node.y_sol[2],
)

# Check for parachute trigger
if self._evaluate_parachute_trigger(
parachute,
noisy_pressure,
height_above_ground_level,
overshootable_node.y_sol,
self.sensors,
phase.derivative,
overshootable_node.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(
overshootable_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(
overshootable_node.t + parachute.lag,
self.u_dot_parachute,
callbacks,
clear=False,
index=phase_index + i,
)
# Rollback history
self.t = overshootable_node.t
self.y_sol = overshootable_node.y_sol
self.solution[-1] = [
overshootable_node.t,
*overshootable_node.y_sol,
]
# Prepare to leave loops and start new flight phase
overshootable_nodes.flush_after(
overshootable_index
)
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]
)
break

# If controlled flight, post process must be done on sim time
# Post-process controllers if needed
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.
X Tutup