In many previous examples we illustrated how
MacroElement objects facilitate the generation of refineable (and non-refineable) meshes in domains with moving, curvilinear boundaries. Consult, for instance:
The two main features of the
MacroElement/Domain - based node-update are that, once the curvilinear domain is represented by a
The availability of a "black-box" node-update procedure is very convenient but in some applications it may be desirable (or even necessary) to provide a customised node-update function, either because the mesh deformation generated by the "black-box" procedure is not appropriate or because it is not efficient. The latter problem arises particularly in fluid-structure interaction problems.
In this tutorial we shall demonstrate an alternative node-update technique, based on
AlgebraicMesh classes. The key feature of this approach is that it allows "each node to update its own
position". This is in contrast to the
Domain/MacroElement-based approach in which we can only update the nodal positions of all nodes in the mesh simultaneously – not a particularly sparse operation! [We note that the
FiniteElement base classes provide the virtual functions
FiniteElement::node_update(). These functions are intended to be used for node-by-node or element-by-element node-updates but in their default implementations they are empty. Hence no node-update is performed unless these functions are overloaded in derived classes such as
The idea behind the algebraic node-updates is simple: The
AlgebraicMesh class (the base class for meshes containing
AlgebraicNodes) contains the pure virtual function
which must be implemented in every specific
AlgebraicMesh. Its task is to update the position of the node specified by the pointer-valued argument.
The specific implementation of the node-update operation is obviously problem-dependent but it is easy to illustrate the general procedure by considering the collapsible channel mesh sketched below:
The upper figure shows the mesh in the undeformed domain in which wall shape is parametrised by the intrinsic (Lagrangian) coordinate as In this configuration each node is located at a certain fraction along the vertical lines across the channel. For instance, the -th node (drawn in blue) is located at a fraction of along the straight line that connects reference point on the bottom wall to reference point on the flexible upper wall. We note that reference point may be identified by its Lagrangian coordinate on the wall.
The lower figure shows a sketch of the deformed domain and illustrates a possible algebraic node-update strategy: Given the new wall shape, described by , we position each node on the straight line that connects its reference point on the lower wall to the reference point on the deformable upper wall.
To perform this node-update procedure for a specific node, we generally have to store
GeomObject(s)that define the curvilinear domain boundaries.
AlgebraicMeshthat implements the node-update procedure.
Since the node-update data is node-specific, we provide storage for it in the
AlgebraicNode class – a class that is derived from the
Node class. The node-update function itself is shared by many nodes in the mesh and is implemented in
AlgebraicMesh::algebraic_node_update(...). This function extracts the node-update parameters, , and and the pointer to the
GeomObject that parametrises the wall,
wall_pt, say, from the
AlgebraicNode passed to it. With these parameters, the position of reference point is given by while the coordinates of reference point may be obtained from a call to
wall_pt->position(...). The nodal position may then be updated via
To use the algebraic node-update capabilities of an existing
Nodes must be replaced by
AlgebraicNodes to allow the storage of the node-specific node-update parameters. Recall that within
Nodes are usually created by the elements, using the function
FiniteElement::construct_node(...) whose argument specifies the local node number of the newly created
Node within the element that creates it. (The specification of the local node number is required to allow the
FiniteElement::construct_node(...) function to create a
Node with the appropriate number of values and history values. For instance, in 2D Taylor-Hood Navier-Stokes elements, the elements' vertex nodes have to store three values, representing the two velocity components and the pressure, whereas all other nodes only require storage for the two velocity components.)
To ensure that the
FiniteElement::construct_node(...) function creates
AlgebraicNodes rather than "ordinary"
Nodes, we provide the templated wrapper class
which overloads the
FiniteElement::construct_node(...) function so that it creates
AlgebraicNodes instead. In most other respects, the "wrapped" element behaves exactly as the underlying
ELEMENT itself. To use an existing
AlgebraicMesh with a certain element type,
QTaylorHoodElement<2>, say, we simply "upgrade" the element to an
AlgebraicElement by specifying the element type as
The changes to an existing driver code that performs the node update by the default
Domain/MacroElement methodology are therefore completely trivial. You may wish to compare the driver code collapsible_channel.cc, discussed in an earlier example, to the driver code collapsible_channel_algebraic.cc in which the fluid domain is discretised by the
MyAlgebraicCollapsibleChannelMesh, discussed below.
To illustrate how to create a new
AlgebraicMesh, we will now discuss the implementation of the
MyAlgebraicCollapsibleChannelMesh – an
AlgebraicMesh - version of the
CollapsibleChannelMesh, used in the earlier example.
We construct the mesh by multiple inheritance from the
AlgebraicMesh base class, and the already-existing
CollapsibleChannelMesh. The constructor first calls the constructor of the underlying
CollapsibleChannelMesh (thus "recycling" the basic mesh generation process, i.e. the generation of nodes and elements etc.) and then adds the algebraic node-update information by calling the function
We declare the interface for the pure virtual function
algebraic_node_update(...), to be discussed below, and implement a second pure virtual function,
AlgebraicMesh::update_node_update(), that may be used to update reference values following a mesh adaptation. Since the current mesh is not refineable, we leave this function empty and refer to another example for a more detailed discussion of its role.
The protected member function
setup_algebraic_node_update() will be discussed below.
When the function
setup_algebraic_node_update() is called, the constructor of the underlying
CollapsibleChannelMesh will already have created the mesh's elements and nodes, and the nodes will be located at their initial positions in the undeformed domain.
To set up the algebraic node-update data, we start by extracting the lengths of the upstream rigid section, , and the length of the collapsible segment, , from the
Next, we loop over all
AlgebraicNodes in the mesh and determine their current positions:
j -th node is located in the "collapsible" section of the mesh we determine its reference coordinate, , on the wall and determine the coordinates of its reference point from the
GeomObject that represents the moving wall.
Just to be on the safe side, we check that the wall is actually in its undeformed configuration, as assumed.
Next, we package the data required for the node-update operations (the pointers to
GeomObject(s) and the reference values) into vectors. In the present example, the node-update operation only involves a single
We have three reference values: The - coordinate of point ,
as well as the fractional height, ,
and the reference coordinate, , along the wall:
The vectors of reference values and geometric objects are then passed to the node, together with the pointer to the mesh (
this) that implements the node-update function.
algebraic_node_update(...) reverses the setup process: It extracts the node-update data from the
AlgebraicNode and updates its position at the
t -th previous time-level:
We start by extracting the vectors of reference values and
GeomObjects involved in this node's node-update, using the functions
AlgebraicNode::vector_ref_value() which returns a Vector of reference values, and the function
AlgebraicNode::vector_geom_object_pt() which returns a Vector of pointers to
Next, we translate these into local variables,
and obtain the current wall position from the wall
Finally, we update the nodal position:
AlgebraicNode::node_update()allows each node to "update its own position". The
AlgebraicElementclasses provide their own re-implementation of the
node_update()functions in the
FiniteElementclasses, and perform the node-updates by executing the
AlgebraicNode::node_update()function of their constituent nodes.
AlgebraicNodeconstructor provides default assignments for the node-update data. In particular, the pointer to the Mesh that performs the node update is initialised by a pointer to the (static instantiation of a)
AlgebraicMesh::update_node_update(...)functions are empty. This ensures that nodes for which these default assignments are not overwritten stay at their original position when the node udate is performed. This implementation provides a sensible default behaviour for the node update.
AlgebraicNode::add_node_update_info(...)function, using its alternative interface
AlgebraicMesh::algebraic_node_update(...)function for a mesh that contains multiple node-update functions, the ID of the node-update function associated with a particular node can be obtained from
AlgebraicNode::node_update_fct_id(), allowing the node-update function to take the appropriate action. (As an example, consider the implementation of the algebraic node-update function for the
RefineableAlgebraicFishMeshin which different node-update functions are used to update the position of nodes in the fish's "body" and in its "tail".) If the
AlgebraicNode::add_node_update_info(...)function is called without specifying an ID, a default ID of 0 is assigned.
AlgebraicNodesmay be associated with multiple node-update functions, though the different node-update functions associated with a node must, of course, give the same result. This may be verified by calling the
AlgebraicNode::self_test()function for all nodes in an
AlgebraicMeshes(discussed in more detail below),
AlgebraicNodesmust be associated with all possible node-update functions to ensure that the reference values for newly created nodes are determined correctly during the adaptive mesh refinement.
AlgebraicMeshesmay be created by multiple inheritance from a suitable
RefineableMeshbase class (e.g. the
RefineableQuadMeshclass), and setting up the required tree representation of the coarse initial mesh, exactly as for "ordinary" meshes (see the tutorial "How to create simple refineable meshes" for details). As an example, here is the class definition for a refineable version of the
AlgebraicNodesare determined by interpolation from the node's "father element". Furthermore, it is assumed
GeomObjectsare involved in the node-update function for the newly created node.
AlgebraicMesh::update_node_update(...)function which is executed automatically whenever a refineable
AlgebraicNode::node_update(...)function is called for a hanging node, it first updates the position of its master nodes (using their own node-update functions) and then updates its own constrained position accordingly.
GeomObjectsthat are involved in an
AlgebraicNode'snode update, may depend on unknowns in the overall problem. Such unknowns constitute a
Datawhich may be obtained from its member function
AlgebraicElementsuse finite differencing to include the shape derivatives (i.e. the derivatives of the residuals of the underlying (wrapped) element with respect to the geometric
Datainvolved in the element's node update) into the element's Jacobian matrix.
MyAlgebraicCollapsibleChannelMeshand its refineable equivalent are slightly simplified versions of the meshes in
src/meshesdirectory. These meshes contain a number of additional sanity checks that we omitted here for the sake of brevity. Furthermore, these meshes can be used with
GeomObjectsthat comprise "sub-"
GeomObjects, a feature that is essential in problems with proper fluid-structure interaction. We will discuss this in another example.
MyAlgebraicCollapsibleChannelMesh, you will notice that the mesh has an additional constructor that allows the specification of the "boundary layer squash function" first introduced in the original
CollapsibleChannelMesh. Explain why in the
AlgebraicMeshversion of this mesh, the function pointer to the "boundary layer squash function" can only be specified via the constructor – the access function is deliberately broken.
A pdf version of this document is available.