Skip to content

Commit c9641fc

Browse files
authored
ENH: addition of weathercocking model to flight.py
- ENH: introduced new weathercock_coeff parameter in Flight.init (default: 1.0) - ENH: updated u_dot_generalized_3dof to compute quaternion derivatives proportional to misalignment with relative wind - ENH: angular velocity = weathercock_coeff * sin(misalignment_angle)
1 parent 41e22a4 commit c9641fc

1 file changed

Lines changed: 75 additions & 2 deletions

File tree

rocketpy/simulation/flight.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ def __init__( # pylint: disable=too-many-arguments,too-many-statements
492492
equations_of_motion="standard",
493493
ode_solver="LSODA",
494494
simulation_mode="6 DOF",
495+
weathercock_coeff=1.0,
495496
):
496497
"""Run a trajectory simulation.
497498
@@ -575,6 +576,13 @@ def __init__( # pylint: disable=too-many-arguments,too-many-statements
575576
A custom ``scipy.integrate.OdeSolver`` can be passed as well.
576577
For more information on the integration methods, see the scipy
577578
documentation [1]_.
579+
weathercock_coeff : float, optional
580+
Coefficient that controls the rate at which the rocket's body axis
581+
aligns with the relative wind direction in 3-DOF simulations.
582+
A higher value means faster alignment (quasi-static weathercocking).
583+
This parameter is only used when simulation_mode is '3 DOF'.
584+
Default is 1.0, which provides moderate alignment. Set to 0 to
585+
disable weathercocking (fixed attitude).
578586
579587
580588
Returns
@@ -606,7 +614,8 @@ def __init__( # pylint: disable=too-many-arguments,too-many-statements
606614
self.equations_of_motion = equations_of_motion
607615
self.simulation_mode = simulation_mode
608616
self.ode_solver = ode_solver
609-
617+
self.weathercock_coeff = weathercock_coeff
618+
610619
# Controller initialization
611620
self.__init_controllers()
612621

@@ -1793,7 +1802,8 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals
17931802
def u_dot_generalized_3dof(self, t, u, post_processing=False):
17941803
"""Calculates derivative of u state vector with respect to time when the
17951804
rocket is flying in 3 DOF motion in space and significant mass variation
1796-
effects exist.
1805+
effects exist.Includes a weathercocking model that evolves the body axis
1806+
direction toward the relative wind direction.
17971807
17981808
Parameters
17991809
----------
@@ -1898,7 +1908,70 @@ def u_dot_generalized_3dof(self, t, u, post_processing=False):
18981908
e_dot = [0, 0, 0, 0] # Euler derivatives unused in 3DOF
18991909
w_dot = [0, 0, 0] # No angular dynamics in 3DOF
19001910
r_dot = [vx, vy, vz]
1911+
# Weathercocking: evolve body axis direction toward relative wind
1912+
# The body z-axis (attitude vector) should align with -freestream_velocity
1913+
if self.weathercock_coeff > 0 and free_stream_speed > 1e-6:
1914+
# Current body z-axis in inertial frame (attitude vector)
1915+
# From rotation matrix: column 3 gives the body z-axis in inertial frame
1916+
body_z_inertial = Vector(
1917+
[
1918+
2 * (e1 * e3 + e0 * e2),
1919+
2 * (e2 * e3 - e0 * e1),
1920+
1 - 2 * (e1**2 + e2**2),
1921+
]
1922+
)
1923+
1924+
# Desired direction: opposite of freestream velocity (into the wind)
1925+
# This is the direction the rocket nose should point
1926+
# Division by free_stream_speed ensures the result is a unit vector
1927+
desired_direction = -free_stream_velocity / free_stream_speed
1928+
1929+
# Compute rotation axis (cross product of current and desired)
1930+
rotation_axis = body_z_inertial ^ desired_direction
1931+
rotation_axis_mag = abs(rotation_axis)
1932+
1933+
if rotation_axis_mag > 1e-8:
1934+
# Normalize rotation axis
1935+
rotation_axis = rotation_axis / rotation_axis_mag
19011936

1937+
# The magnitude of the cross product of two unit vectors equals
1938+
# the sine of the angle between them
1939+
sin_angle = rotation_axis_mag
1940+
1941+
# Clamp sin_angle to valid range
1942+
sin_angle = min(1.0, max(-1.0, sin_angle))
1943+
1944+
# Angular velocity magnitude proportional to misalignment angle
1945+
# Using sin(angle) as approximation for small angles: sin(theta) ≈ theta
1946+
omega_mag = self.weathercock_coeff * sin_angle
1947+
1948+
# Angular velocity in inertial frame
1949+
omega_inertial = rotation_axis * omega_mag
1950+
1951+
# Transform angular velocity to body frame
1952+
omega_body = Kt @ omega_inertial
1953+
1954+
# Quaternion derivative from angular velocity in body frame
1955+
omega1_wc, omega2_wc, omega3_wc = (
1956+
omega_body.x,
1957+
omega_body.y,
1958+
omega_body.z,
1959+
)
1960+
e0_dot = 0.5 * (-omega1_wc * e1 - omega2_wc * e2 - omega3_wc * e3)
1961+
e1_dot = 0.5 * (omega1_wc * e0 + omega3_wc * e2 - omega2_wc * e3)
1962+
e2_dot = 0.5 * (omega2_wc * e0 - omega3_wc * e1 + omega1_wc * e3)
1963+
e3_dot = 0.5 * (omega3_wc * e0 + omega2_wc * e1 - omega1_wc * e2)
1964+
e_dot = [e0_dot, e1_dot, e2_dot, e3_dot]
1965+
w_dot = [0, 0, 0] # No angular acceleration in 3DOF model
1966+
else:
1967+
# Already aligned or anti-aligned
1968+
e_dot = [0, 0, 0, 0]
1969+
w_dot = [0, 0, 0]
1970+
else:
1971+
# No weathercocking or negligible freestream speed
1972+
e_dot = [0, 0, 0, 0]
1973+
w_dot = [0, 0, 0]
1974+
19021975
u_dot = [*r_dot, *v_dot, *e_dot, *w_dot]
19031976

19041977
if post_processing:

0 commit comments

Comments
 (0)