The solid mechanics problem is exactly the same as that described in the unstructured solid mechanics with adaptivity tutorial. The fluid mechanics problem is new, but is a simple extension of the previous problems. The key realisation is that the unstructured refinement can take place independently for the fluid and solid domains provided that the common boundary between them has a common parametrisation. Moreover, the common boundary must have the same parametrisation in order for the fluid-structure interaction to be set up in the first place. Thus, setting up unstructured adaptivity for fsi problems is no more difficult than setting up the original unstructured problem using oomph-lib'sinline unstructured mesh generation procedures.
The problem
The figure below shows a sketch of the problem. A 2D channel is partly obstructed by an elastic bar and has an imposed parabolic inlet velocity profile.
Sketch of the problem showing fluid and solid boundary
The non-dimension formulation is the same as described in the related non-adaptive problem. The fluid structure interaction parameter is the ratio of viscous fluid stress to the reference stress (Young's modulus) of the solid.
Results
The figure below shows streamlines and pressure contours for the steady solution when and
The flow field (streamlines and pressure contours) and the deformation of the elastic obstacle.
For simplicity the common boundaries between the fluid and solid mesh are assigned the same boundary ids, but this is not necessary. The boundary ids for each domain are shown in the sketch above.
Problem Parameters
The various problem parameters are defined in a global namespace. We define the Reynolds number, , and the FSI interaction parameter .
We specify the Poisson ratio of the solid and provide a pointer to the constitutive equation for the solid.
/// Poisson's ratio
double Nu=0.3;
/// Pointer to constitutive law
ConstitutiveLaw* Constitutive_law_pt=0;
The Poisson's ratio and pointer to a constitutive law for the mesh deformation is specified separately.
/// Mesh poisson ratio
double Mesh_Nu = 0.1;
/// Pointer to constitutive law for the mesh
ConstitutiveLaw* Mesh_constitutive_law_pt=0;
} //end namespace
The driver code
We set an output directory, trace file and instantiate the constitutive laws for the real and mesh solid mechanics computations with the appropriate Poisson ratios:
Initially and , so the solid should remain undeformed and the fluid problem is linear. We expect to obtain the solution in one Newton iteration and so we perform one steady solve with the default mesh and output the result. We also output the strain energy of the solid and dissipation of the fluid as global measures of the solution that can be used for validation. (The unstructured meshes generated are not guaranteed to be exactly the same on different computers.)
// Solve the problem
problem.newton_solve();
//Output solution
problem.doc_solution(doc_info);
doc_info.number()++;
//Calculate the strain energy of the solid and dissipation in the
//fluid as global output measures of the solution for validation purposes
problem.output_strain_and_dissipation(trace);
Finally, we perform a parameter study by increasing , computing the result with one round of adaptivity and then writing the results to output files.
//Calculate the strain energy of the solid and dissipation in the
//fluid as global output measures of the solution for validation purposes
problem.output_strain_and_dissipation(trace);
}
The Problem class
The Problem class has a constructor, destructor and a post-processing member function. The class also includes the standard member functions actions_before_adapt() and actions_after_adapt(). There are private member functions that create and destroy the required FSISolidTractionElements that apply the load from the fluid on the solid and the ImposeDisplacementByLagrangeMultiplierElements that are used to (weakly) align the boundary of the fluid mesh with the solid domain. There are also private member functions to compute the fluid dissipation, solid strain energy and a public member function that outputs the computed strain and dissipation to a specified trace file.
The class provided storage for pointers to the Solid Mesh, the Fluid Mesh and vectors of pointers to meshes of FaceElements on the boundaries over which the interaction takes place. There is also storage for the GeomObject incarnations of fsi boundaries of the solid mesh and polygonal representations of the boundaries of the fluid and solid meshes.
The Problem constructor
We start by building the solid mesh, an associated error estimator and then writing the boundaries and mesh to output files. These steps are exactly the same as in the unstructured adaptive solid mechanics tutorial.
We then apply boundary conditions to the fluid mesh by pinning velocity everywhere apart from at the outflow (boundary 4) and pinning all nodal positions on all boundaries that are not in contact with the solid.
// Set the boundary conditions for fluid problem: All nodes are
// free by default
// --- just pin the ones that have Dirichlet conditions here.
//Pin velocity everywhere apart from parallel outflow (boundary 4)
We then build three meshes of traction elements corresponding to the solid boundaries 0,1 and 2 that bound the fluid
// Make traction mesh
//(This must be done first because the resulting meshes are used
// as the geometric objects that set the boundary locations of the fluid
// mesh, as enforced by the Lagrange multipliers)
Traction_mesh_pt.resize(3);
for(unsigned m=0;m<3;m++) {Traction_mesh_pt[m] = new SolidMesh;}
this->create_fsi_traction_elements();
and three analogous meshes of Lagrange multiplier elements.
//Make the Lagrange multiplier mesh
Lagrange_multiplier_mesh_pt.resize(3);
Solid_fsi_boundary_pt.resize(3);
for(unsigned m=0;m<3;m++) {Lagrange_multiplier_mesh_pt[m] = new SolidMesh;}
this->create_lagrange_multiplier_elements();
The order matters because the Lagrange multiplier elements need pointers to the GeomObject incarnation of the FSITractionElements. Thus the traction elements must be created first.
We then combine all the sub meshes into a global mesh.
// Add sub meshes
add_sub_mesh(Fluid_mesh_pt);
add_sub_mesh(Solid_mesh_pt);
for(unsigned m=0;m<3;m++)
{
add_sub_mesh(Traction_mesh_pt[m]);
add_sub_mesh(Lagrange_multiplier_mesh_pt[m]);
}
// Build global mesh
build_global_mesh();
Finally, we setup the fluid-structure interaction for all three boundaries 0, 1 and 2 and then assign the equation numbers.
// Setup FSI
//----------
// Work out which fluid dofs affect the residuals of the wall elements:
// We pass the boundary between the fluid and solid meshes and
// pointers to the meshes. The interaction boundary are boundaries 0, 1 and 2
cout <<"Number of equations: " << assign_eqn_numbers() << std::endl;
} //end constructor
Actions before adaptation
Before any adaptation takes place all surface meshes are deleted and the global mesh is rebuilt.
/// Actions before adapt
void actions_before_adapt()
{
//Delete the boundary meshes
this->delete_lagrange_multiplier_elements();
this->delete_fsi_traction_elements();
//Rebuild the global mesh
this->rebuild_global_mesh();
}
Actions after adaptation
The adaptation is performed separately on the fluid and solid meshes and the order does not matter. In fact, the first mesh to be refined will be the first that is added as a sub mesh (in this case the fluid mesh). After adaptation of all meshes, we first reset the Lagrangian coordinates of the Fluid mesh to ensure that the mesh deformation is as robust as possible.
/// Actions after adapt
void actions_after_adapt()
{
//Ensure that the lagrangian coordinates of the mesh are set to be
We then create the traction and Lagrange multiplier elements and rebuild the global mesh. Again the traction elements must be created first because they are used by the Lagrange multiplier elements.
//Recreate the boundary elements
this->create_fsi_traction_elements();
this->create_lagrange_multiplier_elements();
//Rebuild the global mesh
this->rebuild_global_mesh();
Finally, we setup the FSI on the three boundaries that are in common between the fluid and the solid.
// Setup FSI (again)
//------------------
// Work out which fluid dofs affect the residuals of the wall elements:
// We pass the boundary between the fluid and solid meshes and
// pointers to the meshes. The interaction boundary are boundaries 0, 1 and 2
Creating and destroying the FSI traction and Lagrange multiplier elements
These functions are exactly the same (apart from the obvious changes in boundary id) as those described in the non-adaptive unstructured fsi tutorial. and are not repeated here.
Post-processing
The post-processing routine simply executes the output functions for the fluid and solid meshes and writes the results into separate files. Again this is exactly the same as in the non-adaptive case.
Comments and Exercises
The majority of comments in the non-adaptive unstructured FSI tutorial also apply here. As mentioned above, the reason why the methodology works so straightforwardly is because the parametrisation of common boundaries must be the same in the fluid and solid meshes. If not, setting up the fluid-structure interaction will not work even before any adaptation takes place. Thus, provided that your unstructured FSI problem has been correctly set up in the case without adaptivity, adding adaptivity is completely straightforward.
Exercises
Confirm that the order in which the sub-meshes are added does not affect the results.
Investigate the behaviour of the system under increasing Reynolds number.
Compare the results of the present (two-d elastic) problem to that of the (one-d) beam immersed within a channel. Do the results agree as the thickness of the two-d elastic bar decreases?
Modify your driver to perform unsteady runs and again compare your results to the one-dimensional beam code.
Source files for this tutorial
The source files for this tutorial are located in the directory: