In an earlier example, we demonstrated how the MacroElement/Domain
- based node-update procedure that we originally developed for problems with moving, curvilinear domain boundaries may also be used in fluid-structure interaction problems in which the position of the domain boundary has to be determined as part of the solution. We demonstrated that the driver code for the coupled multi-physics problem was a straightforward combination of the driver codes for the two constituent single-physics problems. The two key steps required to couple the two single-physics codes were:
GeomObject
, using the MeshAsGeomObject
class. This class turns an existing solid mechanics mesh into a "compound" GeomObject
in which material points on the GeomObject's
intrinsic coordinate, . GeomObject
to represent the moving boundary of the fluid mesh. "Upgrade" the fluid elements (of type FLUID_ELEMENT
, say) to the "wrapped" version MacroElementNodeUpdateElement<FLUID_ELEMENT>
to allow the the node-update to be performed node-by-node, and to automatically evaluate the "shape derivatives" – the derivatives of the fluid equations with respect to the (solid mechanics) degrees of freedom that determine their nodal positions. While the implementation of these steps is very straightforward, we pointed out that the resulting code was not particularly efficient as the fluid-node update is not as sparse as it could (should!) be: Since it is impossible to distinguish between the various sub-objects in the "compound" GeomObject
, we can do no better than to assume the worst-case scenario, namely that the positional degrees of freedom of all SolidNodes
in the wall mesh potentially affect the nodal position in all fluid elements. This dramatically increases the size of the elemental Jacobian matrices, and creates many nonzero entries in the off-diagonal blocks in the global Jacobian matrix.
To avoid this problem we need a node-update strategy in which the position of each fluid node is determined by only a small number of solid mechanics degrees of freedom. The algebraic node-update strategy discussed in the non-FSI version of the collapsible channel problem, provides an ideal framework for this, as it allows each node to update its own position, using a node-specific update function. Recall that in the AlgebraicMesh
- version of the CollapsibleChannelMesh
, each AlgebraicNode
stored a pointer to the (single) GeomObject
that represented the moving curvilinear boundary, and the Lagrangian coordinate of a reference point on this GeomObject
. The node's node-update function then placed the node at a fixed vertical fraction on the line connecting the reference point on the "elastic" wall to a second reference point on the fixed lower channel wall. Furthermore, the "wrapped" element, AlgebraicElement<FLUID_ELEMENT>
, automatically computes the "shape derivatives" by finite-differencing the fluid residuals with respect to the degrees of freedom stored in the GeomObject's
geometric Data
, just as in the case of the MacroElement
- based node-update procedure.
If used in the form discussed in the earlier example, this methodology does not (yet!) improve the sparsity of the node update: The geometric Data
of the "compound" GeomObject
that represents the wall still contains the positional degrees of freedom of all of the mesh's constituent FSIHermiteBeamElements
. This is wasteful because the position of a material point on the (discretised) wall depends only on the positional degrees of freedom of the element that this point is located in. The (costly-to-compute) derivatives with respect to all other solid mechanics degree of freedom are zero. We will therefore modify the node-update procedure as follows: Each AlgebraicNode
stores a pointer to the FSIHermiteBeamElement
that its reference point is located in. This is possible because FSIHermiteBeamElements
are derived from the FiniteElement
class which, in turn, is derived from the GeomObject
class. In other words, the sub-objects of the compound MeshAsGeomObject
are GeomObjects
themselves. Their shape is parametrised by the FSIHermiteBeamElement's
local coordinate, , which acts as the (sub-)GeomObject's
intrinsic coordinate, .
Given a pointer to a compound GeomObject
, geom_obj_pt
, say, and the intrinsic coordinate = zeta_compound
of a point in that GeomObject
, the function GeomObject::locate_zeta(...)
may be used to determine a pointer, sub_obj_pt
, to the sub-object that this point is located in, and the vector of intrinsic coordinates zeta_sub_obj
of the point in this sub-object. This procedure is illustrated in this code fragment:
Here is an illustration of the relation between the various coordinates and GeomObjects:
We note that the GeomObject
base class provides a default implementation for the GeomObject::locate_zeta(...)
function as a virtual member function which returns the GeomObject's
"this"
pointer and sets . Unless the function is overloaded in a specific derived class, the GeomObject
therefore acts as its own sub-object. This is a sensible default as it ensures that (geom_obj_pt
, ) and (sub_geom_obj_pt
, ) always identify the same point, regardless of whether nor not the
GeomObject
pointed to by geom_obj_pt
is a "compound" GeomObject
.
The implementation of the sparse node-update strategy requires only a few minor modifications to the AlgebraicCollapsibleChannelMesh
, first discussed in the non-FSI example.
We construct the mesh by multiple inheritance, combining the already existing CollapsibleChannelMesh
with the AlgebraicMesh
base class:
The constructor calls the constructor of the underlying CollapsibleChannelMesh
and then calls the private member function setup_algebraic_node_update()
to initialise the data for the algebraic node update procedures. (The initialisation is implemented in a separate function so it can be called from additional mesh constructors that are not discussed here.) The destructor remains empty.
The function algebraic_node_update(...)
is defined as a pure virtual function in the AlgebraicMesh
base class and therefore must be implemented, whereas the virtual function update_node_update(...)
is only required for refineable meshes and can remain empty.
The setup of the algebraic node update is very similar to that used in the non-FSI example discussed earlier. The main difference between the two versions of the mesh is that we use the function GeomObject::locate_zeta(...)
to determine the sub-GeomObject
within which the reference point on the wall is located. As discussed above, the default implementation of this function in the GeomObject
base class ensures that the mesh can be used with compound and non-compound GeomObjects
.
We start by determining the x and y-coordinates of the nodes and decide if they are located in the collapsible part of the mesh. (The positions of nodes that are located in the rigid upstream and downstream channel segments do not have to be updated; for such nodes we skip the assignment of the node-update data. See the discussion in the non-FSI example for details.)
Assuming that the wall is in its undeformed position (we'll check this in a second...), we determine the intrinsic coordinate of the reference point on the upper wall (taking the offset between and into account: The left end of the elastic wall is located at and at ), and identify the sub - GeomObject
within which the reference point is located.
Just to be on the safe side, we double check that the wall is still in its undeformed position:
Now we can create the node update data for the present AlgebraicNode
. The node update function involves a single GeomObject:
The (sub-)GeomObject
within which the reference point on the upper wall is located.
As in the mesh used in the non-FSI example we store the x-coordinate of the reference point on the lower wall, the fractional height of the node, and its intrinsic coordinate in the (sub-)GeomObject
on the upper wall. We also store the intrinsic coordinate of the reference point in the compound GeomObject
(i.e. the Lagrangian coordinate of the reference point in the continuous beam). This will turn out to be useful in the refineable version of this mesh, to be discussed in the next example.
Finally, we create the node update information by passing the pointer to the mesh, the pointer to the GeomObject
and the reference values to the AlgebraicNode
.
Since oomph-lib's
various node update procedures use the same interfaces, changing the node update strategy from the Domain/MacroElement
- based procedure, discussed in the previous example, to the procedure implemented in the AlgebraicCollapsibleChannelMesh
, only requires minimal changes to the driver code. In fact, the changes are so trivial, that both versions are implemented in the same driver code, fsi_collapsible_channel.cc, using compiler flags to switch from one version to the other. If the code is compiled with the flag -DMACRO_ELEMENT_NODE_UPDATE
the Domain/MacroElement
- based node-update procedure, implemented in the MacroElementNodeUpdateCollapsibleChannelMesh
is used, otherwise the code uses the AlgebraicCollapsibleChannelMesh
, discussed above. Here is one of the few portions of the code where the distinction between the two versions is required: The access function to the "bulk" (fluid) mesh in the problem class.
Incidentally, the driver code also uses compiler flags to switch between Crouzeix-Raviart and Taylor-Hood elements for the discretisation of the Navier-Stokes equations. By default, Crouzeix-Raviart elements are used; Taylor-Hood elements are used if the code is compiled with with the flag -DTAYLOR_HOOD
.
The animations shown below illustrate the interaction between fluid and solid mechanics degrees of freedom in the computations with the algebraic node update. Comparison with the corresponding animations for the Domain/MacroElement
- based procedures, shown in the earlier example demonstrates the greatly improved sparsity of the node update. With the algebraic node-update procedures, the residuals of the FSIHermiteBeamElements
now only depend on the fluid degrees of freedom in the adjacent fluid elements and on the solid mechanics degree of freedom in the FSIHermiteBeamElements
that affect the nodal position in these fluid elements.
Here is the corresponding animation for a discretisation with 2D Taylor-Hood elements. These elements have no internal Data
but the pressure degrees of freedom are stored at the fluid element's corner nodes:
Finally, here is an animation that shows the (solid mechanics) degrees of freedom that affect the node-update of a given fluid node. The red square marker shows the fluid node; the green numbers show the number of the degrees of freedom at the SolidNodes
that are involved that fluid node's node update. With the algebraic node update, the position of each fluid node is only affected by the solid mechanics degree of freedom in the FSIHermiteBeamElement
that contains its reference point.
The improved sparsity leads to a very significant speedup compared to the MacroElement/Domain
- based node update procedure.
AlgebraicCollapsibleChannelMesh
is mainly due to the improved sparsity of the node update, achieved by using the GeomObject::locate_zeta(...)
function. MyAlgebraicCollapsibleChannelMesh
in the file my_algebraic_collapsible_channel_mesh.h, developed for the non-FSI version of the collapsible channel problem, into the FSI driver code fsi_collapsible_channel.cc and use that mesh instead of the AlgebraicCollapsibleChannelMesh
, or replace the line AlgebraicCollapsibleChannelMesh<ELEMENT>::setup_algebraic_node_update()
in collapsible_channel_mesh.template.cc by A pdf version of this document is available.