Trimmed X-HALE - Free-Flying Gust Response¶
This notebook trims the X-HALE aircraft, then releases it as a free-flying body through a 1-minus-cosine gust. We here use a half-model of the aircraft, with symmetry about the fuselage centreline.
This case takes approximately 4 minutes to trim, and 3 minutes to sovle 2800 timesteps, on an M2 Macbook Air. Note that we disable timestep console prints as this causes issues with Jupyter notebook output buffering - to see progress, convert this to a regular Python script and run without setting the verbosity to silent.
Imports¶
import matplotlib.pyplot as plt
from jax import numpy as jnp
from flapjax.aero.flowfields import OneMinusCosineFlowField
from flapjax.models.xhale.xhale import generate_xhale
from flapjax.utils.print_utils import set_verbosity
set_verbosity("silent")
Case parameters¶
Define the free-stream conditions, gust properties, and simulation time.
u_inf_mag: float = 14.0 # X-HALE reference free-stream speed
gust_intensity: float = 0.15 # gust amplitude as a fraction of u_inf
gust_length: float = 15.0 * 0.2 # 15 main-wing chords
physical_time: float = 5.0
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 X-HALE aircraft¶
hale = generate_xhale(half_model=True, flowfield=flowfield)
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 deflection the four elevators at a shared angle. These are chosen to the resultant body forces to zero. Thrust is tied together across all pods (thrust_reference holds only the surviving half-model nodes).
When we solve for thrust, we prescribe one value among all engines. This means that for a symmetric model, the thrust at the centre pod and outer pod are identical, which is not the same as the full model, where the central thrust bis shared between the two halves. The trim solution is therefore not exactly the same as the full model, but is close enough for this demonstration.
static_sol, trim_vars = hale.trim(
prescribed_dofs=tuple(range(6)),
zero_force_dofs=(0, 2, 4), # balance drag, lift, and pitching moment
trim_cs="elevator",
thrust_nodes=[list(hale.structure.thrust_reference.keys())],
trim_orientation="y",
horseshoe=True,
trim_relaxation=0.5,
)
Initialise dynamic case¶
Switch from static-aircraft/dynamic-freestream to dynamic-aircraft/static-freestream for the free-flying gust encounter.
We here must prescribe degrees of freedom that give non-symmetric motion at the root node (tranlation in y, and rotation in x and z).
dynamic_init = hale.initialise_dynamic(static_case=static_sol, prescribed_dofs=(1, 3, 5))
Dynamic solve¶
Run the gust encounter for the free aircraft, with a full (non-horseshoe) wake.
dynamic_sol = hale.dynamic_solve(
init_case=dynamic_init, prescribed_dofs=(1, 3, 5), n_tstep=n_tstep
)
Post-processing¶
Extract time histories of the fuselage rigid-body motion and right-wing-root strains.
t = jnp.arange(n_tstep) * hale.aero.dt # time vector
# fuselage node z-displacement (rigid-body heave)
root_z = dynamic_sol.structure.x[:, 0, 2]
# right-wing-root torsion/bending strains
root_eps = dynamic_sol.structure.eps[:, 0, 3:]
Plots¶
Aircraft rigid-body motion¶
fig, ax = plt.subplots()
ax.plot(t, root_z, label="Fuselage (rigid body)")
ax.set_xlabel("Time [s]")
ax.set_ylabel("Vertical position [m]")
ax.set_title("Vertical position")
ax.legend()
plt.show()
Wing-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("Wing root strains")
fig.tight_layout()
plt.show()
VTK output¶
Write Paraview-compatible VTK files for 3D visualisation. Write every 20th timestep to reduce file size.
out_paths = dynamic_sol.plot("./xhale_output/", index=slice(0, None, 20))