Static Beam Deformation¶
A simple example of a cantilever beam with a tip load applied, obtaining the static deformation where the forcing is in equilibrium.
Imports¶
Loads in all the required packages for the example case.
from jax import numpy as jnp
from matplotlib import pyplot as plt
from flapjax.models.geradin_beam.geradin_beam import generate_geradin_beam
from flapjax.structure import BeamStructure, StructureCase
Case parameters¶
n_nodes = 20 # number of structural nodes
f_ext = jnp.zeros((n_nodes, 6)) # array to hold the external loads (3 forces and 3 moments per node)
f_ext = f_ext.at[-1, 2].set(-600000.0) # apply a tip load (node -1) in the z direction (index 2)
Create the structure and solve for the static deformation¶
The function generate_geradin_beam creates a beam structure based on the Geradin beam model (see the function body for how to construct a structural case from scratch).
The returned object is of type BeamStructure, which is an object which can fully describe the beam properties (reference coordinates, stiffness, inertia etc.). This object is agnostic of conditions, and so does not include any information about loads, boundary conditions, or deformations. The beam is here chosen to be oriented along the x-axis, but this can be changed by modifying the beam_direction argument.
struct: BeamStructure = generate_geradin_beam(n_nodes=n_nodes, beam_direction="x")
Solve for the static deformation¶
Perform a static solution on the beam structure for given applied loads.
This requires specifying the boundary conditions. This is done by providing the index of degrees of freedom that are fixed and therefore not solved for. Each node has 6 degrees of freedom (3 translational and 3 rotational), and are numbered sequentially. For example, if the second node is to be fixed, then we would constrain degrees of freedom index 6-11. For the cantilever case, we have a clamped support and therefore the first 6 degrees of freedom (0-5) are fixed.
There are 3 paths for applying loads, all of which take inputs of shape [n_nodes, 6].
f_ext_follower: follower loads that rotate with the structure. These should be passed with directionality relative to the undeformed reference configurationf_ext_dead: dead loads that do not rotate with the structure.f_ext_aero: aerodynamic loads that act on the structure. This is only used for aeroelastic simulations, where it is set by the UVLM.
For each force type, if no load is desired then the input can be set to None. The below example solves the structural problem with only dead forces applied. The solution is performed in 3 load steps, which can be used to improve convergence for nonlinear problems.
By default, the solver will print information to the console about the convergence of the solution. One row will be printed for each load step, with the following columns:
Iter: How many iterations were required to converge the solution for this load step.Conv: Boolean flag indicating whether the solution converged for this load step.Rel Disp: Relative displacement between the penultimate and final iterations.Abs Disp: Absolute displacement between the penultimate and final iterations.Rel Force: Relative force difference between the penultimate and final iterations.Abs Force: Absolute force difference between the penultimate and final iterations.Load Step: The current load step number.
For the four convergence criteria above, they are in practice arrays of values (for instance, the relative displacement between two timesteps has shape [n_nodes, 6] to account for nodal linear and rotational differences). The solver will take the maximum value of these arrays and compare them to the convergence tolerances. These tolerances have default values which are used here for simplicity, but can be modified through struct.struct_convergence_settings.
result_dead: StructureCase = struct.static_solve(
prescribed_dofs=jnp.arange(6),
f_ext_follower=None,
f_ext_dead=f_ext,
f_ext_aero=None,
load_steps=3,
)
Static deformation results¶
The static problem returns a Structure object, which contains the result of the deformed static solution. This contains the following attributes with array shapes in brackets:
result_dead.x: coordinates of nodes, [n_nodes, 3]result_dead.rmat: rotation matrix of each node relative to the undeformed referece, [n_nodes, 3, 3]result_dead.eps: strain of each element, [n_elem, 6]result_dead.f_ext_follower: external follower forces at each node, [n_nodes, 6]result_dead.f_ext_dead: external dead forces at each node, [n_nodes, 6]result_dead.f_ext_aero: external aerodynamic forces at each node, [n_nodes, 6]result_dead.f_grav: gravitational forces at each node, [n_nodes, 6]result_dead.f_int: internal forces at each node due to stiffness, [n_nodes, 6]result_dead.f_elem: internal forces in each element due to stiffness, [n_elem, 6]result_dead.f_res: residual forces at each node, [n_nodes, 6]
Note that the forces are here expressed in the local frame of reference, as this is where they are resolved during the solve process. If they are desired in the global frame of reference, the methods Structure.to_global() and Structure.to_local() can be used to convert the forces between reference frames.
We wish to plot the deformation of the cantilever. As we also plot the dead tip load, we start by converting the forces to the global frame of reference.
result_dead.to_global() # transform forces
assert result_dead.f_ext_dead is not None # ensure that there are output dead forces, which we know to be true
fig, ax = plt.subplots()
ax.plot(result_dead.x[:, 0], result_dead.x[:, 2], linewidth=3) # plot deformation of the beam
ax.quiver( # plot forces on beam
result_dead.x[:, 0],
result_dead.x[:, 2],
result_dead.f_ext_dead[:, 0],
result_dead.f_ext_dead[:, 2],
color="red",
scale=5e6,
)
ax.set_xlabel("x [m]")
ax.set_ylabel("y [m]")
ax.set_ylim(-3.0, 0.2)
ax.set_title("Cantilever with Tip Dead Force")
plt.show()
Equivelant follower force case¶
result_follower: StructureCase = struct.static_solve(
prescribed_dofs=jnp.arange(6),
f_ext_follower=f_ext,
f_ext_dead=None,
f_ext_aero=None,
load_steps=3,
)
result_follower.to_global()
assert result_follower.f_ext_follower is not None
fig, ax = plt.subplots()
ax.plot(result_follower.x[:, 0], result_follower.x[:, 2], linewidth=3) # plot deformation of the beam
ax.quiver( # plot forces on beam
result_follower.x[:, 0],
result_follower.x[:, 2],
result_follower.f_ext_follower[:, 0],
result_follower.f_ext_follower[:, 2],
color="red",
scale=5e6,
)
ax.set_xlabel("x [m]")
ax.set_ylabel("y [m]")
ax.set_ylim(-3.0, 0.2)
ax.set_title("Cantilever with Tip Follower Force")
plt.show()
Plot to Paraview¶
Whilst the presented cantilever beam case can be easily visualised in 2D, for more complex configurations it is desirable to have a 3D visualisation. This can be done for all cases by exporting the results to .VTU files with Structure.plot(), where these files can be opened in Paraview.
The n_interp argument specifies how many interpolated points are added between each node, which can be used to smooth the visualisation of the deformed structure.
result_follower.plot(directory="./cantilever_follower", n_interp=3)