In this example we discuss the adaptive solution of the 2D advection-diffusion problem
Two-dimensional advection-diffusion problem in a rectangular domain Solve
in the rectangular domain , with Dirichlet boundary conditions
where the Peclet number, the boundary values, , the source function
and the components of the "wind" are given.
|
We choose the forcing function and the boundary conditions such that
is the exact solution of the problem. For large values of the exact solution approaches a step, oriented at an angle against the axis.
In the computations we will impose the "wind"
illustrated in this vector plot:
Plot of the wind.
The graph below shows a plot of the solution, computed at various levels of mesh adaptation, for and a Peclet number of
Plot of the forced solution at different levels of mesh refinement.
More interesting is the following plot which shows the solution for the same parameter values and boundary conditions, but for a zero forcing function,
Plot of the unforced solution at different levels of mesh refinement.
The plot nicely illustrates the physical effects represented by the (unforced) advection diffusion equation. If represents the concentration of a chemical that is advected by the velocity field , while being dispersed by molecular diffusion, the advection-diffusion equation describes the steady-state concentration of this chemical. In this context the Peclet number is a measure of the relative importance of advective and diffusive effects. For very small Peclet number, the concentration is determined predominantly by diffusive effects – as , the advection diffusion equation approaches the Poisson equation. Conversely, at large values of the Peclet number, the concentration is determined predominantly by advective effects. The chemical is "swept along" by the flow and diffusive effects are only important in thin "boundary" or "shear" layers in which the concentration varies over short lengthscales. These can be seen clearly in the most finely resolved solution above.
The driver code
Overall, the structure of the driver code is very similar to that in the corresponding Poisson example. The only difference is that we have to specify function pointers to the source and the "wind" functions, which are passed to the problem constructor. We create the problem, perform a self-test, set the global parameters that affect the solution and solve the problem using oomph-lib's
"black-box" adaptive Newton solver.
{
cout << "\n\n\nProblem self-test ";
if (problem.self_test()==0)
{
cout << "passed: Problem can be solved." << std::endl;
}
else
{
throw OomphLibError("Self test failed",
OOMPH_CURRENT_FUNCTION,
OOMPH_EXCEPTION_LOCATION);
}
problem.newton_solve(4);
problem.doc_solution();
}
/////////////////////////////////////////////////////////////////// /////////////////////////////////...
double TanPhi
Parameter for angle of step.
double Alpha
Parameter for steepness of step.
void source_function(const Vector< double > &x_vect, double &source)
Source function required to make the solution above an exact solution.
void wind_function(const Vector< double > &x, Vector< double > &wind)
Wind.
int main()
Driver code for 2D AdvectionDiffusion problem.
Global parameters and functions
The specification of the source function and the exact solution in the namespace TanhSolnForAdvectionDiffusion
is similar to that for the Poisson examples. The only difference is the inclusion of the Peclet number and the "wind" function.
{
void get_exact_u(
const Vector<double>& x, Vector<double>& u)
{
}
{
}
{
double x=x_vect[0];
double y=x_vect[1];
source =
}
void wind_function(
const Vector<double>& x, Vector<double>& wind)
{
wind[0]=sin(6.0*x[1]);
wind[1]=cos(6.0*x[0]);
}
}
Namespace for exact solution for AdvectionDiffusion equation with "sharp" step.
void get_exact_u(const Vector< double > &x, Vector< double > &u)
Exact solution as a Vector.
double Peclet
Peclet number.
The problem class
The problem class is very similar to those used in the corresponding Poisson examples. The only change is that we use the function Problem::actions_before_adapt()
to document the progress of the automatic spatial adaptation. For this purpose, we store a DocInfo
as private member data in the Problem
. This allows us to increment the counter that labels the output files, accessible from DocInfo::number()
, whenever a new solution has been documented.
template<class ELEMENT>
{
public:
AdvectionDiffusionEquations<2>::AdvectionDiffusionSourceFctPt source_fct_pt,
AdvectionDiffusionEquations<2>::AdvectionDiffusionWindFctPt wind_fct_pt);
{
}
RefineableRectangularQuadMesh<ELEMENT>*
mesh_pt()
{
return dynamic_cast<RefineableRectangularQuadMesh<ELEMENT>*>(
Problem::mesh_pt());
}
private:
AdvectionDiffusionEquations<2>::AdvectionDiffusionSourceFctPt
Source_fct_pt;
AdvectionDiffusionEquations<2>::AdvectionDiffusionWindFctPt
Wind_fct_pt;
};
void actions_before_adapt()
Actions before adapt: Document the solution.
DocInfo Doc_info
DocInfo object.
RefineableAdvectionDiffusionProblem(AdvectionDiffusionEquations< 2 >::AdvectionDiffusionSourceFctPt source_fct_pt, AdvectionDiffusionEquations< 2 >::AdvectionDiffusionWindFctPt wind_fct_pt)
Constructor: Pass pointer to source and wind functions.
void doc_solution()
Doc the solution.
void actions_before_newton_solve()
Update the problem specs before solve: Reset boundary conditions to the values from the tanh solution...
AdvectionDiffusionEquations< 2 >::AdvectionDiffusionSourceFctPt Source_fct_pt
Pointer to source function.
void actions_after_newton_solve()
Update the problem after solve (empty)
AdvectionDiffusionEquations< 2 >::AdvectionDiffusionWindFctPt Wind_fct_pt
Pointer to wind function.
RefineableRectangularQuadMesh< ELEMENT > * mesh_pt()
Overloaded version of the problem's access function to the mesh. Recasts the pointer to the base Mesh...
~RefineableAdvectionDiffusionProblem()
Destructor. Empty.
The Problem constructor
The constructor is practically identical to the constructors used in the various Poisson examples. We specify the output directory in the Problem's
DocInfo
object, create the mesh and an error estimator, and apply the boundary conditions by pinning the nodal values on the Dirichlet boundaries.
template<class ELEMENT>
AdvectionDiffusionEquations<2>::AdvectionDiffusionSourceFctPt source_fct_pt,
AdvectionDiffusionEquations<2>::AdvectionDiffusionWindFctPt wind_fct_pt)
: Source_fct_pt(source_fct_pt), Wind_fct_pt(wind_fct_pt)
{
Doc_info.set_directory("RESLT");
unsigned n_x=4;
unsigned n_y=4;
double l_x=1.0;
double l_y=2.0;
Problem::mesh_pt() =
new RefineableRectangularQuadMesh<ELEMENT>(n_x,n_y,l_x,l_y);
mesh_pt()->spatial_error_estimator_pt()=new Z2ErrorEstimator;
unsigned num_bound = mesh_pt()->nboundary();
for(unsigned ibound=0;ibound<num_bound;ibound++)
{
unsigned num_nod= mesh_pt()->nboundary_node(ibound);
for (unsigned inod=0;inod<num_nod;inod++)
{
mesh_pt()->boundary_node_pt(ibound,inod)->pin(0);
}
}
We complete the problem setup by passing the function pointers to the source and wind functions, and the pointer to the Peclet number to the elements. Finally, we set up the equation numbering scheme.
unsigned n_element = mesh_pt()->nelement();
for(unsigned i=0;i<n_element;i++)
{
ELEMENT *el_pt = dynamic_cast<ELEMENT*>(mesh_pt()->element_pt(i));
el_pt->source_fct_pt() = Source_fct_pt;
el_pt->wind_fct_pt() = Wind_fct_pt;
}
cout <<"Number of equations: " << assign_eqn_numbers() << std::endl;
}
Actions before solve
As before, we use the Problem::actions_before_newton_solve()
function to set/update the boundary conditions.
template<class ELEMENT>
{
unsigned num_bound = mesh_pt()->nboundary();
for(unsigned ibound=0;ibound<num_bound;ibound++)
{
unsigned num_nod=mesh_pt()->nboundary_node(ibound);
for (unsigned inod=0;inod<num_nod;inod++)
{
Node* nod_pt=mesh_pt()->boundary_node_pt(ibound,inod);
Vector<double> x(2);
x[0]=nod_pt->x(0);
x[1]=nod_pt->x(1);
Vector<double> u(1);
nod_pt->set_value(0,u[0]);
}
}
}
Post-processing
The function doc_solution(...)
is identical to that in the Poisson example. We output the solution, the exact solution and the error.
template<class ELEMENT>
{
ofstream some_file;
char filename[100];
unsigned npts=5;
sprintf(filename,"%s/soln%i.dat",Doc_info.directory().c_str(),
Doc_info.number());
some_file.open(filename);
mesh_pt()->output(some_file,npts);
some_file.close();
sprintf(filename,"%s/exact_soln%i.dat",Doc_info.directory().c_str(),
Doc_info.number());
some_file.open(filename);
some_file.close();
double error,norm;
sprintf(filename,"%s/error%i.dat",Doc_info.directory().c_str(),
Doc_info.number());
some_file.open(filename);
error,norm);
some_file.close();
cout << "\nNorm of error : " << sqrt(error) << std::endl;
cout << "Norm of solution: " << sqrt(norm) << std::endl << std::endl;
}
Comments and Exercises
- Explore the change in the character of the solution of the unforced problem when the Peclet number is slowly increased from 0 to 200, say. Note how at small Peclet number, strong diffusive effects smooth out the rapid spatial variations imposed by the boundary conditions. Conversely, at large values of the Peclet number, the behaviour is dominated by advective effects. As a result, in regions where the "wind" is directed into the domain, the value of set by the Dirichlet boundary conditions is "swept" into the domain. In regions where the "wind" is directed out of the domain, the value of "swept along" by the flow in the interior "clashes" with the value prescribed by the boundary conditions and the solution adjusts itself over a very short length scale, leading to the development of thin "boundary layers".
- Explore the character of the solution on coarse meshes at large and small Peclet numbers. Note how at large Peclet numbers the solution on the coarse meshes displays strong "wiggles" throughout the domain. These only disappear once the mesh adaptation fully resolves the regions of rapid variation. We will explore this issue further in another example.
Source files for this tutorial
PDF file
A pdf version of this document is available.