action functions
|
Please note that the library has not been "officially" released. While we continue to work on the documentation, these web pages are likely to contain broken links and documents in draft form. Please send an email to if you wish to be informed of the library's "official" release. |
This document discusses the finite-element-based solution of the Young Laplace equation, a nonlinear PDE that determines the static equilibrium shapes of droplets or bubbles. We start by reviewing the relevant theory and then present the solution of a simple model problem.
A typical problem: The extrusion of a small droplet from a cylindrical tube.
The shape of the air-liquid interface (the meniscus) is then determined by Laplace's law which expresses the balance between the (spatially constant) pressure drop across the meniscus,
, and the surface tension forces acting at the air-liquid interface,
where
is the mean curvature and
the surface tension.
Non-dimensionalising all lengths on some problem-specific lengthscale
(e.g. the radius of the cylindrical tube) and scaling the pressure on the associated capillary scale,
, yields the non-dimensional form of the Young-Laplace equation
This must be augmented by suitable boundary conditions, to be applied at the contact line -- the line along which the meniscus meets the solid surface. We can either "pin" the position of the contact line (as in the above example), or prescribe the contact angle at which the air-liquid interface meets the solid surface.
The mathematical problem may be interpreted as follows: Given the prescribed pressure drop
(and thus
), we wish to determine an interface shape of the required mean curvature
that satisfies the boundary conditions along the contact line.
-plane as
yields the cartesian form of the Young-Laplace equation:
where
Given the imposed pressure drop across the interface, this equation must be solved for
. Note that this is only possible if the interface can be projected onto the
-plane.
which may be derived from energetic considerations. Here the symbol
denotes a variation,
is the position vector to the interface, and the vector
is the unit normal to the meniscus. The left hand side of this equation represents the variation of the interfacial energy during a virtual displacement, and
is an infinitesimal element of the meniscus surface,
. The terms on the right hand side represent the virtual work done by the pressure and the virtual work done by the surface tension forces acting at the free contact line, respectively.
is the length of an element of the contact line
, and
is the vector tangent to the interface and normal to the contact line. Note that, if the contact line is pinned, the variation of its position is zero,
, and the line integral vanishes.
The variational formulation of the problem is particularly convenient for a finite-element-based solution; see, e.g. the Solid Mechanics Theory document for a discussion of how to discretise variational principles with finite elements. The variational and PDE-based formulations are, of course, related to each other: The Young-Laplace equation is the Euler-Lagrange equation of the variational principle.
-plane, we parametrise the meniscus by two intrinsic coordinates as
, where
. Furthermore, we parametrise the domain boundary,
, by a scalar coordinate
so that,
The normal to the meniscus is then given by
where the commas denote partial differentiation with respect to the intrinsic coordinates, and
is the square root of the surface metric tensor.
The area and length differentials required in variational principle are
and
allowing us to write the principle of virtual displacements as
that parametrise the same interface shape. To remove this ambiguity, and to allow for interface shapes that cannot be projected onto the
-plane, we employ the so-called "Method of Spines". This method was originally proposed by Kistler and Scriven for the computation of free surface flows. We decompose the vector
into two parts by writing it as
Here the "spine basis"
and the "spines"
are pre-determined vector fields that must be chosen by the user. Using this decomposition, the meniscus shape is determined by the scalar function
which represents the meniscus' displacement along the spines
.
The idea is illustrated in the simple 2D sketch below: the spine basis vectors,
parametrise the straight line corresponding to a flat meniscus. Positive values of
displace the meniscus along the spines,
(the red vectors), whose orientation allows the representation of meniscus shapes that cannot be represented in cartesian form as 
Sketch illustrating the parametrisation of the meniscus by the Method of Spines.
The spine basis and the spines themselves must be chosen such that the mapping from
to
is one-to-one, at least for the meniscus shapes of interest. Pinned boundary conditions of the form
are most easily imposed by choosing the spine basis such that
implying that 
The simplest possible choice for the spines and spine basis is one that returns the problem to its original cartesian formulation. This is achieved by setting
and 
-- a flat interface. This solution was then used as the initial guess for the solution at a small positive value of
. This process was continued, allowing us to compute strongly deformed meniscus shapes. This method works well, provided small increments in the control parameter
(or equivalently,
) create small changes in the interface shape. This is not always the case, however, as we shall see in the example below. In such cases it is often possible to re-formulate the problem, using the displacement control method discussed in the solid mechanics tutorials. Rather than prescribing the pressure drop
we prescribe the displacement of a control point on the meniscus and regard the pressure drop required to achieve this displacement as an unknown. Since the implementation of the method is very similar to that used for solid mechanics problems, we shall not discuss it in detail here but refer to the appropriate solid mechanics tutorial.
. If we assume that the air-liquid interface is pinned at the edges of the slot, as shown in the sketch below, the problem has an obvious exact solution. The air-liquid interface must have constant mean curvature, so, assuming that its shape does not vary along the slot, the meniscus must be a circular cylinder. If we characterise the meniscus' shape by its vertical displacement along the centreline,
, the (dimensional) curvature of the air-liquid interface is given by
The plot of this function, shown in the right half of the figure below, may be interpreted as a "load-displacement diagram" as it shows the deflection of the meniscus as a function of the imposed non-dimensional pressure drop across the air-liquid interface, 
Sketch of the meniscus above a slot of width 2a. The figure on the right illustrates the relation between the meniscus curvature and its vertical displacement along the centreline.
The plot shows that the load-displacement curve is not single-valued. Furthermore, the presence of a limit point at
indicates that the maximum curvature of the meniscus is given by
This implies that the maximum (dimensional) pressure that the meniscus can withstand is given by
.
The presence of the limit point makes it impossible to compute the entire solution curve by simply increasing
in small increments. However, use of a displacement control approach, by prescribing
while regarding
(and thus
) as an unknown, yields a single-valued function that can be computed by a straightforward continuation method in which
is increased in small increments.
This approach was employed to compute the meniscus shapes shown in the plot below. The initial, zero-curvature configuration of the meniscus (corresponding to a vanishing pressure drop across the air-liquid interface) is the unit square. The meniscus is pinned along the lines
and
, and symmetry (natural) boundary conditions were applied along the lines
and
see Comments and Exercises for further details on the natural boundary conditions. As the pressure drop increases, the meniscus is deflected upwards until it reaches the configuration of maximum curvature when
. Beyond this point, the pressure drop has to be reduced to compute the "bulging" meniscus shapes obtained for 
Deformation of a meniscus that is pinned along the lines y=0 and y=1.
The comparison of the computed "load-displacement curve" against the exact solution, shown below, indicates that the two agree to within plotting accuracy.
Load-displacement diagram for a meniscus that is pinned along the lines y=0 and y=1.
oomph-lib's Young Laplace elements.get_exact_kappa() returns the exact solution for the mean curvature of the meniscus. We will use this function for the validation of our results.
//===== start_of_namespace======================================== /// Namespace for "global" problem parameters //================================================================ namespace GlobalParameters { // Displacement control: //---------------------- /// Height control value double Controlled_height = 0.0; /// Exact kappa double get_exact_kappa() { // Mean curvature of barrel-shaped meniscus return 2.0*Controlled_height/ (Controlled_height*Controlled_height+1.0/4.0); } //end exact kappa
Next we define the orientation of the spines. Anticipating the shape of the meniscus, we set
so that
corresponds to a flat meniscus in the
-plane,
// Spine basis //------------ /// \short Spine basis: The position vector to the basis of the spine /// as a function of the two coordinates x_1 and x_2, and its /// derivatives w.r.t. to these coordinates. /// dspine_B[i][j] = d spine_B[j] / dx_i /// Spines start in the (x_1,x_2) plane at (x_1,x_2). void spine_base_function(const Vector<double>& x, Vector<double>& spine_B, Vector< Vector<double> >& dspine_B) { // Bspines and derivatives spine_B[0] = x[0]; spine_B[1] = x[1]; spine_B[2] = 0.0 ; dspine_B[0][0] = 1.0 ; dspine_B[1][0] = 0.0 ; dspine_B[0][1] = 0.0 ; dspine_B[1][1] = 1.0 ; dspine_B[0][2] = 0.0 ; dspine_B[1][2] = 0.0 ; } // End of bspine functions
and rotate the spines in the
direction by setting
where
. With this choice, the spines are unit vectors that form an angle
(varying between
and
) with the y-axis.
// Spines rotate in the y-direction //--------------------------------- /// Min. spine angle against horizontal plane double Alpha_min = MathematicalConstants::Pi/2.0*1.5; /// Max. spine angle against horizontal plane double Alpha_max = MathematicalConstants::Pi/2.0*0.5; /// \short Spine: The spine vector field as a function of the two /// coordinates x_1 and x_2, and its derivatives w.r.t. to these coordinates: /// dspine[i][j] = d spine[j] / dx_i void spine_function(const Vector<double>& x, Vector<double>& spine, Vector< Vector<double> >& dspine) { /// Spines (and derivatives) are independent of x[0] and rotate /// in the x[1]-direction spine[0]=0.0; dspine[0][0]=0.0; dspine[1][0]=0.0; spine[1]=cos(Alpha_min+(Alpha_max-Alpha_min)*x[1]); dspine[0][1]=0.0; dspine[1][1]=-sin(Alpha_min+(Alpha_max-Alpha_min)*x[1]) *(Alpha_max-Alpha_min); spine[2]=sin(Alpha_min+(Alpha_max-Alpha_min)*x[1]); dspine[0][2]=0.0; dspine[1][2]=cos(Alpha_min+(Alpha_max-Alpha_min)*x[1]) *(Alpha_max-Alpha_min); } // End spine function
.
//===================start_of_main======================================== /// Driver code //======================================================================== int main() { // Create label for output DocInfo doc_info; // Set output directory doc_info.set_directory("RESLT"); //Open a trace file ofstream trace_file; char filename[100]; sprintf(filename,"%s/trace.dat",doc_info.directory().c_str()); trace_file.open(filename); // Write kappa, exact kappa and height values trace_file << "VARIABLES=\"<GREEK>k</GREEK>\",\"<GREEK>k</GREEK>_{ex}\",\"h\"" << std::endl; trace_file << "ZONE" << std::endl;
Next we build the problem object and document the initial configuration: a flat meniscus.
// Create the problem //------------------- // Create the problem with 2D nine-node elements from the // QYoungLaplaceElement family. YoungLaplaceProblem<QYoungLaplaceElement<3> > problem; //Output the solution problem.doc_solution(doc_info,trace_file); //Increment counter for solutions doc_info.number()++;
Finally, we perform a parameter study by increasing the control displacement in small increments and re-computing the meniscus shapes and the associated interface curvatures.
// Parameter incrementation //------------------------- double increment=0.1; // Loop over steps unsigned nstep=2; // 10; for (unsigned istep=0;istep<nstep;istep++) { // Increment prescribed height value GlobalParameters::Controlled_height+=increment; // Solve the problem problem.newton_solve(); //Output the solution problem.doc_solution(doc_info,trace_file); //Increment counter for solutions doc_info.number()++; } // Close output file trace_file.close(); } // end of main
actions_before_newton_solve() as they are irrelevant in the current context. They are discussed in one of the exercises in Comments and Exercises.) The problem's private member data include a pointer to the node at which the meniscus displacement is controlled by the displacement control element, and a pointer to the Data object whose one-and-only value contains the unknown interface curvature, 
//====== start_of_problem_class======================================= /// 2D YoungLaplace problem on rectangular domain, discretised with /// 2D QYoungLaplace elements. The specific type of element is /// specified via the template parameter. //==================================================================== template<class ELEMENT> class YoungLaplaceProblem : public Problem { public: /// Constructor: YoungLaplaceProblem(); /// Destructor (empty) ~YoungLaplaceProblem(){} /// Update the problem before solve void actions_before_newton_solve() { // This only has an effect if displacement control is disabled double new_kappa=Kappa_pt->value(0)-0.5; Kappa_pt->set_value(0,new_kappa); } /// Update the problem after solve: Empty void actions_after_newton_solve(){} /// \short Doc the solution. DocInfo object stores flags/labels for where the /// output gets written to and the trace file void doc_solution(DocInfo& doc_info, ofstream& trace_file); private: /// Node at which the height (displacement along spine) is controlled/doced Node* Control_node_pt; /// Pointer to Data object that stores the prescribed curvature Data* Kappa_pt; }; // end of problem class
with 8x8 elements.
//=====start_of_constructor=============================================== /// Constructor for YoungLaplace problem //======================================================================== template<class ELEMENT> YoungLaplaceProblem<ELEMENT>::YoungLaplaceProblem() { // Setup mesh //----------- // # of elements in x-direction unsigned n_x=8; // # of elements in y-direction unsigned n_y=8; // Domain length in x-direction double l_x=1.0; // Domain length in y-direction double l_y=1.0; // Build and assign mesh Problem::mesh_pt()=new SimpleRectangularQuadMesh<ELEMENT>(n_x,n_y,l_x,l_y);
Next, we choose the central node in the mesh as the node whose displacement is imposed by the displacement control method.
// Check that we've got an even number of elements otherwise // out counting doesn't work... if ((n_x%2!=0)||(n_y%2!=0)) { cout << "n_x n_y should be even" << endl; abort(); } // This is the element that contains the central node: ELEMENT* prescribed_height_element_pt= dynamic_cast<ELEMENT*>( mesh_pt()->element_pt(n_y*n_x/2+n_x/2)); // The central node is node 0 in that element Control_node_pt= static_cast<Node*>(prescribed_height_element_pt->node_pt(0)); std::cout << "Controlling height at (x,y) : (" << Control_node_pt->x(0) << "," << Control_node_pt->x(1) << ")" << "\n" << endl;
We pass the pointer to that node and the pointer to the double that specifies the imposed displacement to the constructor of the displacement control element. The constructor automatically creates a Data object whose one-and-only value stores the unknown curvature,
We store the pointer to this Data object in the private member data to faciliate its output.
// Create a height control element HeightControlElement* height_control_element_pt=new HeightControlElement( Control_node_pt,&GlobalParameters::Controlled_height); // Store pointer to kappa data Kappa_pt=height_control_element_pt->kappa_pt();
The meniscus is pinned along mesh boundaries 0 and 2:
// Boundary conditions //-------------------- // Set the boundary conditions for this problem: All nodes are // free by default -- only need to pin the ones that have Dirichlet conditions // here. unsigned n_bound = mesh_pt()->nboundary(); for(unsigned b=0;b<n_bound;b++) { // Pin meniscus displacement at all nodes boundaries 0 and 2 if ((b==0)||(b==2)) { unsigned n_node = mesh_pt()->nboundary_node(b); for (unsigned n=0;n<n_node;n++) { mesh_pt()->boundary_node_pt(b,n)->pin(0); } } } // end bc
We complete the build of the Young Laplace elements by passing the pointer to the spine functions, and the prescribed curvature.
// Complete build of elements //--------------------------- // Complete the build of all elements so they are fully functional unsigned nelement = mesh_pt()->nelement(); for(unsigned i=0;i<nelement;i++) { // Upcast from GeneralsedElement to YoungLaplace element ELEMENT *el_pt = dynamic_cast<ELEMENT*>(mesh_pt()->element_pt(i)); //Set the spine function pointers el_pt->spine_base_fct_pt() = GlobalParameters::spine_base_function; el_pt->spine_fct_pt() = GlobalParameters::spine_function; // Set the curvature data for the element el_pt->set_kappa(Kappa_pt); }
Finally, we add the displacement control element to the mesh and assign the equation numbers.
// Add the height control element to mesh (comment this out // if you're not using displacement control) mesh_pt()->add_element_pt(height_control_element_pt); // Setup equation numbering scheme cout <<"\nNumber of equations: " << assign_eqn_numbers() << endl; } // end of constructor
//===============start_of_doc============================================= /// Doc the solution: doc_info contains labels/output directory etc. //======================================================================== template<class ELEMENT> void YoungLaplaceProblem<ELEMENT>::doc_solution(DocInfo& doc_info, ofstream& trace_file) { // Output kappa vs height of the apex //------------------------------------ trace_file << -1.0*Kappa_pt->value(0) << " "; trace_file << GlobalParameters::get_exact_kappa() << " "; trace_file << Control_node_pt->value(0) ; trace_file << endl; // Number of plot points: npts x npts unsigned npts=5; // Output full solution //--------------------- ofstream some_file; char filename[100]; 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(); } // end of doc
to
is one-to-one, at least for the meniscus shapes of interest. This requires some prior knowledge of the expected interface shapes.
and
No constraints were applied along the two other domain boundaries (at
and
), indicating that these boundaries are controlled by implied, "natural" boundary conditions. The variational principle (1) shows what these are: since we neglected the boundary integral on the right hand side of equation (1), the meniscus shape must satisfy
implying that outer unit normal to the meniscus boundary,
must be orthogonal to the direction of the spines. Since the spines do not have an
- component, the meniscus must therefore have zero slope in that direction -- just what we need for our problem.
-direction, e.g. by changing their definition to
The natural boundary condition will still force the meniscus to be normal to the spines along the "free" contact line, resulting in interface shapes similar to the one shown in the figure below.
Meniscus shape created by the natural boundary conditions when the spines (shown as vectors) are rotated in the x-direction.
) directly. The relevant code is already contained in the driver code. You'll have to comment out the lines in the problem constructor that create the displacement control element and the line that adds it to the problem's mesh. Replace them by the lines // Comment out the previous two commands and uncomment the following two // to prescribe the pressure drop (the curvature) directly //Kappa_pt=new Data(1); //Kappa_pt->pin(0);
Data object that stores the prescribed curvature. (Note that the value of
is already incremented in actions_before_newton_step(). With displacement control this step has no real effect as the Newton method will overwrite this assignment). Check what happens if the prescribed curvature exceeds the maximum possible curvature of the meniscus.
1.4.7