Trimmed Simple HALE with Hinged Wingtips - Gust Response¶
This notebook trims a simple HALE aircraft with free-hinging wingtips, then flies it through a 1-minus-cosine gust as a free-flying body. After the dynamic solve we plot the wingtip displacement, root strains, and hinge angles over time.
Imports¶
import matplotlib.pyplot as plt
from jax import numpy as jnp
from flapjax.aero.flowfields import OneMinusCosineFlowField
from flapjax.models.simple_hale.simple_hale import generate_simple_hale
Case parameters¶
Define the free-stream conditions, hinge geometry, and simulation time.
u_inf_mag: float = 10.0
gust_intensity: float = 0.2
gust_length: float = 1.0 * u_inf_mag # 1 second gust duration
physical_time: float = 30.0
sigma_wing: float = 1.5 # stiffness multiplier for the wing structure
flare_angle_deg: float = 20.0 # flare angle for stabilising the wingtip
hinge_spring_stiffness: float = 0.0 # optional torsional spring at the hinge to resist rotation
Build the gust flowfield¶
A one-minus-cosine vertical gust positioned upstream of the aircraft, with relative_motion=True
for the trim solve (clamped aircraft, moving air).
flowfield = OneMinusCosineFlowField(
u_inf=jnp.array((u_inf_mag, 0.0, 0.0)),
rho=1.225,
relative_motion=True,
gust_length=gust_length,
gust_amplitude=gust_intensity * u_inf_mag,
gust_x0=jnp.array((-2.0 * gust_length, 0.0, 0.0)),
)
Generate the hinged HALE aircraft¶
Providing a value for flare_angle automatically adds the hinge constraints to the model.
hale = generate_simple_hale(
flowfield=flowfield,
sigma_wing=sigma_wing,
flare_angle=jnp.deg2rad(flare_angle_deg),
hinge_spring_stiffness=hinge_spring_stiffness,
)
n_tstep = int(physical_time / float(hale.aero.dt)) + 1 # number of timesteps for the dynamic solve
Trim¶
Balance the forces on the aircraft using thrust and elevator deflection. For multibody problems, the trim solver also finds the equilibrium hinge angles for the free-hinging wingtips.
Note that a static solve with free wingtips can lead to a singular stiffness matrix - the trim solver handles this by also aiming to drive the torsional hinge moments to zero, and using the wingtip hinge angles as additional trim variables. The trim_hinges argument specifies which hinge constraints to include in the trim solve.
Compared to trimming the regular HALE model where we drive the lift, drag and pitching moment to zero, we here drive the force to zero on all 6 clamped degrees of freedom at the base node. This is necessary as we no longer have a guarantee of a symmetric problem without this, as both wingtips could end up at different hinge angles, creating for example a rolling moment.
static_sol, trim_vars = hale.trim(
prescribed_dofs=tuple(range(6)),
zero_force_dofs=tuple(range(6)),
trim_cs="elevator",
thrust_nodes="thrust",
trim_orientation="y",
horseshoe=True,
trim_relaxation=0.4,
broyden_fd_step=1e-2,
trim_hinges=["left_hinge", "right_hinge"]
)
Initialise dynamic case¶
Switch from static-aircraft/dynamic-freestream to dynamic-aircraft/static-freestream for the free-flying gust encounter.
dynamic_init = hale.initialise_dynamic(static_case=static_sol, prescribed_dofs=())
Dynamic solve¶
Run the gust encounter for the free aircraft.
dynamic_sol = hale.dynamic_solve(
init_case=dynamic_init, prescribed_dofs=(), n_tstep=n_tstep
)
Post-processing¶
Extract time histories of wingtip displacement, root strains, and hinge angles.
t = jnp.arange(n_tstep) * hale.aero.dt # time vector
# root node z-displacement (rigid-body heave)
root_z = dynamic_sol.structure.x[:, 0, 2]
# root torsion/bending strains
root_eps = dynamic_sol.structure.eps[:, 0, 3:]
# hinge angles over time
left_hinge_angle = dynamic_sol.structure.constraint_data["left_hinge"]["angle"]
right_hinge_angle = dynamic_sol.structure.constraint_data["right_hinge"]["angle"]
Plots¶
Aircraft rigid-body motion¶
fig, ax = plt.subplots()
ax.plot(t, root_z, label="Root (rigid body)")
ax.set_xlabel("Time [s]")
ax.set_ylabel("Vertical position [m]")
ax.set_title("Vertical position")
ax.legend()
plt.show()
Root strains¶
fig, axes = plt.subplots(2, 1, sharex=True, figsize=(8, 8))
for i, (ax, label) in enumerate(zip(axes, ["Torsional", "Out-of-plane bending"])):
ax.plot(t, root_eps[:, i])
ax.set_title(label)
ax.set_ylabel("Strain")
axes[-1].set_xlabel("Time [s]")
fig.suptitle("Root strains")
fig.tight_layout()
plt.show()
Hinge angles¶
Shows the transient wingtip hinge angles over time. Note that the right hinge angle is negated to show the symmetric response of the two wingtips.
fig, ax = plt.subplots()
ax.plot(t, jnp.rad2deg(left_hinge_angle), linestyle="-.", label="Left hinge")
ax.plot(t, jnp.rad2deg(-right_hinge_angle), linestyle=":", label="-Right hinge")
ax.set_xlabel("Time [s]")
ax.set_ylabel("Hinge angle [deg]")
ax.set_title("Wingtip hinge angles")
ax.legend()
plt.show()
VTK output¶
Optionally write Paraview-compatible VTK files for 3-D visualisation.
# out_paths = dynamic_sol.plot("./simple_hale_hinged/")