Finite Element
Finite Element
Finite Element
Various methods in the last chapter are mostly applicable to small size problems. We have demonstrated that
the VectorSpace C++ Library help to ease the programming task significantly. However, if the problem size is
down to one or two variables, they might be solved by hand as well. For better approximation of the solution, we
often need to increase the number of the variables substantially. Finite difference method, finite element method,
and boundary element method are three widely accepted methods for large size problems. We have introduced
the finite difference method in Chapter 1 and the boundary element method in the Chapter 3. Yet another defi-
ciency for the variational method in the last chapter is that it is very simplistic in terms of the geometry of the
problem domains. The geometry of the problem domains is, in most case, very simple; a line segment, a square
(or rectangle), or a circle. In real world applications, the geometry of the problem domains is always much more
complicated. We devote the following two chapters for finite element method with considerable depth. The finite
element method is the most well-established method among the three methods for the large-scale problems. It is
also most capable of handling arbitrarily complicated geometry
Moreover, we would also like to demonstrate to the users of the VectorSpace C++ Library that a numerical
method is often not just about mathematical expression which is already made easy by using VectorSpace C++
Library. The programming complexities caused by complicated geometry (and its large size variables) in finite
element method serves as an excellent test bed that the object-oriented programming can make a significant dif-
ference. The source code of “fe.lib” is used to demonstrate the power of object-oriented programming in numer-
ical applications.
The object-oriented programming is the present-day programming paradigm which is supported by the indus-
trial flag-ship general purpose language—C++. Other alternative approaches for programming highly mathemat-
ical problems are symbolic languages, special purpose packages, or program generators written specifically for
dedicated application domains. These alternative approaches may have specialized capability in solving mathe-
where “nen” is the number of nodes in an element. The space of ue is infinite dimensional, in which every point
x on the element has a variable ue(x) associated with it. In Figure 4•1, this infinite dimensional variable ue(x) is
approximated by a finite dimensional space of approximated function u eh (x) which in turn only depends on finite
element— Ωeh
global domain —Ω
boundary—Γ
1
N a ( ξ, η ) = --- ( 1 + ξ a ξ ) ( 1 + η a η ) Eq. 4•2
4
where index “a” ( = 0, 1, 2, 3) is the element node number. The coordinate (ξa , ηa) = {{-1, -1}, {1, -1}, {1, 1}, {-
1, 1}} is the natural (or referential) coordinates of the four nodes. Therefore, the explicit forms for the interpola-
tion functions are
1 1 1 1
N 0 = --- ( 1 – ξ ) ( 1 – η ), N 1 = --- ( 1 + ξ ) ( 1 – η ), N 2 = --- ( 1 + ξ ) ( 1 + η ), N 3 = --- ( 1 – ξ ) ( 1 + η ) Eq. 4•3
4 4 4 4
The interpolation function formula for linear triangular element can be degenerated from Eq. 4•3 by setting
N 0Tri = N 0, N 1Tri = N 1 , and
1 1 1
N 2Tri = N 2 + N 3 = --- ( 1 + ξ ) ( 1 + η ) + --- ( 1 – ξ ) ( 1 + η ) = --- ( 1 + η ) Eq. 4•4
4 4 2
(or using “triangular area coordinates” as in page 454 of Chapter 5). That is
1 1 1
N 0Tri = --- ( 1 – ξ ) ( 1 – η ), N 1Tri = --- ( 1 + ξ ) ( 1 – η ), N 2Tri = --- ( 1 + η ) Eq. 4•5
4 4 2
Coordinate transformation using Eq. 4•3 for quadrilateral and Eq. 4•5 for triangular elements are shown in the
middle column of Figure 4•2. From those integration examples, we note that a reference element1., Ωe , can be
defined in a normalized region with a coordinate transformation rule x ≡ x(Ωe) which maps the reference ele-
ment, Ωe , to a physical element, Ωeh ; i.e., a normalized domain in natural coordinates ξ is transformed to a phys-
ical domain in coordinate x. The interpolation functions for the coordinate transformation rule can be chosen to
be the same as the interpolation for the approximated function u eh (x) as in Eq. 4•1. That is
where x ea is the nodal coordinates (“over-bar” indicates fixed nodal values). A finite element with the same set of
interpolation functions for (1) approximation functions and (2) coordinate transformation rule is called an iso-
1. P. G. Ciarlet, 1978, “ The finite element method for elliptic problems”, North-Holland, Amsterdam.
1-D 2-D
curve quadratic quadrilateral η
linear 2 3 6
7 2
0 1 1 8
0
5
0
linear quadrilateral η ξ
4
3 2
quadratic
1
0 1 2 ξ degenerated η
0 quadratic triangle 2
η 1
degenerated 2 7 8
linear triangle 5
ξ 0
4 ξ
0 1 1
Figure 4•2 (1) 1-D linear and quadratic line elements, and (2) 2-D curve, linear quadrilateral and
trianglular elements, and quadratic quadrilateral and triangular elements.
parametric element. The interpolation functions in finite element method are further subject to continuity and
completeness requirements. The continuity requirement demands that the approximated function to be continu-
ous both in the interior and the boundaries of the element. The completeness requirement demands arbitrary
variation, up to certain order, in the approximated function can be accurately represented. When these require-
ments are relaxed, we have the so-called non-conforming elements.
0, for û ea = g ≡ φ Γa u ea on Γ g
e
W= Eq. 4•7
φ ea, otherwise
g is the essential boundary conditions on the boundary Γg, and u ea (“over-bar” indicates fixed nodal values) is
a
the nodal value of the essential boundary condition with a boundary interpolation function φ Γ on the boundary
e
associated with the element. Since φ ea is defined in the element domain only, this particular choice of weighting
function resembles the subdomain collocation method (see page 229) in the weighted residual method, where W
= 1 on each subdomain and W = 0 elsewhere.
or in matrix forms
where,
k eab = a ( φea, φ eb )
The difference of Eq. 4•8 from Eq. 3•125 in Chapter 3 is now we have second and third terms in the right-hand-
side. The second terms is the non-homogeneous natural boundary conditions
q • n = h ≡ φ a h e on Γh
a
Eq. 4•11
Γe
where q • n is flux q projected on the outward unit surface normal n. This term occurs when we take integration
by parts on the weighted-residual statement, then, applied the Green’s theorem to change the resultant right-
hand-side domain integral into a boundary integral. The third term is due to non-homogeneous essential bound-
ary conditions. According to the first line of Eq. 4•7, rewritten with a new index “b” as g ≡ φ b u eb. In Eq. 4•10 the
Γe
index “a” is the element equation number, and the index “b” is the element variable (degree of freedom) number.
Since W has been taken according to Eq. 4•7, the rows (or equations) corresponding to the fixed degree of free-
doms (essential boundary conditions) will have vanishing weighting function (W = 0) multiplies through-out
every term of Eq. 4•8. Therefore, the rows (or equations) corresponding to the fixed degree of freedoms will be
eliminated at the global level. We also note that the element tensors keab is the element stiffness matrix, and the
element tensors fea is the element force vector.
In summary, for a differential equation problem, we first discretize its domain into elements (as in Figure 4•1)
and approximate its variables (Eq. 4•1), and weighting functions (Eq. 4•7) corresponding to a variational princi-
ple. These steps are known as the finite element approximation1. A finite element approximation depends on the
choice of (1) the variational principle, and (2) a corresponding set of variables approximated by a selected set of
interpolation functions. The various variational principles make the finite element method such an open area for
improvements. These various variational principles also bring a challenge that a finite element program should
be able to endure a dramatic impact of changes in its design structure, and to enable the reuse of existing code in
its evolutionary course. The object-oriented programming has a lot to offer in this regard.
1. p. 3 in F. Brezzi and M. Fortin, 1991, “ Mixed and hybrid finite element methods”, Springer-Verlag, New York, Inc.
K ij û j = Fi
Node(int node_number,
int number_of_spatial_dimension,
double* array_of_coordinates);
Using the terminology of the relational database the “node_number” is the key to this abstract data type—
“Node”. One considers that the “node_number” as the identifier for an instance of the class Node. The following
example is to define a 2-D case with the node number “5”, and coordinates x = {1.0, 2.0}T
double *v;
v = new double[2];
v[0] = 1.0; v[1] = 2.0;
Node *nd = new Node(5, 2, v);
This instantiates an object of type “Node” pointed to by a pointer “nd”. Data abstraction is applied to model the
“Node” as an object. The states of the “Node” is consist of private data members include the node number, the
spatial_dimension, and the values of its coordinates. The behaviors of the “Node” are public member functions
that provide user to query the states of the “Node” including it node number, and spatial dimension, ... etc. The
“operator[](int)” is used to extract the components of the coordinates, and logical operators “opera-
tor==(Node&)” and “operator !=(Node&)” are used for the comparison of the values of two nodes. The data and
the functions that operating on them are now organized together into a coherent unit—class. The private mem-
bers of the object are encapsulated from users that the access are only possible through its public members. The
encapsulation mechanism provides a method to hidden complexities from bothering users (see Figure 4•3).
An element— Ω eh is constructed by
1 Omega_eh(int element_number,
2 int element_type_number,
3 int material_type_number,
int element_node_number,
int *node_number_array);
1. see p. 1 in J. Soukup, 1994, “Taming C++”, Addison-Wesley, Reading, Massachusetts, and preface in J. Lakos, 1996,
“Large-scale C++ software design”, Addison-Wesley, Reading, Massachusetts.
class Node
==
...
int node_no
int spatial_dim controlled access
double* value []
...
node_no()
Figure 4•3 The class Node is consists of private data members to describe its
states, and public member functions provide the access to query its states. The
private members are encapsulated away from the controlled access through the
public members.
The “element_number” play the role of the key for the element class “Omega_eh”. The “element_type_number”
and the “material_type_number” are integers greater or equal to “0”. The default values for the both numbers are
“0”. For example, the “element_node_number” is “3” for a triangular element, and “4” for a four-node element.
The “node_number_array” points to an int pointer array of global node numbers for the element. An example is
1 int *ena; // 10 11
2 ena = new int[4]; // +-------------+
3 ena[0] = 0; ena[1] = 1; // | |
4 ena[2] = 11; ena[3] = 10; // | |
5 Omega_eh *elem = // +-------------+
6 new Omega_eh(0, 0, 0, 4, ena); // 0 1
The order of global node numbers in the “node_number_array” is counter-clockwise from the lower-left corner,
as illustrated in the comment area after each statement, which is conventional in finite element method.
A discretized global domain— Ω h basically consists of a collection of all nodes and elements as
6 7 8
8 9 10 11
3 4 5
5 6 7
4
0 1 2
0 1 2 3
Figure 4•4 Nine elements in a rectangular area consist of 16 nodes.
The data structure Dynamic_Array<T> does what it means, which is declared and defined in “dynamic_array.h”.
It is a simplified version of <dynarray> in the standard C++ library1. Two protected member data consist of
“the_node_array” and “the_omega_eh_array” (element array). The default constructor “Omega_h::Omega_h()”
is declared in the header file, The users of the “fe.lib” are responsible for its definition. The following code seg-
ment shows an example of a user defined discretized global domain as illustrated in Figure 4•4.
1. P.J. Plauger, 1995, “The draft standard C++ library”, Prentice-Hall, Inc., Englewood Cliffs, New Jersey.
Then, we can make an instance of the discretized global domain “Omega_h” by declaring in main() function
Omega_h oh;
The instance “oh” calls the default constructor “Omega_h::Omega_h()” that is custom made by the user.
Remark: For users who are familiar with database languages1, the class definitions of Node, Omega_eh, and
Omega_h per se define the database schema; i.e., the format of the data, which serves the function of the data
definition language (DDL). The function “Dynamic_Array<T>::add(T*)” is an example of data manipulation
language (DML) that assists user to modify the database. And two most important features of data query lan-
guage provided by “fe.lib” are the node selector “Node& Omega_h::operator [ ](int)” and the element selector
“Omega_eh& Omega_h::operator ( )(int)”.
û h on Ωh.
The essential boundary conditions (fixed degree of freedoms) and natural boundary conditions are
g h on Γ h g , and h h on Γ hh
respectively, where the “over-bar” indicates a fixed value. The global variables û h are modeled as class “U_h”.
And, the global boundary conditions g h and h h are modeled as class “gh_on_Gamma_h”. A constraint flag is
used to switch in between “Dirichlet” and “Neumann” to indicate whether the stored values are essential or nat-
ural boundary conditions, respectively.
All three kinds of values û h , g h , and h h are nodal quantities, which are somewhat similar to the coordinates
of a node; i.e., x . Therefore, we can factor out the code segment on coordinates in the class Node and create a
more abstract class Nodal_Value for all of them.
1 class Nodal_Value {
2 protected:
3 int the_node_no,
4 nd; // number of dimension
1. e.g., Al Stevens, 1994, “ C++ database development”, 2nd eds., Henry Holt and Company, Inc., New York, New York.
Now the three classes are publicly derived from the base class “Nodal_Value” as
All three derived classes inherit the public interfaces (member functions) of the class Nodal_Value. For example,
now all three derived classes can use the operator[](int) to access the nodal values. If “nd” is an instance of the
class Node and “uh” is an instance of the class U_h, and “gh” is an instance of the class gh_on_Gamma_h, then,
the access is performed by
1 int main() {
...
2 int ndf = 1;
3 U_h uh(ndf, oh);
4 gh_on_Gamma_h gh(ndf, oh);
...
5 }
The constructor of class U_h is defined in “fe.lib”. The users do not need to worry about it. However, the essen-
tial and natural boundary conditions in the class gh_on_Gamma_h are parts of every differential equation prob-
g = 30 oC
12 13 14 15
6 7 8
8 9 10 11
h=0 3 4 5 h=0
5 6 7
4
0 1 2
0 1 2 3
g = 0 oC
Figure 4•5 Heat conduction problem with two side insulated, bottom and top temperature
boundary conditions are set to 0 oC and 30 oC, respectively.
lems. Therefore, defining the constructor of class gh_on_Gamma_h is users’ responsibility. This constructor
needed to be defined before it is instantiated in the above. For the problem at hand, we have
The first line in the constructor (line 2) called a private member function of class gh_on_Gamma_h. This func-
tion initiates a private data member “Dyanmic_Array<Nodal_Constraint> the_gh_array” for the class
gh_on_Gamma_h. This is a mandatory first statement for every constructor of this class for ochestrating internal
data structure. The first line in the for loop uses a constraint type selector “operator ( )(int degree_of_freedom)”.
It can be assigned, for each degree of freedom, to either as “gh_on_Gamma_h::Dirichlet” to indicate an essential
boundary condition or as “gh_on_Gamma_h::Neumann” to indicate a natural boundary condition. Line 7 uses a
constraint value selector “operator [ ](int degree_of_freedom)” to assign 30oC to the nodes on the upper bound-
ary. The default condition and default value, following finite element method convention, are natural boundary
condition and “0”, respectively. Therefore, for the present problem, the natural boundary condition with “0” on
two sides can be neglected. On the bottom boundary conditions, we only need to specify their constraint type as
essential boundary conditions, the assignment of value of “0.0” (the default value) can be skipped too.
dx = - f / df
For one dimensional problem, f, df, and dx are all Scalar object of C0 type. For n-dimensional problem, n > 1, f
and dx are Vector object of C0 type with length “n” and df is a Matrix object of C0 type with size “n × n”. The
“C0::operator / (const C0&)” now no longer implies “divide” operation. It actually means to invoke matrix solver
that use df as the left-hand-side matrix and “-f” as the right-hand-side vector. The default behavior of Vector-
Space C++ Library is the LU decomposition, although you have the freedom to change the default setting to
Cholesky decomposition (for symmetrical case only), QR decomposition (for ill-conditioned case) or even the
singular value decomposition (for rank deficient case). This single function is sufficient for the very different
arguments taken, and different operations intelligently dispatched to perform upon themselves.
In Chapter 3, we have introduced the non-linear and transient problems in the context of variational methods
which are now the kernel of the element formulation. We considers the impact of change by these two types of
problems that will be played out in the element formulation. We note that an even greater impact will be played
out in the mixed formulation, introduced in Chapter 3 in page 217, if we use global matrix substructuring solu-
tion method (or “static condensation”). We defer the more complicated matrix substructuring until Section 4.2.5.
First, from “fe.lib” user’s perspective, the design of the “element formulation definition language”, if you
would, is for (1) definition of an element formulation and (2) registration of an element type. The user code seg-
ment for the declaration and instantiation of a class HeatQ4 is
From this code, the line 5 which is the declaration of the constructor of the heat conduction element formula-
tion—“HeatQ4(int, Global_Discretization&)”. The definition of this constructor is user customized, the contents
of this constructor is the variational formulation of differential equation problem at hand. We will get to the
details of definitions for the constructor (line 8) at the end of this section.
Polymorphism: First, let’s look at the fe.lib implementation of polymorphism, in this code segment, enhanced by
emulating symbolic language by C++1. The class Element_Formulation and the custom defined user class
HeatQ4 are used hand-in-hand. The Element_Formulation is like a symbol class for its actual content class—
HeatQ4. The symbol class Element_Formulation is responsible for doing all the chores including memory man-
agement and default behaviors of the element formulation. The content class HeatQ4 does what application
domain actually required; i.e., the variational formulation. The class Element_Formulation has a private data
member “rep_ptr” (representing pointer) which is a pointer to an Element_Formulation type as
1 class Element_Formulation {
2 ...
3 Element_Formulation *rep_ptr;
4 C0 stiff, force, ...;
5 protected:
6 virtual C0& __lhs() { return stiff; }
7 virtual C0& __rhs() { return force; }
8 ...
9 public:
10 ...
11 C0& lhs() { return rep_ptr->__lhs(); }
12 C0& rhs() { return rep_ptr->__rhs(); }
13 ...
14 };
Since the derived class HeatQ4 is publicly derived from the base class Element_Formulation, an instance of
HeatQ4 has its own copy of Element_Formulation as its “header”. Therefore, the rep_ptr can point to an instance
1. see (1) p. 58 “handle / body idiom”, (2) p. 70 “envelope / letter” idiom, and (3) p. 315 “symbolic canonical form” in J.O.
Coplien, 1992, “ Advanced C++: Programming styles and idioms”, Addison-Wesley, Reading, Massachusetts.
Symbol Element_Formulation
rep_ptr
Content Element_Formulation
HeatQ4
∂R
R i + 1 ≡ R ( u i + 1 ) = R ( u i + δ u i ) ≅ R ( u i ) + ------- δ u i = 0 Eq. 4•13
∂u u i
From this approximated equation, we have the incremental solution δui as the solution of the simultaneous linear
algebraic equations
–1
∂R –1
δ u i = – ------- R ( u i ) ≡ KT ( u i ) R ( u i ) Eq. 4•14
∂u u i
where both the tangent stiffness matrix K –T1 ( u i ) and the residual vector R ( u i ) are functions of ui. That is at the
element level, the nodal values— û i must be available. Therefore, a new class derived from class
Element_Formulation is
The class “Nonlinear” inherits all the public interfaces of the class Element_Formulation. On top of that we have
declared a private data member “ul”, the element nodal variables, for this nonlinear element. When the class
“Nonlinear” is defined, it is imperative to invoke its private member function “Nonlinear::__initialization(int,
Global_Discretization&)” to setup the element nodal variables. In this case, the use of inheritance for program-
ming by specification is very straight forward. An example of a simple nonlinear problem is shown in Section
4.2.3. In Chapter 5, we investigate state-of-the-art material nonlinear (elastoplasticity) and geometrical nonlin-
ear (finite deformation problems).
For a transient problem, the polymorphic technique is much more complicated. We show the parabolic case
here. From Eq. 3•191 in Chapter 3 (page 253) we have
In this case, the nodal values from the last time step— û n is also needed. In addition, we also need to compute
the mass (heat capacitance) matrix “M”.
Note that in the definition of class Element_Formulation the default behaviors of the last two protected member
functions are through two virtual member functions to return element “stiff” matrix and element “force” vector
as
This is standard for the static, linear finite element problems. When an instance of Element_Formulation calls its
public member functions “Element_Formulation::lhs()” and “Element_Formulation::rhs()”, the requests are for-
warding to its delegates’ virtual member functions. If these two protected virtual member functions have been
overwritten (lines15-23), the default behaviors in the base class will be taken over by the derived class. An exam-
ple of transient program is shown in Section4.2.4.
Element Type Register: A differential equation problem, solved by a finite element method may apply different
elements for different regions. For example, we can choose triangular elements to cover some of the areas, while
quadrilateral elements to cover the rest of the areas. We can have a “truss” element on certain parts of “planner”
elements to simulated a strengthened structure. From user’s perspective, he needs to register multi-elements as
The element type register uses a list data structure. We number the last registered element’s element type number
as “0”. This number increases backwards to the first registered element in the “type_list”. When we define an
element as introduced in page 271. The second argument is supplied with this number such as
Omega_eh *elem;
elem = new Omega_eh(0, element_type_number, 0, 4, ena);
The C++ idiom to implement the element type register is discussed in Section 4.1.3.
Element Formulation Definition: Now we finally get to the core of the Element_Formulation. That is the defi-
nition of its constructor. We show an example of heat conduction four-node quadrilateral element
The “xl” is the element nodal coordinates which is a C0 type Matrix object of size nen × nsd(number of element
nodes) × (number of spatial dimension). The “stiff” is the element stiffness matrix, a square matrix of size
(nen × ndf) × (nen × ndf) (“ndf” as number of degree of freedoms). The “force” is the element force vector of
size (nen × ndf). The VectorSpace C++ Library is most heavily used in this code segment, since it concerns the
subject of variational methods the most. If you have mastered Chapter 3 already, these lines should be com-
pletely transparent to you.
The treatment of the terms on natural boundary conditions ( φei , h ) Γ and the essential boundary conditions
– a ( φ ei , φ ej )u ej , in Eq. 4•8 in page 269, requires some explanation. “fe.lib” adopts the conventional treatment that
the natural boundary conditions are taken care of at the global level in Matrix_Representation::assembly() where
the user input equivalent nodal forces of natural boundary condition are directly added to the global force vector.
The treatment of the third term is also conventional that when the Element_Formulation::__rhs() is called it
automatically call Element_Formulation::__reaction() which is defined as
C0 & Element_Formulation::__reaction() {
the_reaction &= -stiff *gl; // “gl” is the element fixed boundary conditions
return the_reaction;
}
C0 & Element_Formulation::__rhs() {
the_rhs &= __reaction();
if(force.rep_ptr()) the_rhs += force;
return the_rhs;
}
These two default behaviors can be overwritten as in the class “Transient” in the above. Another example is that
we might want to have different interpolation function to approximate the boundary conditions. In such case,
first we need to call “Matrix_Representation::assembly()” in main() program as
1 int main() {
... // instantiation of Global_Discretization object
2 Matrix_Representation mr(gd);
3 mr.assembly();
4 C0 u = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
5 gd.u_h() = u;
6 gd.u_h() = gd.gh_on_gamma_h();
7 cout << gd.u_h();
8 return 0;
9 }
We show an example illustrated in Figure 4•7. Step 4a. A global stiffness matrix is a square matrix of size
tnn × ndf = 7 × 7 per side, and global force vector is of size tnn × ndf = 7, respectively (where “tnn” is the total
number of node, and “ndf” is number of degree of freedoms assumed as “1” for simplicity). The fixed degree of
freedoms are then removed from the global stiffness matrix (with remaining size = 5 × 5) and global force vector
(with remaining size = 5). This is done at line 2 when an instance of Matrix_Representation “mr” is initialized
with an instance of Global_Discretization “gd”. Step 4b. The mapping relationship of element stiffness matrix to
global stiffness matrix, and element force vector to global force vector can be constructed element by element.
This global-element relation is also established in line 2. Step 4c. The maps in Step 4b are used to add element
stiffness matrices and element force vectors to the global stiffness matrix and global force vector as in line 3,
where the public member function “Matrix_Representation::assembly()” is called. Then, the global stiffness
matrix and global force vector are used for linear algebraic solution of the finite element problem as in line 4.
Step 4d. The solution is in the order of free degree of freedom number which is then mapped back to the global
degree of freedom number for output of the solution. This is done in line 5 where the global solution vector
gd.u_h() is updated with the solution “u”. The values for the fixed degree of freedoms can be retrieved from the
program input of the problem. That is the line 6 where the same global solution vector gd.u_h() is updated with
fixed degree of freedom “gd.gh_on_gamma_h()”.
In between the Step 4c and Step 4d, the variational problem has been reduced to a matrix solution problem. A
regular matrix solver provided in C0 type Matrix in Chapter 1 can be applied to solve this problem, although
0 1 2 3 4 5 6 7
0 2 5 7 0
0
1 2 1
2
1 4 6
3 4 3
4
3
5
6
7
Step 4b: map element stiffness matrix and element force vector to global matrix and global vector, respectively
Element 0:
Element 1: Element 2: Element 3: Element 4:
0 1 2 4 5
0
1
2
4
5
Step 4c: assembly of all elements Step 4d: map equation number to global degree of freedom
number global degree of freedom number equation number
0
0 1
2
1
3
2
4
4
5
5
6
7
Figure 4•7 Element connectivity example. Step 1. elimination of fixed degree of freedoms, Step 2.
element to global mapping, Step 3. assembly all elements, and Step 4. equation number to global
degree of freedoms number.
The global stiffness matrix and global force vector can be replaced by corresponding special matrix and vector,
provided you have code all the needed interfaces for retrieving the components in the special forms of the global
matrix and vector.
Just as in the Element_Formulation, object-oriented programming provides mechanisms to deal with impact
of change for a swift evolution of “fe.lib”. Examples of these changes are mixed and hybrid method and contact
mechanics. In abstract mathematical form, they all belong to the category of constrained optimization problems.
Dependency Graph
The four major components in the modeling of finite element method are (1) the discretized global
domain Ω h , (2) variables u h , (3) element formulation (EF), and (4) matrix representation (MR). We can draw a
tetrahedron with the four vertices represent the four components and the six edges represents their mutual rela-
tions (see Figure 4•8). The first thing we can do is this tetrahedron can be reduced to a planner graph, meaning
that no edge among them can cross each other; i.e., to reduce it to a lower dimension. This step can not always be
done. If there is any such difficulty, we need to applied dependency breakers (to be discussed later) to the graph
to reduce it to a lower dimension. In a planner graph, we represent a component as a node, and their relations as
the arrows. For a component, the number of arrows pointing towards the node is called degree of entrance. In the
convention of object-oriented method, an arrow stands for a dependency relation that the node on the starting
point of an arrow depends on the node at the ending point of the arrow.
We briefly explain these dependency relations. The entrance number “0” says the global discretized variables
u h depends on the global discretization Ω h . u h is defined as interpolation of nodal variables as in Eq. 4•1; i.e.,
conceptually u h ( φ, û ) , and the nodal variables û depends on how nodes and element, Ω h , are defined. The
1. Johnson, C., 1987, “ Numerical solution of partial differential equations by the finite element method”, Press Syndicate of
the University of Cambridge, UK.
EF 7 MR
MR
Figure 4•8Tetrahedron to show four components on the vertices with six edges. This can be
transformed to a planner graph with arrows to show dependency relation. The numbers
marked are the entrance numbers.
entrance number “1” says the element formulation depends on the global variables u h , since the element stiff-
ness matrix and element force vector are all calculated corresponding to the interpolated value of the element
nodal variables û e . The entrance number “2” says the matrix representation depends on the element formula-
tion, since element formulation supplies the element stiffness matrices and element force vectors to be mapped
to the global matrix and global vector. The entrance number “3” is a redundant dependency relation. Since u h
depends on Ω h and EF depends on u h , we can conclude that EF must depend on Ω h . The entrance number 4 is
a similar redundant relation with one more step of MR depending on EF. The entrance number 5 and 7 show a
mutual dependency relation that MR depends on u h for MR is just the lhs and rhs to solve for u h , and after we
get solution from solving MR we need to map the solution vector from MR back to u h , since the fixed degree of
freedom is excluded from the MR, the variable number in MR is different from the number of global degree of
freedom. Therefore, u h depends on the knowledge of MR. The entrance number “6” has Ω h depends on EF.
When we define elements, we need to specify the element type number.
The Ω h has highest degree of entrance that means it should be at the highest root of class hierarchy. However, u h
and EF have same degree of entrance. Since the EF explicitly depends on u h . u h is to be escalated and EF is to
be demoted. The order in the class hierarchical is, therefore, Ω h , u h , EF, MR, as the order shown in TABLE 4•1
The pseudo-level structure is shown in the right-hand-side of Figure 4•9. The redundant relations, entrance num-
bers 3, 4, and 5, are drawn as light arrows. These redundant dependencies are first to be eliminated. Next, there
are still two un-resolved entrances (entrance 6 and 7 pointing downwards) in the left-hand-side of Figure 4•9,
which make the graph not to be a level structure. Therefore, in the rest of this section we will explore C++ level-
ization idioms1 that help us to break these two dependency relations. Now not only the graph is simple to under-
stand for human mind, but also it will have a profound impact on the organization of the software components.
Firstly, with a simplified dependency hierarchy, the interfaces of the software components are much more simpli-
fied. The interaction among the components can be understood easier. For example one can just bear in mind that
only components that are lower in the hierarchy depend on those on the above. And , then, if there are exception,
such as entrances 6 and 7, we just mark them as such. On the other hand, the complicated network of software
components such as the one in the left-hand-side of Figure 4•8 will be extremely difficult to follow. There are so
many cliques among them. One nodes can lead to the other and then back to itself. The dynamical interaction
patterns among the components seems to have a life of its own. The sequence of events can be acted out differ-
ently every time. Therefore, the model based on the graph level structure will be less error proned. Secondly, the
complicated network demands all module to be developed, tested and maintained all together. Divide and con-
quer is the principal strategy that we always need to deploy in the development, testing and maintenance of a pro-
gram. The graph level structure in the right-hand-side of Figure 4•9 means that now these processes can be done
in a more modulized fashion from top level 0 down to level 3 incrementally. We discuss two dependency break-
ers in the followings.
Pointer to a Forward Declaration Class: We can apply a traditional C technique to break the dependency rela-
tion caused by entrance number 7. That is the output for solution u h needs the knowledge of class
Matrix_Representation. The the order of the solution vector “u”, in the main(), is corresponding to the order of
variable number in the Matrix_Representation. For output of solution, we need to map this internal order of the
Matrix_Representation back to the order of global nodal degree of freedoms u h according to the specification
from the problem. This breaking of dependency relations can be done with the forward declaration in traditional
Level 3 MR MR
7 7
Ia. “u_h.h”
1 class Matrix_Representation;
2 class U_h {
3 Matrix_Representation *mr;
4 ...
5 public:
6 ...
7 Matrix_Representation* &matrix_representation() { return mr; }
8 U_h& operator=(C0&);
9 U_h& operator+=(C0&);
10 U_h& operator-=(C0&);
11 };
Ib, “u_h.cpp”
12 #include “u_h.h”
13 ...
IIa. “matrix_representation.h”
14 class Matrix_Representation {
15 ...
16 protected:
17 Global_Discretization &the_global_discretization;
18 ...
19 public:
IIb. “matrix_representation.cpp”
23 #include “u_h.h”
24 #include “matrix_representation.h”
25 void Matrix_Representation::__initialization(char *s) {
26 if(!(the_global_discretization.u_h().matrix_representation()) )
27 the_global_discretization.u_h().matrix_representation() = this;
28 ...
29 }
30 U_h& U_h::operator=(C0& a) { ... }
31 U_h& U_h::operator+=(C0& a) { ... }
32 U_h& U_h::operator-=(C0& a) { ... }
The class U_h and class Matrix_Representation are actually depend on each other. Therefore, the implementa-
tions of them in the “cpp” extension files will require the knowledge of their definitions. That is to include the
“.h” extension files. Traditional C language (note that class can be viewed as a special case of struc) provides
mechanism to break this mutual dependency relation by forward declaration such as in line 1 that the class name
Matrix_Representation is introduced in the name scope of the translation unit “u_h.h”, on the condition that only
the name of class Matrix_Representation, not its member data or member functions are to be used in the defini-
tion of class U_h. In class U_h, we at most refer to a pointer of class Matrix_Representation, which is only an
address in the computer memory, not an actually instance of the class Matrix_Representation, because the transla-
tion unit has no knowledge yet of what class Matrix_Representation really is. Now a programmer in the devel-
oper team can compile and test “u_h.cpp” separately, without having to define class Matrix_Representation at all.
One scenario of using the forward declaration of a class and using a member pointer to it is after the entire
product has been completed and sale to the customer, if we want to change the definition and implementation of
class Matrix_Representation we do not need to recompile the file “u_h.cpp”. The changes in “.h” and “.cpp” files
of the class Matrix_Representation do not affect the object code of class U_h module. A less dramatic scenario of
using a member pointer is that a developing process is iterative and the files always need to be compiled many
times. During developing cycles, class U_h module does not need to be recompiled every time that class
Matrix_Representation is changed. Therefore we have seen a most primitive form of a compilation firewall been
set to separate the compile-time dependency among source files. In a huge project, such as the one developed in
Mentor Graphics we mentioned earlier. They may have thousands of files. It will be ridiculous that when an un-
important change of a tiny file higher in the dependency hierarchy is made. The “make” command may trigger
tens of hours in compile time to update all modules that are depending on it. Not for long you will refuse to do
any change at all. In yet another scenario, when class Matrix_Representation is intended to be encapsulated from
end-users, this same technique insulates end-users from accessing the class Matrix_Representation directly.
Certainly, the dependency relation of entrance number 7 exists, which is demanded by the problem domain,
we can only find a way to get around it. We successfully break this particular dependency and make class U_h an
independent software module, but how do we re-connect them as the problem domain required. When we define
the constructor of the class Matrix_Representation, the first line of the constructor is to call its private member
function “__initialization(char*)”. This private member function set up the current instance of
Element Type Register: In page 281, we have discussed the element type register from user’s code segment as
registration by
1 Element_Fomulation* Element_Formulation::type_list = 0;
2 Element_Type_Register element_type_register_instance;
3 static Truss truss_instance(element_type_register_instance); // element type number “2”
4 static T3 t3_instance(element_type_register_instance); // element type number “1”
5 static Q4 q4_instance(element_type_register_instance); // element type number “0”
The element types are registered in a list data structure. The last registered element type number is “0”, and then
the number increases backwards to the first registered element in the “type_list”. This element type numbers are
referred to when we define the element as
This user interface design itself breaks the dependency of the definition of an element on element types. The
C++ technique to implement this design is the autonomous virtual constructor1. Let’s first look at the definitions
of the class Element_Formulation
1. see autonomous generic constructor in J. O. Coplien, 1992, “ Advanced C++: Programming styles and idioms”, Addison-
Wesley, Reading, Massachusetts.
The class Element_Type_Register, in line 1, is a dummy one that is used like a signature in line 8 to indicate that
the instance of class Element_Formulation generated is for element type identifier, and the static member
type_list embedded in the Element_Formulation will be maintained automatically. This element_type_number
information is used in “Matrix_Representation::assembly()” as
Line 3 is to compute the Element_Formulation, and form an instance of Element_Formulation, say “ef”, it can be
used as “ef.lhs()” and “ef.rhs()” to query information. The task of “create()” is to call “make()” forward by its
delegate “rep_ptr->make()”. Since “make()” is virtual and to be redefined in the derived class. The request in line
3 is dispatched to a user defined element class. The virtual function mechanism is usually referred to as the late-
binding technique at run-time. In this case, the cyclic dependence of an element on element formulation, deliber-
ately broken for the software modulization, is re-connected at the run-time by the late-binding technique sup-
ported by C++.
Level 0
Node
Ω eh
Ωh
Level 1
uh g ∈ Γ g, h ∈ Γ h
Global_Discretization
Level 2
EF
User Defined
Elements
Finite_Element_Approximation
Level 3
MR
Global
Tensors Finite_Element_
Element Approximation
Tensors
MR
Globla_Discretization
EF
MR
Ωh
uh
EF
MR
//==========================================================================
// Step 1: Global_Discretization
//==========================================================================
//==========================================================================
// Step 2: Element_Formulation
//==========================================================================
//==========================================================================
// Step 3: Matrix_Representation and Solution Phase
//==========================================================================
24 int main() {
25 int ndf = 1; // instantiation of Global_Discretization
26 Omega_h oh;
27 gh_on_Gamma_h gh(ndf, oh);
28 U_h uh(ndf, oh);
29 Global_Discretization gd(oh, gh, uh);
30 Matrix_Representation mr(gd);
31 mr.assembly(); // assemble the global matrix
32 C0 u = ((C0)(mr.rhs())) / ((C0)(mr.lhs())); // solution phase
33 gd.h_h(); = u; gd.u_h() = gd.gh_on_gamma_h(); // update solution
34 cout << gd.u_h(); // output solution
35 return 0;
36 }
Many segments and their variations of this template have been discussed in 4.1.2. The rest of this Chapter con-
sists of concrete examples of writing user programs using this template.
2
du
– 2
= cos πx, 0 < x < 1 Eq. 4•16
dx
1. Dirichlet boundary conditions: From Eq. 4•9 and Eq. 4•10 we have the element stiffness matrix as
1
dφ i dφ j
--------e- --------e- dx
k eij = a ( φ ei , φ ej ) = ∫ dx dx Eq. 4•18
0
The last identity is obtained, since the essential and natural boundary conditions are all homogeneous the second
term ( φ ei , h ) Γ and the third term – a ( φ ei , φ ej )u ej always vanish. In more general cases that they are not homoge-
1. p. 367-371 in J.N. Reddy, 1986, “Applied functional analysis and variational methods in engineering”, McGraw-Hill, Inc.
1 1
φ e0 = --- ( 1 – ξ ), and φ e1 = --- ( 1 + ξ ) Eq. 4•20
2 2
This is the linear interpolation functions we have used for integration of a line segment in Chapter 3 (Eq. 3•10
and Eq. 3•11 of Chapter 3).
The finite element program using VectorSpace C++ Library and “fe.lib” to implement the linear element is
shown in Program Listing 4•1. We use the program template in the previous section. First, we define nodes and
elements in “Omega_h::Omega_h()”. This constructor for the discretized global domain defines nodes with their
node numbers and nodal coordinates as
The elements are defined with global node number associated with the element as
Three sets of boundary conditions are (1) Dirichlet (2) Neumann, and (3) Mixed. The corresponding code seg-
ments can be turned on or off with a macro definitions set, at compile time, as
1 #if defined(__TEST_MIXED_BOUNDARY_CONDITION)
2 gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
3 __initialization(df, omega_h);
4 the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet;
5 the_gh_array[node_order(0)][0] = 0.0;
6 the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Neumann;
7 the_gh_array[node_order(node_no-1)][0] = 0.0;
#include "include\fe.h"
static const int node_no = 9; static const int element_no = 8; static const int spatial_dim_no = 1;
Omega_h::Omega_h() {
Definte discretizaed global domain
for(int i = 0; i < node_no; i++) {
double v; v = ((double)i)/((double)element_no); define nodes
Node* node = new Node(i, spatial_dim_no, &v); the_node_array.add(node);
}
int ena[2]; define elements
for(int i = 0; i < element_no; i++) {
ena[0] = i; ena[1] = ena[0]+1;
Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena); the_omega_eh_array.add(elem);
}
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h);
define boundary conditions
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet; u(0) = u(1) = 0
the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Dirichlet;
}
class ODE_2nd_Order : public Element_Formulation {
instantiate fixed and free variables and
public: Global_Discretization
ODE_2nd_Order(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
ODE_2nd_Order(int, Global_Discretization&);
};
Element_Formulation* ODE_2nd_Order::make(int en, Global_Discretization& gd) {
return new ODE_2nd_Order(en,gd);
}
static const double PI = 3.14159265359;
ODE_2nd_Order::ODE_2nd_Order(int en, Global_Discretization& gd)
: Element_Formulation(en, gd) {
Quadrature qp(spatial_dim_no, 2);
Define user element “ODE_2nd_Order”
H1 Z(qp),
N=INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 2, 1, qp); 1d Gauss Quadrature
N[0] = (1-Z)/2; N[1] = (1+Z)/2;
H1 X = N*xl;
H0 Nx = d(N)(0)/d(X); N0 = (1-ξ)/2, N1 = (1+ξ)/2
J dv(d(X)); coordinate transformation rule
stiff &= (Nx * (~Nx)) | dv;
force &= ( ((H0)N)*cos(PI*((H0)X)) )| dv;
N,x
} the Jacobian
Element_Formulation* Element_Formulation::type_list = 0; 1 1
dφ ei dφ ej
∫ --------
- --------- dx , and f ei= ∫ φ ei cos πx dx
static Element_Type_Register element_type_register_instance;
k eij =
static ODE_2nd_Order ode_2nd_order_instance(element_type_register_instance); dx dx
int main() { 0 0
const int ndf = 1; register element
Omega_h oh; gh_on_Gamma_h gh(ndf, oh);
U_h uh(ndf, oh); Global_Discretization gd(oh, gh, uh);
Matrix Form
Matrix_Representation mr(gd); assembly all elements
mr.assembly(); solve linear algebraic equations
C0 u = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h();
update solution and B.C.
cout << gd.u_h(); output
return 0;
}
Listing 4•1 Dirichlet boundary condition u(0) = u(1) = 0, for the differential equation - u” = f (project:
“2nd_order_ode” in project workspace file “fe.dsw” (in case of MSVC) under directory “vs\ex\fe”).
The Dirichlet boundary conditions is taken as the default macro definition. The constraint type selector is the
“operator () (int dof)”. We can assign type of constraint to the corresponding degree of freedom as
“gh_on_Gamma_h::Neumann” or “gh_on_Gamma_h::Dirichlet”. The default constraint type is Neumann con-
dition. The constraint value selector is the “operator [ ](int dof)”. The default constraint value is “0.0”. In other
words, you can eliminate lines 5-7, lines12-15, and lines 17, 23, 25, and the results should be the same.
The added essential boundary conditions on the middle point of the problem domain (line 16, and 17) are
necessary for the Neumann boundary conditions for this problem, because the solution is not unique under such
boundary conditions only.
“fe.lib” requires the following codes to ochestrate the polymorphic mechanism of the Element_Formulation
to setup the element type register. For a user defined class of “ODE_2nd_Order” derived from class
Element_Formulation we have
Lines 10 and 11 setup the data for registration and Line 12 register the element formulation “ODE_2nd_Order”.
Line 5 is the constructor for class ODE_2nd_Order where we defined the user customized element formulation as
For the element stiffness matrix, instead of “stiff &= (Nx* (~Nx)) | dv;”, the tensor product operator “H0&
H0::operator%(const H0&)” in VectorSpace C++ can be used for expressing
1
dφ e dφ e
k e =∫ -------- ⊗ -------- dx Eq. 4•21
dx dx
0
as
1 int main() {
2 const int ndf = 1;
3 Omega_h oh; // global discretizaed domain—Ω h
4 gh_on_Gamma_h gh(ndf, oh); // fixed variables — g ∈ Γ g, h ∈ Γ h
5 U_h uh(ndf, oh); // free variables— u h
6 Global_Discretization gd(oh, gh, uh); // the class Global_Discretization
7 Matrix_Representation mr(gd);
8 mr.assembly(); // assembly all elements
9 C0 u = ((C0)(mr.rhs())) / ((C0)(mr.lhs())); // matrix solver
The instances of global discretization, “oh”, and fixed and free variables, “gh” and “uh”, respectively, are then
all go to instantiate an instance of class Global_Discretization, “gd”. The results of using the linear line element
for the second order differential equation in finite element method are shown in Figure 4•11.
1
-0.1
0.2 0.4 0.6 0.8 1 0.2 0.4 0.6 0.8
Figure 4•11 The results from eight linear elements for (1) Dirichelt (2) Neumann and (3) Mixed
boundary condtions for the second-order ordinary differentail equation. Line segments with open
squares are finite element solutions, and continuous curves are analytical solutions.
–ξ ξ
φ e0 = ------ ( 1 – ξ ), φ e1 = ( 1 – ξ ) ( 1 + ξ ) and φ e2 = --- ( 1 + ξ ) Eq. 4•22
2 2
These are the same quadratic interpolation functions in the Chapter 3 (Eq. 3•22).
The finite element program using VectorSpace C++ Library and “fe.lib” to implement the quadratic line ele-
ment is shown in Program Listing 4•2. The definitions of 5 nodes and 2 quadratic elements are
#include "include\fe.h"
static const int node_no = 5; static const int element_no = 2; static const int spatial_dim_no = 1;
Omega_h::Omega_h() {
Definte discretizaed global domain
for(int i = 0; i < node_no; i++) {
double v; v = ((double)i)/((double)element_no); define nodes
Node* node = new Node(i, spatial_dim_no, &v); the_node_array.add(node);
}
int ena[3]; define elements
for(int i = 0; i < element_no; i++) {
ena[0] = i; ena[1] = ena[0]+1; ean[2] = ena[0] + 2;
Omega_eh* elem = new Omega_eh(i, 0, 0, 3, ena); the_omega_eh_array.add(elem);
}
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h);
define boundary conditions
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet; u(0) = u(1) = 0
the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Dirichlet;
}
class ODE_2nd_Order_Quadratic : public Element_Formulation {
instantiate fixed and free variables and
public: Global_Discretization
ODE_2nd_Order_Quadratic(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
ODE_2nd_Order_Quadratic(int, Global_Discretization&);
};
Element_Formulation* ODE_2nd_Order_Quadratic::make(int en, Global_Discretization& gd) {
return new ODE_2nd_Order_Quadratic(en,gd);
}
static const double PI = 3.14159265359;
ODE_2nd_Order::ODE_2nd_Order_Quadratic(int en, Global_Discretization& gd)
: Element_Formulation(en, gd) {
Quadrature qp(spatial_dim_no, 2);
Define user element “ODE_2nd_Order”
H1 Z(qp),
N=INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 3, 1, qp); 1d Gauss Quadrature
N[0] = -Z*(1-Z)/2; N[1] = (1-Z)*(1+Z); N[2] = Z*(1+Z)/2;
H1 X = N*xl;
N0=-ξ (1-ξ) / 2, N1=(1-ξ) (1+ξ),
H0 Nx = d(N)(0)/d(X); N2 = ξ (1+ξ) / 2
J dv(d(X)); coordinate transformation rule
stiff &= (Nx * (~Nx)) | dv;
force &= ( ((H0)N)*cos(PI*((H0)X)) )| dv;
N,x
} the Jacobian
Element_Formulation* Element_Formulation::type_list = 0; 1 1
dφ i dφ j
--------e- --------e- dx , and f i= φ i cos πx dx
∫ dx dx e ∫ e
static Element_Type_Register element_type_register_instance;
static ODE_2nd_Order_Quadratic
k eij =
ode_2nd_order_quadratic_instance(element_type_register_instance); 0 0
int main() { register element
const int ndf = 1; Omega_h oh; gh_on_Gamma_h gh(ndf, oh);
U_h uh(ndf, oh); Global_Discretization gd(oh, gh, uh);
Matrix Form
Matrix_Representation mr(gd); assembly all elements
mr.assembly(); solve linear algebraic equations
C0 u = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h();
update solution and B.C.
cout << gd.u_h(); output
return 0;
}
Listing 4•2 Quadratic Element for Dirichlet boundary condition u(0) = u(1) = 0 of the differential equa-
tion - u” = f (project: “quadratic_ode” in project workspace file “fe.dsw” under directory “vs\ex\fe”).
The interpolation functions for Eq. 4•22 in the constructor of the user defined element is
1 H1 Z(qp),
2 N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
3 "int, int, Quadrature", 3/*nen*/, 1/*nsd*/, qp);
–ξ ξ
4 N[0] = -Z*(1-Z)/2; N[1]=(1-Z)*(1+Z); N[2]=Z*(1+Z)/2; // φ e0 = ------ ( 1 – ξ ), φe1 = ( 1 – ξ ) ( 1 + ξ ), φ e2 = --- ( 1 + ξ )
2 2
The results of using only two quadratic elements are shown in Figure 4•12.
Figure 4•12 The results from two quadratic elements for (1) Dirichelt (2) Neumann and (3)
Mixed boundary condtions for the second-order ordinary differentail equation. Dashed curves
with open squares are finite element solutions, and continuous curves are analytical solutions.
1 ∂ ∂u 1 ∂2u ∂2u
∇ 2 u = --- ----- r ------ + ---2- --------2- + --------2 Eq. 4•23
r ∂r ∂r r ∂θ ∂z
1. see for example p. 667, Eq (II.4.C4) in L.E. Malvern, 1969, “Introduction to the mechanics of a continuous medium”,
Prentice-Hall, Inc., Englewood Cliffs, N.J.
2. example in p. 364-367 in J.N. Reddy, 1986, “Applied functional analysis and variational methods in engineering”,
McGraw-Hill, Inc.
50mm
31.6mm
κ=5 κ=1
20mm r 100oC 0oC
20mm 31.6mm 50mm
κ=5
κ=1
Replace dΩ = 2πr dr in the volume integral, the element stiffness matrix in Eq. 4•9 and Eq. 4•10 is obtained by
integration by parts of the weighted-residual statement with Eq. 4•24
dφ e dφ e
ke = ∫ κ -------
dr
- ⊗ -------- 2πrdr
dr
Eq. 4•25
where “Nr” is the derivative of shape functions “N” with respect to “r”. This is implemented in Program Listing
4•3. The results are shown in Figure 4•14.
100
80
T oC 60
40
20
r
25 30 35 40 45 50
Figure 4•14The solution of heat conduction of an axisymmetrical problem with two
hollow cylinders.
#include "include\fe.h"
static const int node_no = 9; static const int element_no = 8; static const int spatial_dim_no = 1;
Omega_h::Omega_h() {
Definte discretizaed global domain
double r[9] = {20.0, 22.6, 25.1, 28.4, 31.6, 35.7, 39.8, 44.9, 50.0};
for(int i = 0; i < node_no; i++) { define 9 nodes
Node* node = new Node(i, spatial_dim_no, r+i); the_node_array.add(node); }
int ena[2], material_type_no;
for(int i = 0; i < element_no; i++) { define 8 elements
ena[0] = i; ena[1] = ena[0]+1;
if(i < element_no / 2) material_type_no = 0; else material_type_no = 1;
Omega_eh* elem = new Omega_eh(i, 0, material_type_no, 2, ena);
the_omega_eh_array.add(elem);
}
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
define boundary conditions
__initialization(df, omega_h); u(20) = 100, u(50) = 0
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(0)][0] = 100.0;
the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(node_no-1)][0] = 0.0;
}class ODE_Cylindrical_Coordinates : public Element_Formulation {
public:
ODE_Cylindrical_Coordinates(Element_Type_Register a) : Element_Formulation(a) {}
instantiate fixed and free variables and
Element_Formulation *make(int, Global_Discretization&); Global_Discretization
ODE_Cylindrical_Coordinates(int, Global_Discretization&);
};
Element_Formulation* ODE_Cylindrical_Coordinates::make(int en, Global_Discretization& gd) {
return new ODE_Cylindrical_Coordinates(en,gd); }
static const double PI = 3.14159265359; static const double kapa[2] = {5.0, 1.0};
ODE_Cylindrical_Coordinates::ODE_Cylindrical_Coordinates(int en, Global_Discretization& gd)
: Element_Formulation(en, gd) {
Quadrature qp(spatial_dim_no, 2); Define user element “ODE_2nd_Order”
H1 Z(qp),
N=INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 2, 1, qp);
N[0] = (1-Z)/2; N[1] = (1+Z)/2;
1d Gauss Quadrature
H1 r = N*xl;
H0 Nr = d(N)(0)/d(r); N0= (1-ξ) / 2, N1= (1+ξ) / 2
J dr(d(r));
stiff &= ( ( kapa[material_type_no]*2.0*PI*((H0)r) ) * (Nr%Nr) ) | dr;
coordinate transformation rule
} N,x, and the Jacobian
Element_Formulation* Element_Formulation::type_list = 0;
dφe dφ e
∫ κ -------
- ⊗ -------- 2πrdr
static Element_Type_Register element_type_register_instance;
ke =
static ODE_Cylindrical_Coordinates ode_cylindrical_instance(element_type_register_instance); dr dr
int main() {
const int ndf = 1; Omega_h oh; gh_on_Gamma_h gh(ndf, oh); register element
U_h uh(ndf, oh); Global_Discretization gd(oh, gh, uh);
Matrix_Representation mr(gd);
Matrix Form
mr.assembly(); assembly all elements
C0 u = ((C0)(mr.rhs())) / ((C0)(mr.lhs())); solve linear algebraic equations
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h();
cout << gd.u_h();
update solution and B.C.
return 0; output
}
Listing 4•3 Axisymmetrical problem using cylindrical coordinates for the differential equation - u” = 0
(project: “cylindrical_ode” in project workspace file “fe.dsw” under directory “vs\ex\fe”).
and the shear force V is equal to the derivative of bending moment (M) as
Therefore,
2
dM
= f Eq. 4•28
dx2
The transverse deflection of the beam is denoted as w, and the curvature (d2w/dx2) of the beam is related to the
bending moment “M” and the flexure rigidity “EI” as
2
dw M
= --------- Eq. 4•29
dx2 2EI
Substituting “M” in Eq. 4•29 into Eq. 4•28 gives the fourth-order ordinary differential equation
2
d d w
2
EI = f, 0<x<L Eq. 4•30
d x2 d x2
We consider a boundary value problem that the Eq. 4•30 is subject to the boundary conditions1
2 2
dw dw d d w
w( 0 ) = ( 0 ) = 0, EI 2 ( L ) = M, – EI (L) = V( L) = 0 Eq. 4•31
dx dx dx d x2
In the previous chapter, we solved this boundary value problem using Rayleigh-Ritz method with four weak for-
mulations—(1) irreducible formulation, (2) mixed formulation, (3) Lagrange multiplier formulation, and (4)
penalty function formulation. We use finite element method in this section to implement these four weak formu-
lations.
1. J.N. Reddy, 1986, “Applied functional analysis and variational methods in engineering”, McGraw-Hill, Inc.
2 2
EI d w dw
J( w ) = ∫ ------ – fw dx – w V Γh – M Γh
2 d x2 dx
Eq. 4•32
Ω
The last two terms are natural boundary conditions generated from integration by parts. Using δw = εv, where ε
is a small real number. Taking the variation of J and setting δJ(u) = 0 gives
2
d 2 δw d w
– δwf dx – δ wV Γh + – ----------- M Γh
dδw
δJ ( w ) = ∫ -
EI ------------
dx 2 d x 2 dx
Ω
2 2
d v d w
= ε ∫ EI 2 2 – vf dx – v V Γh + – ------ M Γ h = 0
dv
dx
Ω d x d x
2 2
d v d w
EI 2 2 – vf dx – vV Γh + – ------ M Γ h = 0
dv
∫ dx dx dx
Eq. 4•33
Ω
The integrand of Eq. 4•33 contains derivative of variables up to second order. For this equation to be integrable
through out Ω, we have to require that the first derivative of the variable be continuous through out the integra-
tion domain. If the first derivative of the variable is not continuous at any point on the integration domain and its
boundaries, the second derivative of the variable on that point will be infinite, therefore, Eq. 4•33 is not integra-
ble. In other words, the first derivative of the variable at nodal points should be required to be continuous. This
is to satisfy the so-called continuity requirement. For example, we consider a two nodes line element with two
degrees of freedom associated with each notes. That is the nodal degrees of freedom are set to be û e = [w0, -dw0/
dx, w1, -dw1/dx] on the two nodes. The node numbers are indicated by subscripts “0” and “1”. The variables,
defined in an element domain, are defined as
1. see derivation in p. 383 in J.N. Reddy, 1986, “Applied functional analysis and variational methods in engineering”,
McGraw-Hill, Inc.
ξ 2
φ e1 = – ξ 1 – -----
h e
ξ 2 ξ 3
φ e2 = 3 ----- – 2 -----
he he
ξ 2 ξ
φe3 = – ξ ----- – ----- Eq. 4•35
h e h e
2 2
d φ e d φ e
k e = a ( φ e, φ e ) = ∫ EI 2 ⊗ 2 dx
dx dx
Eq. 4•36
Ωe
dw dw
where essential boundary conditions are u e = [w0, – , w1, – ], and
dx 0 dx 1
where P = {V0,- M0, VL, -ML}T is the natural boundary conditions on boundary shear forces and boundary bend-
ing moments. Notice that in the previous chapter we take counter clockwise direction as positive for bending
moment. The sign convention taken here for the bending moment is just the opposite. The natural boundary con-
ditions are programmed to automatically taken care of in “Matrix_Representation::assembly()” where the left-
hand-side is assumed to be a positive term instead of what happened in the left-hand-side of Eq. 4•43. This is the
reason of take a minus sign in front of M for the definition of the vector P. The Program Listing 4•4 implemented
the irreducible formulation for the beam bending problem.
The solutions of the transverse deflection w and slope -dw/dx can be calculated from nodal values according
to Eq. 4•34. They are almost identical to the exact solutions in Figure 3•16 and Figure 3•17 of the last chapter in
page 208 and page 212, respectively. Therefore, the error instead are shown in Figure 4•15. Note that the exact
2. or alternative form from p. 49 in T.J.R. Hughes, 1987,”The finite element method: Linear static and dynamic finite element
analysis”, Prentice-Hall, Inc.
#include "include\fe.h"
static const int node_no = 5; static const int element_no = 4; static const int spatial_dim_no = 1;
static const double L_ = 1.0; static const double h_e = L_/((double)(element_no));
static const double E_ = 1.0; static const double I_ = 1.0; static const double f_0 = 1.0;
static const double M_ = -1.0;
Omega_h::Omega_h() {
for(int i = 0; i < node_no; i++) {
Definte discretizaed global domain
double v = ((double)i)*h_e; define nodes
Node* node = new Node(i, spatial_dim_no, &v); the_node_array.add(node); }
int ena[2];
for(int i = 0; i < element_no; i++) {
define elements
ena[0] = i; ena[1] = ena[0]+1;
Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena); the_omega_eh_array.add(elem); }
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
define boundary conditions
__initialization(df, omega_h); M(L) = -1 (positive clockwise)
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(0)](1) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(node_no-1)][1] = M_;
} instantiate fixed and free variables and
static const int ndf = 2; static Omega_h oh; static gh_on_Gamma_h gh(ndf, oh); Global_Discretization
static U_h uh(ndf, oh); static Global_Discretization gd(oh, gh, uh);
class Beam_Irreducible_Formulation : public Element_Formulation {
public:
Beam_Irreducible_Formulation(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Beam_Irreducible_Formulation(int, Global_Discretization&);
}; “Beam_Irreducible_Formulation”
Element_Formulation* Beam_Irreducible_Formulation::make(int en,Global_Discretization& gd) { Simpson’s rule
return new Beam_Irreducible_Formulation(en,gd); }
Beam_Irreducible_Formulation::Beam_Irreducible_Formulation(int en, Global_Discretization&
Hermit cubics
gd) : Element_Formulation(en, gd) {
ξ 2 ξ 3
double weight[3] = {1.0/3.0, 4.0/3.0, 1.0/3.0}, φ e0 = 1 – 3 ----- + 2 -----
h_e = fabs( ((double)(xl[0] - xl[1])) ); h e h e
Quadrature qp(weight, 0.0, h_e, 3);
J d_l(h_e/2.0);
ξ 2
H2 Z((double*)0, qp), z = Z/h_e, φ e1 = – ξ 1 – -----
N = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE( h e
"int, int, Quadrature", 4/*nen x ndf*/, 1/*nsd*/, qp);
N[0] = 1.0-3.0*z.pow(2)+2.0*z.pow(3); N[1] = -Z*(1.0-z).pow(2);
ξ 2 ξ 3
N[2] = 3.0*z.pow(2)-2.0*z.pow(3); N[3] = -Z*(z.pow(2)-z); φ e2 = 3 ----- – 2 -----
H0 Nxx = INTEGRABLE_VECTOR("int, Quadrature", 4, qp); h e h e
for(int i = 0; i < 4; i++) Nxx[i] = dd(N)(i)[0][0];
stiff &= ( (E_*I_)* (Nxx*(~Nxx)) ) | d_l;
ξ 2 ξ
force &= ( ((H0)N) * f_0) | d_l; φ e3 = – ξ ----- – -----
} h e h e
Element_Formulation* Element_Formulation::type_list = 0;
2 2
static Element_Type_Register element_type_register_instance; d φ e d φ e
static Beam_Irreducible_Formulation beam_irreducible_instance(element_type_register_instance);
static Matrix_Representation mr(gd);
ke = ∫ EI 2 ⊗ 2 dx
dx dx
Ωe
int main() {
mr.assembly(); C0 u= ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
}
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h(); cout << gd.u_h(); return 0; fei = ∫ φei fdx
Ωe
Listing 4•4 Beam-bending problem irreducible formulation using Hermit cubics (project:
“beam_irreducible_formulation” in project workspace file “fe.dsw” under directory “vs\ex\fe”).
Figure 4•15 The error (= exact solution - finite element solution) of the irreducible
formulation for beam bending problem.
solution of the transverse deflection w is a polynomial of x up to fourth-order (see Eq. 3•68 in page 207). The
cubic approximation will not give solution identical to the exact solution.
We consider two more examples for different types of boundary conditions and loads.1 The first example is to
have unit downward nodal load on a simply supported beam at location of x = 120 in. (Figure 4•16). The flexure
rigidity of the beam is EI = 3.456x1010 lb in.2 The length of the beam is 360 in. We divide the beam to two cubic
Hermit elements. The definitions of the problem is now
1 static const int node_no = 3; static const int element_no = 2; static const int spatial_dim_no = 1;
2 static const double L_ = 360.0; static const double E_I_ = 144.0*24.0e6;
3 Omega_h::Omega_h() { // discritized global 4domain
5 double v = 0.0; Node* node = new Node(0, spatial_dim_no, &v);
6 the_node_array.add(node);
7 v = 120.0; node = new Node(1, spatial_dim_no, &v);
8 the_node_array.add(node);
9 v = 360.0; node = new Node(2, spatial_dim_no, &v);
10 the_node_array.add(node);
P = -1.0 lb
flexure rigidity (EI) = 3.456x1010 lb in.2
0 0 1 1 2
Figure 4•16 Unit downward nodal loading on position x = 120. The flexure
rigidity of the beam is 3.456x1010. Two cubic Hermict elements are used.
1. Example problems from p. 390 in J.N. Reddy, 1986, “Applied functional analysis and variational methods in engineering”,
McGraw-Hill, Inc.
Now in the computation for element force vector, you can either set f_0 = 0.0, or use conditional compilation,
with macro definition, to leave that line out. The results of this problem is shown in Figure 4•17.
The second example have distributed load
x
f ( x ) = f 0 --- Eq. 4•39
L
where L = 180 in. and set f0 = -1.0. This distributed load is a linear downward loading increases from zero at the
left to unity at the right. The moment of inertia is I = 723 in.4, and Young’s modulus is E = 29x106 psi. with
boundary conditions w(0) = w(L) = dw/dx (L) = 0. We divide the beam into four equal size cubic Hermit ele-
ments. The problem definitions for nodes, elements, and boundary conditions are
1 static const int node_no = 5; static const int element_no = 4; static const int spatial_dim_no = 1;
2 static const double L_ = 180.0; static const double element_size = L_/((double)(element_no));
3 static const double E_ = 29.0e6; static const double I_ = 723.0; static const double f_0 = -1.0;
4 Omega_h::Omega_h() { // discritized global domain
-0.0001 dw
w 50 100 150 200 250 300 350 x
dx
-0.00015
-6
-1. 10
-0.0002
-6
-2. 10
Figure 4•17 Finite element solution for the nodal load problem for irreducible
formulation of beam bending problem.
In the constructor of the class Beam_Irreducible_Formulation the element force vector is computed as
The results of this distributed load problem using the irreducible formulation are shown in Figure 4•18. These
two extra problems are actually coded in the same project “beam_irreducible_formulation” in project workspace
file “fe.dsw” (in case of MSVC) under directory “vs\ex\fe”. They can be activated by setting corresponding
macro definitions at compile time.
x
25 50 75 100 125 150 175
-0.00002 -6
1. 10
-0.00004
w dw
-0.00006
x
25 50 75 100 125 150 175
dx
-0.00008 -6
-1. 10
-0.0001
-6
-0.00012 -2. 10
Figure 4•18 Finite element solution of the distributed load problem for the irreducible formulation of beam
bending problem. The distributed load is a linear downward loading increases from zero at the left to unit
load at the right.
2 2
dw M dM
= ---------, and = f Eq. 4•40
dx2 2EI dx2
L
M2
∫ d x d x + --------- + fw dx – M Γ – w Γ
dw dM dw dM
J M ( w, M ) = Eq. 4•41
2EI dx h dx h
0
dM dw
where the boundary conditions on the shear force and slope are V = – and ψ = –
dx dx
The Euler-Lagrange equations are obtained by setting δJ(w, M)= 0 (where δw = εw vw and δM = εM vM)
L
dv w dM
∫ d x + v w f dx – v w Γ
dM
δ w J M = εw = 0
dx d x h
0
L
dv M dw
∫ d x + v M ------ dx – v M Γ = 0
M dw
δ M J M = εM Eq. 4•42
dx EI dx h
0
For the Bubnov-Galerkin method we use interpolation functions φ ew for both w and vw, and interpolation func-
tions φ eM for both M and vM. In matrix form finite element formulation from Eq. 4•42 is (dropping εw and εM)
1. p. 310 in T.J.R. Hughes, 1987, “The finite element method: Linear static and dynamic finite element analysis”, Prentice-
Hall, inc., Englewood cliffs, New Jersey.
dφ ew dφ eM
---------
- ⊗ ---------- dx
0 ∫ dx dx
∫ –φew fdx – φew VΓ
Ωe ŵ e h
= Ωe Eq. 4•43
dφ eM dφ ew φ eM ⊗ φ eM ˆ
---------- ⊗ ---------- dx ---------------------
- dx
M
∫ ∫ – φ eM ψ Γh
e
dx dx EI
Ωe Ωe
The natural boundary conditions specified through V is hard-wired in “fe.lib” to be automatically taken care of in
“Matrix_Representation::assembly()” where the left-hand-side is assumed to be a positive term instead of what
happened in the left-hand-side of Eq. 4•43. We can choose to take an opposite sign convention on the boundary
condition as what we have done for the bending moment boundary condition in the irreducible formulation. The
disadvantage of doing that is that we have put the burden on user to specify the program correctly. That may
often cause serious confusion. Therefore, we prefer to make the sign of Eq. 4•43 to be consistent with what is
done in the “assembly()” by changing sign as
dφ ew dφ eM
0 – ∫ ---------- ⊗ ---------- dx
Ωe
dx dx
ŵ e ∫ φew fdx + φew VΓ h
= Ωe Eq. 4•44
dφ M dφ ew φM ⊗ φ eM ˆ
e
– ∫ ---------- ⊗ ---------- dx – ∫ e
---------------------- dx
M e
φ eM ψ Γ h
dx dx EI
Ωe Ωe
The Program Listing 4•5 implement the beam bending problem subject to boundary conditions in Eq. 4•31, using
Eq. 4•44. In finite element convention, the degree of freedoms for a node are packed together. We can re-arrange
the degree of freedom, for every node, corresponding to the essential boundary conditions as {w, M}T, and natu-
ral boundary conditions are {V, ψ}T The Eq. 4•44 becomes
0 a 00 0 a 01 ŵ 0 f0
T
a 00 T
b 00 a 01 b 01 ˆ
M r0
0
= Eq. 4•45
0 a 10 0 a 11 ŵ 1 f1
T b
a 10 T ˆ r1
10 a 11 b 11 M 1
where subscripts indicate the element nodal number and each component in the matrix or vectors is defined as
dφ iw dφ jM φ iM φ jM
a ij = – ∫ ---------- ---------- dx, b ij = – ∫ --------------- dx, f i = ∫ φiw fdx + φiw VΓ , and r i = φ iM ψ Γh Eq. 4•46
dx dx EI h
Ωe Ωe Ωe
The submatrix/subvector component access through either continuous block selector “operator ()(int, int)” or
regular increment selector “operator[](int)” in VectorSpace C++ library makes the coding in the formula of
either Eq. 4•44 or Eq. 4•45 equally convenient.
#include "include\fe.h"
static const int node_no = 5; static const int element_no = node_no-1;
static const int spatial_dim_no = 1; static const double L_ = 1.0;
static const double h_e = L_/((double)(element_no)); static const double E_ = 1.0;
static const double I_ = 1.0; static const double f_0 = 1.0; static const double M_ = 1.0;
Omega_h::Omega_h() {
for(int i = 0; i < node_no; i++) {
Definte discretizaed global domain
double v = ((double)i)*h_e; define nodes
Node* node = new Node(i, spatial_dim_no, &v); the_node_array.add(node); }
int ena[2];
for(int i = 0; i < element_no; i++) {
define elements
ena[0] = i; ena[1] = ena[0]+1;
Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena); the_omega_eh_array.add(elem); }
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
define boundary conditions
__initialization(df, omega_h); M(L) = 1
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet; // w(0) = 0
the_gh_array[node_order(node_no-1)](1) = gh_on_Gamma_h::Dirichlet; // M(L) = M_
the_gh_array[node_order(node_no-1)][1] = M_;
} instantiate fixed and free variables and
class Beam_Mixed_Formulation : public Element_Formulation { Global_Discretization
public:
Beam_Mixed_Formulation(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Beam_Mixed_Formulation(int, Global_Discretization&);
};
Element_Formulation* Beam_Mixed_Formulation::make( int en, Global_Discretization& gd) {
return new Beam_Mixed_Formulation(en,gd); } “Beam_Mixed_Formulation”
Beam_Mixed_Formulation::Beam_Mixed_Formulation(int en, Global_Discretization& gd)
: Element_Formulation(en, gd) {
Quadrature qp(spatial_dim_no, 2);
H1 Z(qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 2/*nen*/, 1/*nsd*/, qp);
N[0] = (1-Z)/2; N[1] = (1+Z)/2;
H1 X = N*xl;
H0 Nx = d(N)(0)/d(X); J d_l(d(X)); φ ew = φ eM = {(1-ξ)/2, (1+ξ)/2}T
stiff &= C0(4, 4, (double*)0); C0 stiff_sub = SUBMATRIX("int, int, C0&", 2, 2, stiff);
stiff_sub[0][1] = -(Nx * (~Nx)) | d_l; stiff_sub[1][0] = stiff_sub[0][1];
stiff_sub[1][1] = -(1.0/E_/I_)* ( (((H0)N)*(~(H0)N)) | d_l );
force &= C0(4, (double*)0); C0 force_sub = SUBVECTOR("int, C0&", 2, force);
force_sub[0] = ( (((H0)N)*f_0) | d_l );
dφ ew dφeM
k e10 = k e01 = – ∫ ---------- ⊗ ---------- dx
}
Element_Formulation* Element_Formulation::type_list = 0; dx dx
static Element_Type_Register element_type_register_instance; Ωe
static Beam_Mixed_Formulation beam_mixed_instance(element_type_register_instance);
φM
⊗ φ eM
k e11 = – ∫ ---------------------- dx
e
int main() {
const int ndf = 2; EI
Omega_h oh; gh_on_Gamma_h gh(ndf, oh); Ωe
U_h uh(ndf, oh); Global_Discretization gd(oh, gh, uh);
Matrix_Representation mr(gd);
mr.assembly(); C0 u = ((C0)(mr.rhs()))/((C0)(mr.lhs())); f e0 = ∫ φew fdx
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h(); cout << gd.u_h(); Ωe
return 0;
Listing 4•5 Beam-bending problem mixed formulation using linear line element (project:
“beam_mixed_formulation” in project workspace file “fe.dsw” under directory “vs\ex\fe”).
0.5 1.4
0.4
1.3
w 0.3 M
1.2
0.2
1.1
0.1
Figure 4•19 Transverse deflection “w” and bending moment “M” from mixed
formulation. The dashed line segments with open squares are finite element
solutions, and the solid curves are the exact solutions.
The results are shown in Figure 4•19. The solutions at the nodal points match the exact solutions of the trans-
verse deflection and the bending moment. That is,
1 static const int node_no = 3; static const int element_no = 2; static const int spatial_dim_no = 1;
2 static const double L_ = 360.0; static const double E_ = 24.0e6; static const double I_ = 144.0;
3 Omega_h::Omega_h() {
4 double v = 0.0; Node* node = new Node(0, spatial_dim_no, &v); the_node_array.add(node);
5 v = 120.0; node = new Node(1, spatial_dim_no, &v); the_node_array.add(node);
6 v = 360.0; node = new Node(2, spatial_dim_no, &v); the_node_array.add(node);
7 int ena[2];
8 for(int i = 0; i < element_no; i++) {
9 ena[0] = i; ena[1] = ena[0]+1;
10 Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena);
11 the_omega_eh_array.add(elem);
12 }
13 }
14 gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
15 __initialization(df, omega_h);
16 the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet; // w(0) = 0
17 the_gh_array[node_order(0)](1) = gh_on_Gamma_h::Dirichlet; // M(0) = 0
18 the_gh_array[node_order(1)](0) = gh_on_Gamma_h::Neumann; // V(120) = -1.0; shear force
19 the_gh_array[node_order(1)][0] = -1.0;
20 the_gh_array[node_order(2)](0) = gh_on_Gamma_h::Dirichlet; // w(360) = 0
21 the_gh_array[node_order(2)](1) = gh_on_Gamma_h::Dirichlet; // M(360) = 0
22 }
-0.00005 60
-0.0001
w M 40
-0.00015
20
-0.0002
Figure 4•20Transverse deflection w and bending moment M for the nodal loading
problem using linear interpolation functions for both w and M.
For the element force vector we can either set f_0 = 0 or just comment out the corresponding statement for effi-
ciency. The result of the nodal loading case is shown in Figure 4•20. The bending moment solution is exact for
this case.
The problem definition in C++ code for the distributed loading case is
1 static const int node_no = 5; static const int element_no = 4; static const int spatial_dim_no = 1;
2 static const double L_ = 180.0; static const double element_size = L_/((double)(element_no));
3 static const double E_ = 29.0e6; static const double I_ = 723.0; static const double f_0 = -1.0;
4 Omega_h::Omega_h() {
5 for(int i = 0; i < node_no; i++) {
6 double v = ((double)i)*element_size;
7 Node* node = new Node(i, spatial_dim_no, &v); the_node_array.add(node);
8 }
9 int ena[2];
10 for(int i = 0; i < element_no; i++) {
11 ena[0] = i; ena[1] = ena[0]+1;
12 Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena); the_omega_eh_array.add(elem);
13 }
14 }
15 gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
16 __initialization(df, omega_h);
17 the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet; // w(0) = 0
18 the_gh_array[node_order(0)](1) = gh_on_Gamma_h::Dirichlet; // M(0) = 0
19 the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Dirichlet; // w(L) = 0
20 the_gh_array[node_order(node_no-1)](1) = gh_on_Gamma_h::Neumann; // dw/dx(L) = 0
21 }
1 H0 f = (f_0/L_)*((H0)X);
2 force &= C0(4, (double*)0);
3 C0 force_sub = SUBVECTOR("int, C0&", 2, force);
4 force_sub[0] = ( (((H0)N)*f) | d_l );
x 1000
25 50 75 100 125 150 175
-0.00002 500
-0.00004 M x
25 50 75 100 125 150 175
w -0.00006
-500
-0.00008
-1000
-0.0001
-1500
-0.00012
-2000
In the irreducible formulation, we are required to include the higher-order derivatives be interpolated using
the abstruse cubic Hermite functions. In the mixed formulation this requirement is relaxed. However, both the
irreducible and the mixed formulation require one more variable (-dw/dx, and M, respectively) to be solved
together with w. This increases the number of degrees of freedom in the matrix solution process. This can be dis-
advantageous for a large-size problem.
2 2
EI d w dw
J( w ) = ∫ ------ – fw dx – w V Γh – M Γh
2 d x2 dx
Eq. 4•48
Ω
Now, in the context of constrained optimization discussed in Chapter 2, we define constraint equation for nega-
tive slope ψ that
dw
C ( ψ, w ) ≡ ψ + ------- = 0 Eq. 4•49
dx
dw
Substituting ψ = – ------- into Eq. 4•48, we have
dx
EI dψ 2
J ( ψ, w ) = ∫ ------
2 dx
– fw dx – w V Γh + ψM Γh Eq. 4•50
Ω
The minimization of Eq. 4•50 subject to constraint of Eq. 4•49 using Lagrange multiplier method (with the
Lagrange multiplier λ) leads to the Lagrangian functional in the form of Eq. 2•11 of Chapter 2 in page 118 as
EI dψ 2
– fw dx + ∫ λ ψ + ------- dx – w VΓ h + ψM Γ h
dw
( ψ, w, λ ) ≡ J ( ψ, w ) + λ C ( ψ, w ) = ∫ ------
2 dx dx
Eq. 4•51
Ω Ω
The Euler-Lagrange equations are obtained from δL = 0 as (where δψ = εψ vψ, δw = εw vw, and δλ = ελ vλ)
dv ψ dψ
δψ = εψ ∫ EI ---------
dx d x
dx + ∫ v ψ λ dx + v ψ M Γh = 0
Ω Ω
dv w
δw = ε w – ∫ v w f dx + ∫ ---------- λ dx – v w V Γh = 0
dx
Ω Ω
= ε λ ∫ v λ ψ + ------- dx = 0
dw
δλ Eq. 4•52
dx
Ω
Dropping the arbitrary constants of εψ, εw, and ελ and use interpolation functions for each of the variables {ψ, w,
λ}T we have, in matrix form, the finite element formulation as
dφ eψ dφ eψ
EI ∫ ---------- ⊗ ---------- dx 0 ∫ φeψ ⊗ φeλ dx
dx dx
Ωe Ωe – φeψ M Γh
ψ̂ e
dφ ew
0 0 ∫ ---------
dx
- ⊗ φ eλ dx ŵ e = ∫ φew f dx + φew VΓ h
Eq. 4•53
Ωe Ωe
λ̂ e
dφ ew 0
∫ φeλ ⊗ φeψ dx ∫ φ eλ ⊗ ---------- dx
dx
0
Ωe Ωe
Again, the bending moment boundary conditions appears on the right-hand-side of the first equation is negative.
This is in conflict with the nodal loading input is positive on the right-hand-side assumed in the implementation
of the “Matrix_Rxpresentation::assembly()”. In order to keep the convention of counter clock-wise rotation as
positive, we can change sign on the first row of Eq. 4•53 as
dφ eψ dφ eψ
– E I ∫ ---------- ⊗ ---------- dx 0 – ∫ φeψ ⊗ φ eλ dx
dx dx
Ωe Ωe φ eψ M Γh
ψ̂ e
dφ ew
0 0 ∫ ---------
dx
- ⊗ φ eλ dx ŵ e = ∫ φew f dx + φew VΓ h
Eq. 4•54
Ωe Ωe
λ̂ e
dφew 0
∫ φeλ ⊗ φeψ dx ∫ φ eλ ⊗ ---------- dx
dx
0
Ωe Ωe
Again, the degree of freedoms for each node can be packed together just as in Eq. 4•45. With the aid of the regu-
lar increment selector “operator[](int), the Eq. 4•54 is sufficient clear without really needing to rewrite to the
form of Eq. 4•45. The Program Listing 4•6 implemented the Eq. 4•54 with linear interpolation functions
{ φ eψ, φ ew, φ eλ }T for all three variables. The essential boundary conditions are {ψ, w, λ}T, and the natural boundary
conditions are {M, V, 0}T The results are shown in Figure 4•22 which are compared to the exaction solutions.
2M + fL 2
w ( x ) = ---------------------- x 2 – --------- x 3 + ------------ x 4
fL f
4EI 6EI 24EI
– ( 2M + fL 2 ) fL f
ψ ( x ) = -----------------------------
2EI
- x + --------- x 2 – --------- x 3
2EI 6EI
λ( x) = f ( L – x ) Eq. 4•55
ψ and λ is obtained by differentiating the exact solution of w(x) in the first line from the corresponding defini-
tions. The shear force solution, the lagrange multiplier λ per se, coincides with the exact solution..
#include "include\fe.h"
static const int node_no = 5; static const int element_no = 4; static const int spatial_dim_no = 1;
static const double L_ = 1.0; static const double h_e = L_/((double)(element_no));
static const double E_ = 1.0; static const double I_ = 1.0; static const double f_0 = 1.0;
static const double M_ = 1.0;
Omega_h::Omega_h() {
for( int i = 0; i < node_no; i++) {
Definte discretizaed global domain
double v = ((double)i)*h_e; define nodes
Node* node = new Node(i, spatial_dim_no, &v); the_node_array.add(node); }
for( int i = 0; i < element_no; i++) {
int ena[2]; ena[0] = i; ena[1] = ena[0]+1;
define elements
Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena); the_omega_eh_array.add(elem); }
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h);
define boundary conditions
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet; // psi(0) = -dw/dx(0) = 0
the_gh_array[node_order(0)](1) = gh_on_Gamma_h::Dirichlet; // w(0) = 0
the_gh_array[node_order(node_no-1)](2) = gh_on_Gamma_h::Dirichlet; // lambda(L) = 0;
the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Neumann; // M(L) = M_
the_gh_array[node_order(node_no-1)][0] = M_; // end bending moment M(L) = 1
} instantiate fixed and free variables and
class Beam_Lagrange_Multiplier_Formulation : public Element_Formulation {
public:
Global_Discretization
Beam_Lagrange_Multiplier_Formulation(Element_Type_Register a)
: Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Beam_Lagrange_Multiplier_Formulation(int, Global_Discretization&);
};
Element_Formulation* Beam_Lagrange_Multiplier_Formulation::make(int en,
Global_Discretization& gd) { return new Beam_Lagrange_Multiplier_Formulation(en,gd); }
Beam_Lagrange_Multiplier_Formulation::Beam_Lagrange_Multiplier_Formulation(int en,
“Beam_Lagrange_Multiplier_Formulati
Global_Discretization& gd) : Element_Formulation(en, gd) { on”
Quadrature qp(spatial_dim_no, 2);
H1 Z(qp), N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 2/*nen*/, 1/*nsd*/, qp);
N[0] = (1-Z)/2; N[1] = (1+Z)/2;
H1 X = N*xl; H0 Nx = d(N)(0)/d(X); J d_l(d(X)); φ eψ = φew = φ eλ = {(1-ξ)/2, (1+ξ)/2}T
stiff &= C0(6, 6, (double*)0); C0 stiff_sub = SUBMATRIX("int, int, C0&", 3, 3, stiff);
dφeψ dφ eψ
k e00 = – ∫ EI ---------- ⊗ ---------- dx
stiff_sub[0][0] = -((E_*I_) * Nx * (~Nx)) | d_l; stiff_sub[0][2] = -(((H0)N) % ((H0)N)) | d_l;
stiff_sub[2][0] = -( ~stiff_sub[0][2] ); stiff_sub[1][2] = (Nx % ((H0)N)) | d_l; dx dx
stiff_sub[2][1] = ~stiff_sub[1][2]; Ωe
force &= C0(6, (double*)0); C0 force_sub = SUBVECTOR("int, C0&", 3, force);
dφeψ dφ eλ
ke02 = – ( k e20 ) T = – ∫ ---------- ⊗ --------- dx
force_sub[1] = (((H0)N)*f_0) | d_l;
} dx dx
Element_Formulation* Element_Formulation::type_list = 0; Ωe
static Element_Type_Register element_type_register_instance;
dφew
∫ ---------
static Beam_Lagrange_Multiplier_Formulation lagrange(element_type_register_instance);int
main() { k e12 = ( k e21 ) T = - ⊗ φ eλ dx
dx
const int ndf = 3; Omega_h oh; gh_on_Gamma_h gh(ndf, oh); Ωe
U_h uh(ndf, oh); Global_Discretization gd(oh, gh, uh);
Matrix_Representation mr(gd);
mr.assembly(); C0 u = ((C0)(mr.rhs()))/((C0)(mr.lhs())); f e1 = ∫ φew fdx
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h();cout << gd.u_h(); return 0; Ωe
}
Listing 4•6 Beam-bending problem Lagrange multipler formulation using linear line element (project:
“beam_lagrange_multiplier” in project workspace file “fe.dsw” under directory “vs\ex\fe”).
-0.4 0.4
ψ w λ0.6
-0.6 0.3
0.4
-0.8 0.2
0.2
-1 0.1
Figure 4•22 Lagrange multiplier formulation for beam bending problem using
linear interpolation function for all three variables.
The problem definitions for the nodal load case can be coded as the followings
1 static const int node_no = 4; static const int element_no = node_no-1; static const int spatial_dim_no = 1;
2 static const double L_ = 360.0; static const double E_ = 24.0e6; static const double I_ = 144.0;
3 static const double P_ = 1.0;
4 Omega_h::Omega_h() {
5 double v = 0.0; Node* node = new Node(0, spatial_dim_no, &v); the_node_array.add(node);
6 v = 120.0; node = new Node(1, spatial_dim_no, &v); the_node_array.add(node);
7 v = 240.0; node = new Node(2, spatial_dim_no, &v); the_node_array.add(node);
8 v = 360.0; node = new Node(3, spatial_dim_no, &v); the_node_array.add(node);
9 for(int i = 0; i < element_no; i++) {
10 int ena[2]; ena[0] = i; ena[1] = ena[0]+1;
11 Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena); the_omega_eh_array.add(elem);
12 }
13 }
14 gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
15 __initialization(df, omega_h);
16 the_gh_array[node_order(0)](1) = gh_on_Gamma_h::Dirichlet; // w(0) = 0
17 the_gh_array[node_order(1)](1) = gh_on_Gamma_h::Neumann; // f(120) = - P; shear force
18 the_gh_array[node_order(1)][1] = -P_;
19 the_gh_array[node_order(node_no-1)](1) = gh_on_Gamma_h::Dirichlet; // w(360) = 0
20 }
Again, we can just comment out the element force vector computation in the constructor of class
Beam_Lagrange_Multiplier for efficiency. The results are shown in Figure 4•23. The solution for this boundary
condition case is not acceptable. The exact solution shear force is constant within each element, while we use lin-
ear interpolation functions for the shear force. The problem is overly constrained. On the other hand, the slope
and transverse deflection require higher order of interpolation functions than the linear functions. The choice of
different order of interpolation functions and the number of nodes per variable/per element to obtain a meaning-
ψ w λ exact soln.
-6
2.5 10 50 100 150 200 250 300 350 x
-6 0.5
2. 10
-6 -0.00005
1.5 10
50 100 150 200 250 300 350
x
-6
1. 10
-7 -0.5
-0.0001
5. 10
-1
50 100 150 200 250 300 350 x
-7 -0.00015
-5. 10 -1.5
-6
-1. 10
-0.0002 -2
Figure 4•23 The Lagrange multiplier method with all three variables interpolated
using linear element for the nodal load problem does not produce satisfactory
result.
ful result depends on the so-called LBB-condition in finite element method that we will discussed in details in
Section 4.4
The distributed load case is defined as
1 static const int node_no = 5; static const int element_no = 4; static const int spatial_dim_no = 1;
2 static const double L_ = 180.0; static const double element_size = L_/((double)(element_no));
3 static const double E_ = 29.0e6; static const double I_ = 723.0; static const double f_0 = -1.0;
4 Omega_h::Omega_h() {
5 for(int i = 0; i < node_no; i++) {
6 double v = ((double)i)*element_size;
7 Node* node = new Node(i, spatial_dim_no, &v); the_node_array.add(node);
8 }
9 for(int i = 0; i < element_no; i++) {
10 int ena[2]; ena[0] = i; ena[1] = ena[0]+1;
11 Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena); the_omega_eh_array.add(elem);
12 }
13 }
14 gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
15 __initialization(df, omega_h);
16 the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Neumann; // M(0) = 0
17 the_gh_array[node_order(0)](1) = gh_on_Gamma_h::Dirichlet; // w(0) = 0
18 the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Dirichlet; // psi(L) = -dw/dx(L) = 0
19 the_gh_array[node_order(node_no-1)](1) = gh_on_Gamma_h::Dirichlet; // w(L) = 0
20 }
1 H0 f = (f_0/L_)*((H0)X);
2 force &= C0(6, (double*)0); C0 force_sub = SUBVECTOR("int, C0&", 3, force);
3 force_sub[1] = (((H0)N)*f) | d_l;
-1. 10
-6 -0.00008
0.2 0.4 0.6 0.8 1 x
-6
-1.5 10 -0.0001 -25
-50
Figure 4•24 The results of the distrubted loading case using Lagrange
multiplier formulation for the beam bending problem.
ρ EI dψ 2 ρ dw 2
p ( ψ, w ;ρ ) ≡ J ( ψ, w ) + --- C 2 ( ψ, w ) = ∫ ------ – fw dx + --- ∫ ψ + ------- dx – w VΓ h + ψM Γh Eq. 4•56
2 2 dx 2 dx
Ω Ω
where the popular quadratic form of the penalty function is taken. The Euler-Lagrange equations obtained from
setting δ p = 0 are (where δψ = εψ vψ, δw = εw vw )
dv ψ dψ
dx + ρ ∫ v ψ ψ + ------- dx + v ψ M Γh = 0
dw
δψ p = εψ ∫ EI ---------
dx d x dx
Ω Ω
dv w
= ε w – ∫ v w f dx + ρ ∫ ---------- ψ + ------- dx – v w V Γh = 0
dw
δw p Eq. 4•57
dx dx
Ω Ω
Dropping the arbitrary constants εψ and εw and substituting interpolation functions for {ψ, w}, and {vψ, vw}, the
Euler-Lagrange equations, Eq. 4•57, are re-written for the element formulation in matrix form as
dφeψ dφ eψ dφ ew
EI ∫ ---------- ⊗ ---------- dx + ρ ∫ φ eψ ⊗ φ eψ dx ρ ∫ φeψ ⊗ ---------- dx – φ eψ M Γh
Ω dx dx
Ω
Ω dx
ψ̂ e
e = Eq. 4•58
dφ ew dφ ew dφ ew ŵ e ∫ φew f dx + φew VΓ
ρ ∫ ---------- ⊗ φ eψ dx ρ ∫ ---------- ⊗ ---------- dx
h
dx dx dx Ωe
Ω Ω
Changing the sign of the first equation to keep the right-hand-side positive, we have
dφ eψ dφ eψ dφ ew
– EI ∫ ---------- ⊗ ---------- dx + ρ ∫ φ eψ ⊗ φeψ dx – ρ ∫ φ eψ ⊗ ---------- dx φ eψ M Γh
Ω dx dx
Ω
Ω
dx
ψ̂ e
e = Eq. 4•59
dφ ew dφew dφ ew ŵ e ∫ φew f dx + φew VΓ
ρ ∫ ---------- ⊗ φ eψ dx ρ ∫ ---------- ⊗ ---------- dx
h
dx dx dx Ωe
Ω Ω
As discussed in a sub-section “Penalty Methods” on page 153 in Chapter 2, the penalty parameter ρ should be
initially set to a small number, then gradually increase its values in subsequent iterations. Starting out with a
small ρ means we are to weight more on the minimization of the objective functional (for this problem the mini-
mum energy principle in mechanics). Subsequently increasing the penalty parameter enforces the constraint
x 0.6
0.2 0.4 0.6 0.8 1
-0.2 0.5
-0.4 0.4
w
ψ -0.6 0.3
-0.8 0.2
0.1
-1
Figure 4•25 The solutions of end-bending moment case with penalty formulation.
#include "include\fe.h"
static const int node_no = 5; static const int element_no = 4; static const int spatial_dim_no = 1;
static const double L_ = 1.0; static const double h_e = L_/((double)(element_no));
static const double E_ = 1.0; static const double I_ = 1.0; static const double f_0 = 1.0;
static const double M_ = 1.0; static double k_ = 1.0; Definte discretizaed global domain
Omega_h::Omega_h() { for(int i = 0; i < node_no; i++) { double v = ((double)i)*h_e;
Node* node = new Node(i, spatial_dim_no, &v); the_node_array.add(node); }
define nodes
for(int i = 0; i < element_no; i++) { int ena[2]; ena[0] = i; ena[1] = ena[0]+1; define elements
Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena); the_omega_eh_array.add(elem); } }
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {__initialization(df, omega_h);
the_gh_array[node_order(0)](0)=the_gh_array[node_order(0)](1)=gh_on_Gamma_h::Dirichlet;
define boundary conditions
the_gh_array[node_order(node_no-1)][0] = M_; }class Beam_Penalty_Function_Formulation : M(L) = 1
public Element_Formulation { public: instantiate fixed and free variables and
Beam_Penalty_Function_Formulation(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make( int, Global_Discretization&);
Global_Discretization
Beam_Penalty_Function_Formulation( int, Global_Discretization&); };
Element_Formulation* Beam_Penalty_Function_Formulation::make(int en,
Global_Discretization& gd) { return new Beam_Penalty_Function_Formulation(en,gd); }
Beam_Penalty_Function_Formulation::Beam_Penalty_Function_Formulation( int en,
Global_Discretization& gd) : Element_Formulation(en, gd) {
Quadrature qp(spatial_dim_no, 2); φ eψ = φ ew = {(1-ξ)/2, (1+ξ)/2}T
H1 Z(qp), N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
dφeψ dφ eψ
k e00 = – ∫ EI ---------- ⊗ ---------- dx –
"int, int, Quadrature", 2/*nen*/, 1/*nsd*/, qp);
N[0] = (1-Z)/2; N[1] = (1+Z)/2; H1 X = N*xl; H0 Nx = d(N)(0)/d(X); J d_l(d(X)); dx dx
stiff &= C0(4, 4, (double*)0); C0 stiff_sub = SUBMATRIX("int, int, C0&", 2, 2, stiff); Ωe
stiff_sub[0][0] = -( (E_*I_) * Nx * (~Nx) + k_ * (((H0)N)*(~(H0)N)) ) | d_l;
ρ ∫ φ eψ ⊗ φ eψ dx
stiff_sub[0][1] = -k_* ( (((H0)N) * (~Nx)) | d_l ); stiff_sub[1][0] = -(~stiff_sub[0][1]);
stiff_sub[1][1] = k_* ( (Nx * (~Nx)) | d_l );
force &= C0(4, (double*)0); C0 force_sub = SUBVECTOR("int, C0&", 2, force); Ω
force_sub[1] = (((H0)N)*f_0) | d_l;
dφ eψ dφ eλ
k e01 = – ( k e10 ) T = – ρ ∫ ---------- ⊗ --------- dx
}
Element_Formulation* Element_Formulation::type_list = 0; dx dx
static Element_Type_Register element_type_register_instance; Ωe
static Beam_Penalty_Function_Formulation beam_penalty_function_formulation_instance(
dφ ew dφ ew
k e11 = ρ ∫ ---------- ⊗ ---------- dx
element_type_register_instance);
int main() { dx dx
const int ndf = 2; Omega_h oh; gh_on_Gamma_h gh(ndf, oh); Ω
U_h uh(ndf, oh); Global_Discretization gd(oh, gh, uh);
Matrix_Representation mr(gd);
C0 w(node_no, (double*)0), w_old(node_no, (double*)0), f e1 = ∫ φew fdx
delta_w(node_no, (double*)0), u_optimal; Ωe
double min_energy_norm = 1.e20, k_optimal;
for( int i = 0; i < 10; i++) {
mr.assembly(); C0 u = ((C0)(mr.rhs()))/((C0)(mr.lhs()));
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h();
for( int j = 0; j < node_no; j++) w[j] = gd.u_h()[j][1];
delta_w = ((i) ? w-w_old : w); w_old = w;
if((double)norm(delta_w) < min_energy_norm) { monitor convergence with norm(∆w)
min_energy_norm = norm(delta_w); u_optimal = u; k_optimal = k_; }
cout << "penalty parameter: " << k_ << " energy norm: " << norm(delta_w) << endl
<< gd.u_h() << endl; k_ *= 2.0; }
gd.u_h() = u_optimal; gd.u_h() = gd.gh_on_gamma_h();
cout << "penalty parameter: " << k_optimal << endl << gd.u_h() << endl; return 0;
}
Listing 4•7 Beam-bending problem with penalty function formulation using linear line element (project:
“beam_penalty_function_formulation” in project workspace file “fe.dsw” under directory “vs\ex\fe”).
2
du 2
u 2 + = 1,
du
0 < x < 1, with u’( 0 ) = 0, and u ( 1 ) = 2 Eq. 4•60
dx d x
d du
u = 1, 0 < x < 1, with u’( 0 ) = 0, u ( 1 ) = 2 Eq. 4•62
dx dx
Parallel to the development in Chapter 3, we solve this problem in finite element with (1) Galerkin formulation,
and (2) least squares formulation.
Galerkin Formulation
du h
d h --------
R( uh ) ≡ u –1 Eq. 4•63
dx dx
With Galerkin weightings vh , which is homogeneous at the boundaries, and uh = vh + uΓg , where uΓg is the essen-
tial boundary conditions, the weighted residuals statement gives
1 1
h
d h du
I ( u h ) ≡ ∫ v h R ( u h ) dx = ∫ vh u -------- – 1 dx = 0 Eq. 4•64
dx dx
0 0
1
dv h du h
I(uh) = ∫ – u h -------- -------- – v h dx = 0
dx dx
Eq. 4•65
0
An iterative algorithm is employed for this non-linear problem with uh interpolated at the element level as
u eh ≡ φ ei û ei , where “hat” denotes the nodal values.
∂I
I ( û k + 1 ) = I ( û k + δû k ) ≅ I ( û k ) + δû k = 0 Eq. 4•66
∂ û k
û
where û k + 1 ≡ û k + δû k . The approximation in this equation is the Taylor expansion to the first-order derivatives.
That is the increment of the solution δûk can be solved by
–1
∂I – I ( û k )
δû k = I ( û k ) = ---------------- Eq. 4•67
∂ û k
IT
û
d ( v̂ i φ ei ) du k dφ j du k
∂I φ j --------e- + u k --------e- dx = v̂ A
dφ e dφ
IT ≡ = A – ∫ ------------------ - ⊗ φ e --------e- + u ek -------e- dx
– ∫ ------- Eq. 4•68
∂ û ∀e e dx e dx dx
û k
Ω dx ∀e Ω dx dx
e e
and,
dφ e du ek
I ( û k ) = v̂ A
∀e ∫ – u ek -------- --------- – φ e dx
dx dx
Eq. 4•69
Ωe
v̂ is an arbitrary constant of global nodal vector and appears on both the nominator and denominator of Eq.
4•67. Therefore, it can be dropped. We define the element tangent stiffness matrix and element residual vector as
dφ e du ek dφ e dφ e du ek
ke T ≡ ∫ – -------- ⊗ φ e --------- + -------- u ek dx , and re ≡ ∫ u ek -------- --------- + φ e dx Eq. 4•70
dx dx dx dx dx
Ωe Ωe
The Program Listing 4•8 implements element formulation in Eq. 4•70, then, uses an iterative algorithmic solve
for the increment of the solution ( δu h )k with Eq. 4•67. An initial values of zero, u0 = 0, will lead to singular left-
hand-side matrix, therefore, the initial values are set to unity, u0 = 1.0. In the element level the nodal value of ue
is supplied by a private member function __initialization(int) of class Non_Linear_ODE_Quadratic as “ul”
The line 3 in the above assigns nodal free degree of freedom values plus nodal fixed degree of freedom values to
“ul”. The values of ue itself can be computed at the element level as
#include "include\fe.h"
static const int node_no = 5; static const int element_no = 2; static const int spatial_dim_no = 1;
Omega_h::Omega_h() {
for(int i = 0; i < node_no; i++) { Definte discretizaed global domain
double v; v = ((double)i)/((double)(node_no-1)); define nodes
Node* node = new Node(i, spatial_dim_no, &v); the_node_array.add(node); }
for(int i = 0; i < element_no; i++) {
int ena[3]; ena[0] = i*2; ena[1] = ena[0]+1; ena[2] = ena[0]+2; define elements
Omega_eh* elem = new Omega_eh(i, 0, 0, 3, ena); the_omega_eh_array.add(elem); }
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
define boundary conditions
__initialization(df, omega_h);
du
the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Dirichlet; ( 0 ) = 0, u ( 1 ) = 2
the_gh_array[node_order(node_no-1)][0] = sqrt(2.0); } dx
static const int ndf = 1; static Omega_h oh; static gh_on_Gamma_h gh(ndf, oh);
static U_h uh(ndf, oh); static Global_Discretization gd(oh, gh, uh); instantiate fixed and free variables and
class Non_Linear_ODE_Quadratic : public Element_Formulation { Global_Discretization
C0 ul; void __initialization(int);
public:
Non_Linear_ODE_Quadratic(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Non_Linear_ODE_Quadratic(int, Global_Discretization&); };
static int initial_newton_flag;
void Non_Linear_ODE_Quadratic::__initialization(int en) {
ul &= gd.element_free_variable(en) + gd.element_fixed_variable(en);
if(!initial_newton_flag) gl = 0.0; }
Element_Formulation* Non_Linear_ODE_Quadratic::make(int en, Global_Discretization& gd) {
return new Non_Linear_ODE_Quadratic(en,gd); }
Non_Linear_ODE_Quadratic::Non_Linear_ODE_Quadratic(int en, Global_Discretization& gd)
: Element_Formulation(en, gd) { dφ e du ek dφ e
__initialization(en); Quadrature qp(spatial_dim_no, 3); k eT ≡ ∫ – -------- ⊗ φ e --------- + -------- u ek dx
H1 Z(qp), N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( dx dx dx
Ωe
"int, int, Quadrature", 3/*nen*/, 1/*nsd*/, qp);
N[0] = -Z*(1-Z)/2; N[1] = (1-Z)*(1+Z); N[2] = Z*(1+Z)/2;
H1 X = N*xl; J d_l(d(X)); H0 Nx = d(N)(0)/d(X); H1 U = N*ul; H0 Ux = d(U)/d(X); dφ e du ek
stiff &= -(Nx * ~( ((H0)N)*Ux + Nx * ((H0)U) ) ) | d_l; re ≡ ∫ u ek -------- --------- + φ e dx
dx dx
force &= ( ((H0)U) * Nx * Ux + ((H0)N) ) | d_l; } Ωe
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static Non_Linear_ODE_Quadratic non_linear_ode_quadratic_instance
(element_type_register_instance);
static Matrix_Representation mr(gd); static const double EPSILON = 1.e-12;
int main() {
C0 u, du, unit(gd.u_h().total_node_no(), (double*)0); unit = 1.0; gd.u_h() = unit; –1
∂I –1
gd.u_h() = gd.gh_on_gamma_h(); initial_newton_flag = TRUE; δû k= [ – I ( û k ) ] = I T [ – I ( û k ) ]
do { ∂ û
û k
mr.assembly(); initial_newton_flag = FALSE; du = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
if(!(u.rep_ptr())) { u = du; u = 1.0; }
u += du; gd.u_h() = u; û k + 1 ≡ û k + δû k
cout << norm((C0)(mr.rhs())) << " , " << norm(du) << endl << gd.u_h();
(C0)(mr.lhs()) = 0.0; (C0)(mr.rhs()) = 0.0;
} while((double)norm(du) > EPSILON); reset left-hand-side and right-hand-side
cout << gd.u_h();
return 0; }
Listing 4•8 Solution of nonlinear ordinary differential equation using Galerkin formulation for finite ele-
ment (project: “nonlinear_ode” in project workspace file “fe.dsw” under directory “vs\ex\fe”).
The default behavior of the class Element_Formulation is that essential boundary conditions “gl” will be
included in the computation of reaction, which is “stiff * gl”, and to be subtracted out from the right-hand-side
vector. For the iterative algorithm which solves the increment of solution, δû k , only at the initial loop (k = 0)
when we compute δû 0 , the reaction need to be subtracted out of the right-hand-side once for all. For k > 0, “gl”
is set to zero, as in line 4, to prevent the reaction to be subtracted out of the right-hand-side at every iteration.
This ad hoc mechanism is incorporated by a “initial_newton_flag” in the main() function as
1 int main() {
...
2 initial_newton_flag = TRUE;
3 do { // Newton iteration loop
4 mr.assembly();
5 intial_newton_flag = FALSE;
...
6 } while (... );
...
7 }
The “initial_newton_flag” is set to TRUE initially (line 2). After the global matrix and global vector have been
assembled for the first time (line 4), the initial_newton_flag is set to FALSE (line 5). Therefore, at the element
level the reaction can be prevent from subtracting out of the right-hand-side again. The error of this computation,
defined as the difference of the exact solution ( u ex ( x ) = 1 + x 2 ) and finite element solution, is shown in Fig-
ure 4•26.The nodal solutions are almost identical to the exact solution.
0.0006
0.0004
0.0002
Error =
exact - f.e. solution x
0.2 0.4 0.6 0.8 1
-0.0002
-0.0004
-0.0006
-0.0008
∂ R ( u h ) 22 ∂R ( u h )
------------------------- = 2 ------------------, R ( u h ) = 0 Eq. 4•71
∂u h ∂u h
∂R ( u h )
w = -----------------
- Eq. 4•72
∂u h
∂R ( u h ) ∂I ∂2 R( uh ) ∂R ( u h ) ∂R ( u h )
I ( u h ) ≡ -----------------
-, R ( u h ) , and I T ≡
= --------------------
-, R ( u h ) + ------------------, ------------------
Eq. 4•73
∂u h ∂ uh ∂u h 2 ∂u h ∂u h
uh
For the non-linear problem in the previous section, the residual, at the element level, is
d 2 ue du 2
- + -------e- – 1
R ( u e ) ≡ u e ---------- Eq. 4•74
dx 2 dx
and the first derivative of the residual, with respect to the nodal variables ( u e ≡ φ ei û ei ), is
∂R ( û ei ) d 2 ue d 2 φ ei dφ ei du e
------------------ = φei ----------- + u e ----------- + 2 --------- -------- Eq. 4•75
∂û ei dx 2 dx 2 dx dx
∂ 2 R ( û ei ) d2 φ d2 φ dφ dφ
--------------------- = φ e ⊗ ----------e- + ----------e- ⊗ φ e + 2 -------e- ⊗ -------e- Eq. 4•76
∂û e i2 dx 2 dx 2 dx dx
From Eq. 4•73, the element tangent stiffness matrix and the element residual vector are
∂R ( û e ) ∂R ( û e ) ∂ 2 R ( û e )
ke T ≡ ∫ ----------------- ⊗ ----------------- + -------------------- R ( u e ) dx
∂û e ∂û e ∂û e 2
, and
Ωe
∂R ( û e )
r e ≡ – ∫ ----------------- R ( u e ) dx Eq. 4•77
∂û e
Ωe
The Program Listing 4•9 implements Eq. 4•77. An immediate difficulty associates with the least squares formu-
lation is the presence of the second derivatives. As we have discussed in the irreducible formulation for beam
bending problem in page 306, the C1-continuity on node is required for the entire problem domain to be integra-
ble. Otherwise, if first derivative is not continuous on node, the second derivative on node will be infinite, and
the entire problem domain is not integrable. This means that we need to have du/dx in the set of nodal variables
to ensure the first derivative is continuous on the nodes. As in the irreducible formulation for beam bending
problem, a 2-node element can be used with the Hermite cubics discussed previously. At the element level, we
have
The Hermite cubics (lines 9-12) are the same as those in the irreducible formulation except that we have positive signs for
both du0/dx and du1/dx variables (which is taken as negative in bending problem conventionally to improve the symmetry
of the formulation).
#include "include\fe.h"
static const int node_no = 5;
static const int element_no = 4;
static const int spatial_dim_no = 1;
Omega_h::Omega_h() {
Definte discretizaed global domain
for( int i = 0; i < node_no; i++) {
double v; v = ((double)i)/((double)(node_no-1)); define nodes
Node* node = new Node(i, spatial_dim_no, &v); the_node_array.add(node);
}
define elements
for( int i = 0; i < element_no; i++) {
int ena[2]; ena[0] = i; ena[1] = ena[0]+1;
Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena); the_omega_eh_array.add(elem);
}
}
gh_on_Gamma_h::gh_on_Gamma_h( int df, Omega_h& omega_h) { define boundary conditions
__initialization(df, omega_h);
du
the_gh_array[node_order(0)](1) = gh_on_Gamma_h::Dirichlet;
( 0 ) = 0, u ( 1 ) = 2
the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Dirichlet; dx
the_gh_array[node_order(node_no-1)][0] = sqrt(2.0);
} instantiate fixed and free variables and
static const int ndf = 2; static Omega_h oh;
Global_Discretization
static gh_on_Gamma_h gh(ndf, oh);
static U_h uh(ndf, oh);
static Global_Discretization gd(oh, gh, uh);
class Non_Linear_Least_Squares : public Element_Formulation {
C0 ul; void __initialization(int);
public:
Non_Linear_Least_Squares(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Non_Linear_Least_Squares(int, Global_Discretization&);
};
static int initial_newton_flag;
void Non_Linear_Least_Squares::__initialization(int en) {
ul &= gd.element_free_variable(en) + gd.element_fixed_variable(en);
if(!initial_newton_flag) gl = 0.0;
}
Element_Formulation* Non_Linear_Least_Squares::make(int en,
Global_Discretization& gd) { return new Non_Linear_Least_Squares(en,gd); }
Non_Linear_Least_Squares::Non_Linear_Least_Squares(int en,
Global_Discretization& gd) : Element_Formulation(en, gd) {
__initialization(en);
double weight[3] = {1.0/3.0, 4.0/3.0, 1.0/3.0},
h_e = fabs( ((double)(xl[0] - xl[1])) );
Quadrature qp(weight, 0.0, h_e, 3);
J d_l(h_e/2.0);
H2 Z((double*)0, qp),
z = Z/h_e,
N = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 4/*nen x ndf*/, 1/*nsd*/, qp);
N[0] = 1.0-3.0*z.pow(2)+2.0*z.pow(3);
N[1] = Z*(1.0-z).pow(2); Hermite cubics
N[2] = 3.0*z.pow(2)-2.0*z.pow(3);
N[3] = Z*(z.pow(2)-z);
H0 Nx = INTEGRABLE_VECTOR("int, Quadrature", 4, qp),
Nxx = INTEGRABLE_VECTOR("int, Quadrature", 4, qp);
Nx = d(N)(0);
for(int i = 0; i < 4; i++) { Nxx[i] = dd(N)(i)[0][0]; }
H2 U = N*ul;
d2ue du 2
H0 Ux, Uxx;
- + -------e- – 1
R ( u e ) ≡ u e ----------
Ux = d(U)(0); dx 2 dx
Uxx = dd(U)[0][0];
H0 uR = ((H0)U)*Uxx + Ux.pow(2) - 1.0,
Ru = ((H0)N)*Uxx + ((H0)U)*Nxx + 2.0*Nx*Ux, ∂R ( û ei ) d 2 ue d 2 φ ei dφ ei du e
------------------ = φ ei ----------- + u e ----------- + 2 --------- --------
Ruu = (((H0)N)%Nxx) + (Nxx%((H0)N)) + 2.0*(Nx%Nx); ∂û ei dx 2 dx 2 dx dx
stiff &= ( (Ru%Ru + Ruu*uR) ) | d_l;
force &= -(Ru*uR) | d_l ;
} ∂ 2 R ( û e ) d2 φe d2 φe
-------------------- = φ e ⊗ ----------- + ----------- ⊗ φ e +
Element_Formulation* Element_Formulation::type_list = 0; ∂û e 2 dx 2 dx 2
static Element_Type_Register element_type_register_instance;
static Non_Linear_Least_Squares
non_linear_least_squares_instance(element_type_register_instance); dφ e dφe
2 -------- ⊗ --------
static Matrix_Representation mr(gd); dx dx
static const double EPSILON = 1.e-12;
int main() {
∂R ( û e ) ∂R ( û e )
∫
C0 p, u, du;
k eT ≡ - ⊗ ----------------- +
----------------
gd.u_h() = gd.gh_on_gamma_h(); ∂û e ∂û e
C0 unit(gd.u_h().total_node_no()*ndf, (double*)0); Ωe
unit = 1.0;
gd.u_h() = unit; ∂ 2 R ( û e )
-------------------- R ( u e ) dx
do { ∂û e 2
mr.assembly();
p = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
∂R ( û e )
if(!(u.rep_ptr())) { u = p; u = 1.0; } r e ≡ – ∫ ----------------- R ( u e ) dx
double left = 0.0, right = 1.0, length = right-left; ∂û e
Ωe
do {
Matrix_Representation::Assembly_Switch = Matrix_Representation::RHS; line search
du = (left + 0.618 * length) * p;
gd.u_h() = u + du;
golden section
(C0)(mr.rhs()) = 0.0;
mr.assembly();
double residual_golden_right = norm((C0)(mr.rhs()));
du = (left + 0.382 * length)* p;
gd.u_h() = u + du;
(C0)(mr.rhs())=0.0;
mr.assembly();
double residual_golden_left = norm((C0)(mr.rhs()));
if(residual_golden_right < residual_golden_left) left = left + 0.382 * length;
else right = left+0.618*length;
length = right - left;
} while(length > 1.e-2);
cout << "bracket: (" << left << ", " << right << ")" << endl;
u += du; û k + 1 ≡ û k + δû k
cout << "residual norm: " << norm((C0)(mr.rhs())) <<
" search direction norm: " << norm(p) << endl << “solution: “ << gd.u_h() << endl;
Matrix_Representation::Assembly_Switch = Matrix_Representation::ALL;
(C0)(mr.lhs()) = 0.0;
(C0)(mr.rhs()) = 0.0;
} while((double)norm(p) > EPSILON);
cout << gd.u_h();
return 0;
}
Listing 4•9 Solution of nonlinear ordinary differential equation using least squares formulation for finite
element (project: “nonlinear_least_squares_ode” in project workspace file “fe.dsw” under directory
In place of evaluating the objective functional value in Chapter 2, the finite element method is to minimized the
residuals of the problem. In the loop for the golden section line search, the assembly flag is set to only assemble
the right-hand-side vector (line 3). The norm of the right-hand-side vector is used as the criterion for the line
search minimization. At outer loop where Newton’s formula is used to compute the next search direction p, the
assembly flag is reset back to assembly both the left-hand-side matrix and the right-hand-side vector (line 19).
The results are shown in Figure 4•27.
1.4 0.7
0.6
1.3 0.5
u du/dx0.4
1.2
0.3
1.1 0.2
0.1
Figure 4•27 Nodal solutions (open squares) comparing to the exact solutions (solid
curves) for the nonlinear least squares formulation.
where C is the heat capacity matrix, K is the conductivity matrix, and f is heat source vector. The variable u is
the temperature and u· is the time derivative of temperature. And, the hyperbolic equation for structural dynam-
ics
where M is the consistent mass matrix, K the stiffness matrix and f the force vector. The variable u is the dis-
placement and u·· , the second time derivative of the displacement, gives the acceleration.
Parabolic Equation
From Eq. 3•191 of Chapter 3 (in page 253),
∂u ∂ 2 u ∂u
----- – -------- = 0, 0 < x < 1 subject to u ( 0, t ) = 0, ------ ( 1, t ) = 0, and u ( x, 0 ) = 1 Eq. 4•81
∂t ∂x 2 ∂x
∂φ e ∂φ e
ce = ∫ φe ⊗ φe dx, and k e = ∫ -------
∂x
- ⊗ -------- dx
∂x
Eq. 4•82
Ωe Ωe
θ is a scalar parameter and ∆t is the time step length. The Program Listing 4•10 implements Eq. 4•80 and Eq.
4•82. At the element level, the heat capacity matrix ce is the additional term to the static case as
1 C0& Parabolic_Equation::__lhs() {
2 the_lhs &= mass + theta_* dt_*stiff; // C + ∆tθK
3 return the_lhs;
4 }
#include "include\fe.h"
static const int node_no = 5; static const int element_no = 4; static const int spatial_dim_no = 1;
Omega_h::Omega_h(){
Definte discretizaed global domain
for(int i = 0; i < node_no; i++) { double v;v=((double)i)/((double)element_no); define nodes
Node* node = new Node(i, spatial_dim_no, &v); the_node_array.add(node); }
for(int i = 0; i < element_no; i++) { int ena[2]; ena[0] = i; ena[1] = ena[0]+1;
Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena); the_omega_eh_array.add(elem); } }
define elements
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) { __initialization(df, omega_h);
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet; define boundary conditions
the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Neumann; }
static const int ndf = 1; static Omega_h oh; static gh_on_Gamma_h gh(ndf, oh);
instantiate fixed and free variables and
static U_h uh(ndf, oh); static Global_Discretization gd(oh, gh, uh); Global_Discretization
class Parabolic_Equation : public Element_Formulation { C0 mass, ul;
void __initialization(int, Global_Discretization&);
public:
Parabolic_Equation(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Parabolic_Equation(int, Global_Discretization&);
C0& __lhs(); C0& __rhs(); };
overwrite protected member functions
void Parabolic_Equation::__initialization(int en, Global_Discretization& gd) {
ul &= gd.element_free_variable(en); }
Element_Formulation* Parabolic_Equation::make(int en, Global_Discretization& gd) {
return new Parabolic_Equation(en,gd); }
Parabolic_Equation::Parabolic_Equation(int en, Global_Discretization& gd) :
Element_Formulation(en, gd) { __initialization(en, gd);
heat capacitance c e = ∫ φ e ⊗ φ e dx
Ωe
Quadrature qp(spatial_dim_no, 2);
H1 Z(qp), ∂φ e ∂φ e
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 2, 1, qp);
N[0] = (1-Z)/2; N[1] = (1+Z)/2; H1 X = N*xl; H0 Nx = d(N)(0)/d(X); J dv(d(X));
conductivity ke = ∫ -------
∂x
- ⊗ -------- dx
∂x
Ωe
stiff &= (Nx % Nx) | dv; mass &= ( ((H0)N)%((H0)N) ) | dv; }
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static Parabolic_Equation parabolic_equation_instance(element_type_register_instance);
static Matrix_Representation mr(gd);
static double theta_ = 0.5; static double dt_ = 0.05;
C0& Parabolic_Equation::__lhs() { the_lhs &= mass + theta_* dt_*stiff; return the_lhs; } C + ∆tθK
C0& Parabolic_Equation::__rhs() {
Element_Formulation::__rhs();
the_rhs += (mass - (1.0-theta_)*dt_*stiff)*ul;
( C – ∆t ( 1 – θ )K )u n – f̂
return the_rhs; }
int main() {
for(int i = 0; i < node_no; i++) uh[i][0] = 1.0;
gd.u_h() = gd.gh_on_gamma_h();
initial conditions
mr.assembly();
C0 decomposed_LHS = !((C0)(mr.lhs()));
for(int i = 0; i < 28; i++) { ( C – ∆t ( 1 – θ )K )u n – f̂
u n + 1 = ---------------------------------------------------------
C0 u = decomposed_LHS*((C0)(mr.rhs())); gd.u_h() = u; ( C + ∆tθK )
double iptr;
if(modf( ((double)(i+1))/4.0, &iptr)==0) {
cout << "time: " << (((double)(i+1))*dt_) << ", at (0.5, 1.0), u = (" <<
gd.u_h()[(node_no-1)/2][0] << ", " << gd.u_h()[node_no-1][0] << ")" << endl; }
if(i < 27) { (C0)(mr.rhs()) = 0.0; (C0)(mr.lhs()) = 0.0; mr.assembly(); }
}
return 0;
}
Listing 4•10 Solution of hyperbolic equation using center difference scheme in time dimension (project:
“hyperbolic_equation” in project workspace file “fe.dsw” under directory “vs\ex\fe”).
In the main() function the decomposition of the left-hand-side matrix is done only once, which is outside of the
time integration loop. The results of this program are shown in Program Listing 4•10.
1 t0
0.8 t0.2
u
0.6
t0.4
0.4
t0.6
0.2 t0.8
t1.0
t1.2
t1.4
0 0.2 0.4 0.6 0.8 1
x
Figure 4•28Finite element solutions for the hyperbolic equation for heat conduction.
ˆ ˆ
K = K + a 0 M + a 1 C, and R n + 1 = – f n + 1 + ( a 0 u n + a 2 u· n + a 3 u·· n )M + ( a 1 u n + a 4 u· n + a 5 u·· n )C Eq. 4•83
where u·· n + 1 = a 0 ( u n + 1 – un ) – a2 u· n – a 3 u·· n and u· n + 1 = u· n + a 6 u·· n + a 7 u·· n + 1 , the Newmark coefficients ai are
γ γ ∆t γ
a 0 = -----------2, a 1 = ---------, a 2 = ---------, a 3 = ------ – 1, a 4 = --- – 1, a 5 = ----- --- – 2 , a 6 = ∆t ( 1 – γ ), a 7 = γ∆t
1 1 1
Eq. 4•84
β∆t β∆t β∆t 2β β 2 β
∂2u ∂4 u
-------- = – --------, 0 < x < 1, t > 0
∂t 2 ∂x 4
∂u ( 0, t ) ∂u ( 1, t )
boundary conditions u ( 0, t ) = u ( 1, t ) = ------------------- = ------------------- = 0
∂x ∂x
∂u ( x, 0 )
and initial conditions u(x, 0) = sin(πx)-πx(1-x), and -------------------- = 0 Eq. 4•85
∂t
The finite element formulation for consistent mass matrix and stiffness matrix is
∂ 2 φe ∂ 2 φe
me = ∫ φ e ⊗ φ e dx, and k e = ∫ ----------- ⊗ ----------- dx
∂x 2 ∂x 2
Eq. 4•86
Ωe Ωe
The damping matrix ce is either in the form of me times damping parameter or in the form of Raleigh damping as
a linear combination of me and ke.1 Again, for two-node element the Hermite cubics are required for the stiffness
matrix as in the irreducible formulation of beam bending problem. The Program Listing 4•11 implements the
hyperbolic equation. Now variables un , u· n , u·· n at tn and un + 1 , u· n + 1 , u·· n + 1 at tn+1 need to be registered as
1 static U_h u_old(ndf, oh); static U_h du_old(ndf, oh); static U_h ddu_old(ndf, oh);
2 static U_h u_new(ndf, oh); static U_h du_new(ndf, oh); static U_h ddu_new(ndf, oh);
These variables are supplied to the element constructor by a private member function
Hyperbolic_Equation::__initialization(int, Global_Discretization&) as
1. p. 93 and p. 339 in K-J Bathe and E.L.Wilson, 1976, “Numerical methods in finite element analysis”, Prentice-Hall, inc.,
Englewood Cliffs, New Jersey.
#include "include\fe.h"
static const int node_no = 5;
static const int element_no = node_no-1;
static const int spatial_dim_no = 1;
static const double L_ = 1.0;
static const double h_e = L_/((double)(element_no));
Omega_h::Omega_h() {
Definte discretizaed global domain
for(int i = 0; i < node_no; i++) { define nodes
double v = ((double)i)*h_e;
Node* node = new Node(i, spatial_dim_no, &v);
the_node_array.add(node);
}
for(int i = 0; i < element_no; i++) { define elements
int ena[2]; ena[0] = i; ena[1] = ena[0]+1;
Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena);
the_omega_eh_array.add(elem);
}
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
define boundary conditions
__initialization(df, omega_h);
the_gh_array[node_order(0)](0) =
the_gh_array[node_order(0)](1) =
the_gh_array[node_order(node_no-1)](0) =
the_gh_array[node_order(node_no-1)](1) =
gh_on_Gamma_h::Dirichlet;
}
static const int ndf = 2;
instantiate fixed and free variables and
static Omega_h oh; Global_Discretization
static gh_on_Gamma_h gh(ndf, oh);
static U_h uh(ndf, oh);
static Global_Discretization gd(oh, gh, uh);
static U_h u_old(ndf, oh); static U_h du_old(ndf, oh); static U_h ddu_old(ndf, oh);
static U_h u_new(ndf, oh); static U_h du_new(ndf, oh); static U_h ddu_new(ndf, oh);
class Hyperbolic_Equation : public Element_Formulation {
C0 mass, ul, dul, ddul;
void __initialization(int, Global_Discretization&);
public:
Hyperbolic_Equation(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Hyperbolic_Equation(int, Global_Discretization&);
C0& __lhs(); overwrite protected member functions
C0& __rhs();
};
void Hyperbolic_Equation::__initialization(int en, Global_Discretization& gd) {
Omega_h& oh = gd.omega_h();
gh_on_Gamma_h& gh = gd.gh_on_gamma_h();
Global_Discretization gd_u_old(oh, gh, u_old);
ul &= gd_u_old.element_free_variable(en); un
Global_Discretization gd_du_old(oh, gh, du_old); u· n
dul &= gd_du_old.element_free_variable(en);
Global_Discretization gd_ddu_old(oh,gh,ddu_old); u··
n
ddul &=gd_ddu_old.element_free_variable(en);
}
Element_Formulation* Hyperbolic_Equation::make(int en, Global_Discretization& gd) {
return new Hyperbolic_Equation(en,gd);
}
Listing 4•11 Newmark scheme for hyperbolic equation using finite element method.
Basically, the time integration algorithm is to update variables un , u· n , u·· n at time tn to u n + 1 , u· n + 1 , u·· n + 1 at
time tn+1. At the beginning of time tn+1, u n , u· n , u·· n are given, and u n + 1 is solved from back-substitution of glo-
·
bal stiffness matrix and global residual vector. The velocity and acceleration u·· n + 1 and u n + 1 at time tn+1 are
computed at the global level in the main() program, when the variable “u_new”, un + 1 , is available, such as
1 ddu_new = a[0]*(((C0)u_new)-((C0)u_old))-a[2]*((C0)du_old)-a[3]*((C0)ddu_old);
2 du_new = ((C0)du_old) + a[6]*((C0)ddu_old)+a[7]*((C0)ddu_new);
This is implemented according to the formula for acceleration u·· n + 1 = a 0 ( un + 1 – u n ) – a 2 u· n – a 3 u·· n (line 1) and
velocity u· n + 1 = u· n + a 6 u·· n + a 7 u·· n + 1 (line 2), respectively. The results of this computation are shown in Figure
4•29.
initial condition t = 0
t = 0.28
0.2 0.2 t = 0.26
t = 0.02
t = 0.04 t = 0.24
0.1 0.1
u t = 0.06 u
t = 0.20
0.2 0.4 0.6 0.8 1 x 0.2 0.4 0.6 0.8 1 x
t = 0.08
-0.1 -0.1 t = 0.22
t = 0.10
t = 0.12 t = 0.18
-0.2 -0.2
t = 0.14 t = 0.16
t = 0.0 to 0.14 t = 0.16 to 0.28
Figure 4•29 Beam vibration using finite element method with Newmark scheme to solve the
hyperbolic equation. The finite element solutions of downward deflection are piece-wise cubic
functions of nodal deflection “ û ” and nodal negative slope “ ψ̂ ≡ -du/dx” (i.e., u = f( û , ψ̂ ) for
two-node Hermite cubic element). Solutions of every four time steps are shown.
dφ ew dφ eM
0 – ∫ ---------- ⊗ ---------- dx
dx dx
Ωe ŵ e ∫ φew fdx + φew VΓ h
= Ωe Eq. 4•87
dφ eM dφ ew φ eM ⊗ φ eM ˆ
–∫ ---------- ⊗ ---------- dx – ∫ ---------------------- dx
M e
φ eM ψ Γ h
dx dx EI
Ωe Ωe
0 a e ŵ e fe
= Eq. 4•88
a eT b e M
ˆ
e
re
where the stiffness matrix has the size of 4x4 and the solution and force vectors have the sizes of 4. Following the
finite element convention, we collect degree of freedoms w and M together for each node. At the global level, the
matrix form is
0 a 00 0 a 01 … … 0 a0 ( n – 1 ) ŵ 0 f0
T
a 00 b 00 T
a 01 b 01 … … a 0T( n – 2 ) b0 ( n – 1 ) ˆ
M 0 r0
0 a 10 0 a 11 … … 0 a1 ( n – 1 ) ŵ 1 f1
T
a 10 b 10 T
a 11 b 11 … … a 1T( n – 2 ) b1 ( n – 1 ) ˆ
M 1 =
r1
Eq. 4•89
… … … … …… … … … …
… … … … …… … … … …
0 a ( n – 1 )0 0 a ( n – 1 )1 … … 0 a ( n – 1 ) ( n – 1 ) ŵ f( n – 1 )
( n – 1)
a (Tn – 1 )0 b( n – 1 )0 a (Tn – 1 )1 b( n – 1 )1 … … a (Tn – 1 ) ( n – 1 ) b( n – 1 ) ( n – 1 ) ˆ r( n – 1 )
M( n – 1 )
For large-size problem, the stiffness matrix size could be critical for limited computer memory space , and even
more seriously for the computation time. One approach to reduce the number of degree of freedom in the global
matrix solution process is to separate the variables ŵ and Mˆ in Eq. 4•89 at the global level. Rewriting the ele-
ment formulation of Eq. 4•89 in global submatrix form as
0 A ŵ f Eq. 4•90
=
ˆ
AT B M r
ˆ = r
A T ŵ + BM Eq. 4•91
Therefore,
ˆ = B –1 ( r – A T ŵ )
M Eq. 4•92
ˆ = f , we
Note we have use the property that B is invertible. Observing that the first equation of Eq. 4•90 is AM
pre-multiply Eq. 4•92 with A, such that
ˆ = AB –1 ( r – A T ŵ ) = AB – 1 r – AB –1 A T ŵ
f = AM Eq. 4•93
ŵ = ( AB –1 A T ) – 1 ( AB –1 r – f ) Eq. 4•94
We have relied on the property that AB-1AT is invertible. With ŵ solved, M ˆ can be recovered according to Eq.
4•92, if necessary. The solution using the substructuring technique has two major advantages. Firstly, only A and
B need to be stored in memory space that is only half of the memory space comparing to the entire left-hand-side
matrix in Eq. 4•90. Secondly, the matrix solver in substructuring deals with B-1 and AB-1AT which are smaller
matrices than the left-hand-side matrix in Eq. 4•90. The cost for a matrix solver can be a function of cubic power
of size. For the present case, each of the inverse of B and the inverse of AB-1AT requires about one-eighth of
computation time comparing to that of the solution of the left-hand-side matrix in Eq. 4•90. That is only a quar-
ter of computation time is needed for the matrix solver using substructuring.1
1. Note that the term f includes (1) the distributed load term, the term contains “f” in Eq. 4•87, (2) shear force
(V), the “nodal loading boundary condition” VΓ(treated as natural boundary condition specified corresponding to
“w”-dof), and (3) essential boundary condition of MΓ by subtracting “AMΓ” out of f. The term r includes (1)
negative slope (ψ), the “nodal loading boundary condition” ψ Γ(treated as natural boundary condition specified
corresponding to “M”-dof), and (2) the essential boundary conditions of {wΓ, MΓ} by subtracting “AT wΓ+BMΓ”
out of r .
1. Moreover, Eq. 4•89 has a lot of zero diagonals, which is not without trouble for the matrix solver. We either need to use
modified Cholesky decomposition with the diagonal pivoting or we need to ignore the symmetry and use LU decomposition
with complete pivoting.
0 A ŵ f Eq. 4•95
=
ˆ
AT B M r
The matrix representation, “MR”, for the diagonal submatrix B and its corresponding right-hand-side r is
declared as standard class of “Matrix_Representation”
Matrix_Representation mr(mgd);
This matrix representation instance “mr” can be called to assemble and instantiate the submatrix B and the sub-
vector r. They can be retrieved by
1 mr.assembly();
2 C0 B = ((C0)(mr.lhs())), // diagonal submatrix B
3 r = ((C0)(mr.rhs())); // r
The rows of submatrix A corresponding to “w”-dof, the principal discretization, and the columns of submatrix A
corresponding to “M”-dof, the subordinate discretization. The class “Matrix_Representation_Couple” is
declared instead as
The second argument of this constructor is reserved for instantiation sparse matrix, the third and the fourth argu-
ments of this constructor referencing to right-hand-side vectors corresponding to the principal and the subordi-
nate discretization of submatrix A. In the above example, the principal right-hand-side is supplied with a “0”, the
null pointer. In this case, the principal right-hand-side vector f will be instantiated. When the argument is not
null, such as the subordinate right-hand-side is reference to “mr.rhs()” in this case. The subordinate right-hand-
1 int main() {
2 mrc.assembly();
3 C0 f = ((C0)(mrc.rhs())),
4 A = ((C0)(mrc.lhs()));
5 mr.assembly();
6 C0 B = ((C0)(mr.lhs())),
7 r = ((C0)(mr.rhs()));
8 C0 B_inv = B.inverse(),
9 w = (A*B_inv*r - f)/(A*B_inv*(~A)), // ŵ = ( AB –1 A T ) – 1 ( AB –1 r – f )
10 M = B_inv*(r-(~A)*w); ˆ = B –1 ( r – A T ŵ )
// M
11 wh = w; wh = wgd.gh_on_gamma_h();
13 mh = M; mh = mgd.gh_on_gamma_h();
14 cout << "deflection:" << endl << wh << endl << "bending moment:" << endl << mh;
15 return 0;
16 }
The complete listing of the substructure mixed formulation is in Program Listing 4•12. The cases for nodal
loading and distributed loading, discussed in the mixed formulation of Section 4.2.2, can be turn on by setting
macro definitions “__TEST_NODAL_LOAD” and “__TEST_DISTRIBUTED_LOAD” . The results of the
present computation are completely identical to those of the previous section on mixed formulation..
The nonlinear and transient problems bring only marginal changes to the “fe.lib”. We certainly can create
new classes of “Nonlinear_Element_Formulation” and “Transient_Element_Formulation” for a user defined ele-
ment to derived from. This is similar to the class “Element_Formulation_Couple” in the present example is cre-
ated for user to derived a user defined element formulation from it. We can even create a multiple inheritance (an
advanced but controversial C++ feature) of class Nonlinear_Element_Formulation and class
Transient_Element_Formulation to capture both the nonlinear and the transient capabilities. The object-oriented
programming provides the basic mechanisms for a smooth code evolution of “fe.lib” to be extended to vastly
different area of problems. However, the problem of “mixed formulation with separate variables” brings the
greatest impact of change to fe.lib. We need to change all four strong components of the “fe.lib” to implement
this problem. With mechanisms of the object-oriented programming, we are not only able to reuse the code in
“fe.lib” by deriving from it, but also are able to keep the simplicity of the “fe.lib” intact. After the “fe.lib” has
been modified to deal with the new problem, the beginner of the fe.lib still only need to learn the unscrambled
basic set of “fe.lib” without to confront all kinds of more advanced problems in finite element at once. For For-
tran/C programmers who are already familiar with a couple of existing full-fledged finite element programs, this
advantage of using object-oriented programming to accommodate vastly different problems would be most
immediately apparent.
#include "include\fe.h"
#include "include\omega_h_n.h"
Matrix_Representation_Couple::assembly_switch
initialize static member of class
Matrix_Representation_Couple::Assembly_Switch = Matrix_Representation_Couple::ALL; “Matrix_Representation_Couple”
static const int node_no = 5;
static const int element_no = 4;
static const int spatial_dim_no = 1;
static const double L_ = 1.0;
static const double h_e = L_/((double)(element_no));
static const double E_ = 1.0;
static const double I_ = 1.0;
static const double f_0 = 1.0;
static const double M_ = 1.0;
Omega_h::Omega_h() {
Definte discretizaed global domain
for(int i = 0; i < node_no; i++) { define nodes
double v = ((double)i)*h_e;
Node* node = new Node(i, spatial_dim_no, &v);
the_node_array.add(node);
}
for(int i = 0; i < element_no; i++) { define elements
int ena[2];
ena[0] = i;
ena[1] = ena[0]+1;
Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena);
the_omega_eh_array.add(elem);
}
}
gh_on_Gamma_h_i::gh_on_Gamma_h_i(int i, int df, Omega_h& omega_h) : gh_on_Gamma_h() { define boundary conditions
gh_on_Gamma_h::__initialization(df, omega_h);
if(i == 0) {
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet;
} else if(i == 1) {
the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(node_no-1)][0] = M_;
}
} instantiate fixed and free variables and
static const int ndf = 1;
static Omega_h oh;
Global_Discretization
static gh_on_Gamma_h_i wgh(0, ndf, oh); {Ωh, ŵ h}
static U_h wh(ndf, oh);
static Global_Discretization wgd(oh, wgh, wh);
static gh_on_Gamma_h_i mgh(1, ndf, oh);
ˆ h}
{Ωh, M
static U_h mh(ndf, oh);
static Global_Discretization mgd(oh, mgh, mh);
static Global_Discretization_Couple gdc(wgd, mgd);
class Beam_Mixed_Formulation : public Element_Formulation_Couple {
Global_Discretization_Couple
public:
Beam_Mixed_Formulation(Element_Type_Register a) : Element_Formulation_Couple(a) {}
Element_Formulation *make(int, Global_Discretization&);
Beam_Mixed_Formulation(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&);
Beam_Mixed_Formulation(int, Global_Discretization_Couple&);
};
Element_Formulation* Beam_Mixed_Formulation::make(int en, Global_Discretization& gd) {
return new Beam_Mixed_Formulation(en,gd);
}
Listing 4•12 Substructure solution for the mixed formulation of the beam bending problem.
where q is the heat flux and f is the heat source. This is subject to Dirichlet and Neumann boundary conditions
respectively. We use “u” for temperature and n as the outward unit surface normal. The Fourier law assumes
that the heat flux can be related to temperature gradient as
q = – κ ∇u Eq. 4•98
where κ is the thermal diffusivity. The weighted residual statement of Eq. 4•96 with the Fourier law gives
Integration by parts and applying divergence theorem of Gauss to transform the volume integral into a boundary
integral gives
∫ ∇w ( κ ∇u ) dΩ + ∫ w ( – κ ∇u ) • n dΓ – ∫ wf dΩ = 0 Eq. 4•100
Ω Γ Ω
Since the “w” is homogeneous at Γg, the boundaries with Dirchlet boundary conditions, the second term of the
boundary integral becomes
∫ wq • n dΓ = – ∫ wh dΓ Eq. 4•101
Γ Γh
The second term ( φ ea, h ) Γ is the Neumann boundary conditions, which is most easily specified in the problem
h
definition as equivalent nodal load, and the third term – a ( φea, φ eb )u eb accounts for the Dirichlet boundary condi-
tions. Again, the default behaviors of “fe.lib” will deal with these two terms automatically.
For an isoparametric bilinear 4-nodes element, the bilinear shape functions are taken for both the variable
interpolation, u eh ≡ φ ea û ea , and the coordinate transformation rule, x ≡ φ ea x ea , that is
1
φ ea ≡ N a ( ξ, η ) = --- ( 1 + ξ a ξ ) ( 1 + η a η ) Eq. 4•104
4
index “a” indicates element node number, and (ξa, ηa) , for a = 0, 1, 2, 3 are four nodal coordinates {(-1, -1), (1,
-1), (1, 1), (-1, 1)} of the referential element. The variable interpolation becomes
where û ea is the nodal variables, and the coordinate transformation rule becomes
x eh ≡ N a ( ξ, η )x ea Eq. 4•106
where x ea is the element nodal coordinates. The integration in Eq. 4•102 and first term of Eq. 4•103 gives
∂x ∂x
ke = ∫ ( ∇N ⊗ κ∇N )dx = ∫ ( ∇N ⊗ κ∇N )det -----
∂ξ
- dξ , and fe = ∫ ( Nf )dx = ∫ ( Nf )det -----
∂ξ
- dξ Eq. 4•107
Ωe Ωe Ωe
Ωe
The Gaussian quadrature requires the integration domain to be transformed from the physical element domain
“Ωe” to the referential element domain “Ωe” with the Jacobian of the coordinate transformation as “J
≡ d et ( ∂x ⁄ ∂ξ ) ” (i.e., the determinant of the Jacobian matrix), where the Jacobian matrix of the coordinate trans-
formation rule, “ ∂x ⁄ ∂ξ ”, is computed from the definition of the coordinate transformation rule in Eq. 4•106.
The derivatives of the variables are taken from Eq. 4•105 as
û e0 ∂N 0 ∂N ∂N 2 ∂N3 û e
0
∂N 0 ∂N ∂N ∂N 3 û e0
∂ξ ∂η
------ ------ --------- ---------1 ---------2 --------- ∂Na ∂ξ T
∂x ∂x ∂ξ ∂ξ ∂ξ ∂ξ û e1
= = --------- ------ û ea Eq. 4•108
∂ξ ∂η ∂N 0 ∂N 1 ∂N2 ∂N 3 ∂ξ ∂x
------ ------ û e2
∂y ∂y --------- --------- --------- ---------
∂η ∂η ∂η ∂η û e3
The derivative of shape functions with respect to natural coordinates ∂N ⁄ ∂ξ , is computed from the definition of
the shape functions in Eq. 4•104. The term ∂ξ ⁄ ∂x is computed from the inverse of the derivative of the coordi-
nate transformation rule from Eq. 4•106 as
∂ξ ⁄ ∂x = ( ∂x ⁄ ∂ξ ) –1 Eq. 4•109
That is, Eq. 4•108 gives the formula to compute the derivatives of shape functions matrix (nen × dof = 4 × 2) for
the element stiffness matrix in Eq. 4•107
∂N ∂x –1
∇N = ------- ------ Eq. 4•110
∂ξ ∂ξ
Since there is no heat source in the square area “f = 0”, and due to symmetry of the boundary conditions no tem-
perature gradient can be generated in x-direction, Eq. 4•111 reduces to
utop= 30oC
q•n=0 q•n=0
ubottom= 0oC
Figure 4•30 Conduction in a square insulated from two sides.
That is the temperature gradient in y-direction is 10 (oC per unit length). In other words, the nodal solutions at
the row next to the bottom is u = 10 oC, and the row next to the top is u = 20 oC. The Program Listing 4•13
implements element formulation for the stiffness matrix and force vector in Eq. 4•107 for this simple problem.
The nodes and elements can be generated as
1 int row_node_no = 4,
2 row_element_no = row_node_no - 1;
3 double v[2];
4 for(int i = 0; i < row_node_no; i++)
5 for(int j = 0; j < row_node_no; j++) {
6 int nn = i*row_node_no+j;
7 v[0] = (double)j; v[1] = (double)i;
8 Node* node = new Node(nn, 2, v);
9 the_node_array.add(node);
10 }
11 int ena[4];
12 for(int i = 0; i < row_element_no; i++)
13 for(int j = 0; j < row_element_no; j++) {
14 int nn = i*row_node_no+j;
15 ena[0] = nn; ena[1] = ena[0]+1; ena[3] = nn + row_node_no; ena[2] = ena[3]+1;
16 int en = i*row_element_no+j;
17 Omega_eh* elem = new Omega_eh(en, 0, 0, 4, ena);
18 the_omega_eh_array.add(elem);
19 }
#include "include\fe.h"
Omega_h::Omega_h() {
int row_node_no = 4, row_element_no = row_node_no - 1;
define nodes
for(int i = 0; i < row_node_no; i++)
for(int j = 0; j < row_node_no; j++) {
int nn = i*row_node_no+j; double v[2]; v[0] = (double)j; v[1] = (double)i;
Node* node = new Node(nn, 2, v); the_node_array.add(node);
}
for(int i = 0; i < row_element_no; i++) define elements
for(int j = 0; j < row_element_no; j++) {
int nn = i*row_node_no+j, en = i*row_element_no+j;
int ena[4]; ena[0] = nn; ena[1] = ena[0]+1; ena[3] = nn + row_node_no; ena[2] = ena[3]+1;
Omega_eh* elem = new Omega_eh(en, 0, 0, 4, ena); the_omega_eh_array.add(elem);
}
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) { define B.C.
__initialization(df, omega_h);
int row_node_no = 4, first_top_node_no = row_node_no*(row_node_no-1);
for(int i = 0; i < row_node_no; i++) {
the_gh_array[node_order(i)](0) = gh_on_Gamma_h::Dirichlet; top boundary u = 0oC
the_gh_array[node_order(first_top_node_no+i)](0) = gh_on_Gamma_h::Dirichlet; bottom boundary u = 30oC
the_gh_array[node_order(first_top_node_no+i)][0] = 30.0;
}
}
class HeatQ4 : public Element_Formulation { public: define element
HeatQ4(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
HeatQ4(int, Global_Discretization&);
};
Element_Formulation* HeatQ4::make(int en, Global_Discretization& gd) {
return new HeatQ4(en,gd);
}
HeatQ4::HeatQ4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
Quadrature qp(2, 4); 1
N a ( ξ, η ) = --- ( 1 + ξ a ξ ) ( 1 + η a η )
H1 Z(2, (double*)0, qp), Zai, Eta, 4
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 4, 2, qp);
Zai &= Z[0]; Eta &= Z[1];
∂N ∂x – 1
∇N = ------- ------
N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4;
N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4; ∂ξ ∂ξ
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dv(d(X).det()); double k = 1.0;
∂x
}
stiff &= (Nx * k * (~Nx)) | dv; ke = ∫ ( ∇N ⊗ κ ∇N )det -----
∂ξ
- dξ
Element_Formulation* Element_Formulation::type_list = 0; Ωe
Element_Type_Register element_type_register_instance;
static HeatQ4 heatq4_instance(element_type_register_instance);
void output(Global_Discretization&);
int main() {
int ndf = 1; Omega_h oh; gh_on_Gamma_h gh(ndf, oh); U_h uh(ndf, oh);
Global_Discretization gd(oh, gh, uh); Matrix_Representation mr(gd); assembly
mr.assembly();
C0 u = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
matrix solver
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h(); update free and fixed dof
cout << gd.u_h(); output
return 0;
}
Listing 4•13 Two-dimensional heat conduction problem (project workspace file “fe.dsw”, project
“2d_heat_conduction”.
We use a 2-D 2 × 2 Gaussian quadrature for all integrable objects (line 2). In line 6 and 7, the shape functions
“N” is defined according to Eq. 4•104. The coordinate transformation rule in line 8 is from Eq. 4•106. The deriv-
ative of shape function are calculated according to Eq. 4•109 and Eq. 4•110. Line 10 on “the Jacobian” and line
12 on stiffness matrix is the first part of the Eq. 4•107. The rest of the code is not very different from that of a 1-
D problem.
1 double coord[4][2] = {{0.0, 0.0}, {3.0, 0.0}, {3.0, 3.0}, {0.0, 3.0}};
2 int control_node_flag[4] = {TRUE, TRUE, TRUE, TRUE};
3 block(this, 4, 4, 4, control_node_flag, coord[0]);
The first integer argument specifies in “block()” the number of nodes generated row-wise, which is “4”. The sec-
ond integer argument specifies the number of nodes generated column-wise. The following integer is the number
of control nodes. In this example, the four control nodes are located at node numbers “0”, “3”, “15”, and “12”
12 13 14 15
6 7 8
8 11
9 10
3 4 5
4 5 6 7
0 1 2
0 1 2 3
Figure 4•31 16 nodes and 9 elements generated by a single “block()” function call.
ordered counter-clockwise starting from the lower-left corner. The components in the int array of the
“control_node_flag” are all set as TRUE (=1). This is followed by the pointer to double array “coord[0]”. Notice
that in the semantics of C language (“pointer arithmatics”), the expression of the symbol “coord” with “[]” means
casting the double** to double*, while the index “0” means with an off-set of zero from the first memory address
of the double*.
An example with two “block()” function calls has the potential of being more adaptive to deal with compli-
cated geometry (see Figure 4•32)
1 double coord1[4][2] = {{0.0, 0.0}, {3.0, 0.0}, {3.0, 3.0}, {0.0, 3.0}},
2 coord2[4][2] = {{3.0, 0.0}, {6.0, 0.0}, {6.0, 3.0}, {3.0, 3.0}};
3 int control_node_flag[4] = {1, 1, 1, 1};
4 block(this, 4, 4, 4, control_node_flag, coord1[0], 0, 0, 3, 3);
5 block(this, 4, 4, 4, control_node_flag, coord2[0], 3, 3, 3, 3);
In this example, the coordinates of the control nodes are given as rectangles for simplicity. The first int argument
after the coordinates of type double* is the first node number generated, the next int argument is the first element
generated. The last two int arguments are “row-wise node number skip” and “row-wise element number skip”.
For example, in line 5 the second block definition has both its first node and first element numbered as “3”. The
row-wise node number and element number both skip “3”. Therefore, the first node number of the second row is
“10” and the first element number of the second row is “9”. When we define the first block in line 4 the nodes
numbered “3”, “10”, “17” and “24” has been defined. On line 5, when the “block()” function is called again,
these four nodes will be defined again. In “fe.lib”, the “block()” function use “Omega_h::set()” instead of
“Omega_h::add()”, in which the database integrity is accomplished by checking the uniqueness of the node
number. Using the terminology of relational database, the node number is the key of the database tabulae in this
case. If a node number exist, it will not be added to the database again.
A third example shows a cylinder consists of eight blocks (see Figure 4•33) which is even much more chal-
lenging. The code for generating these eight blocks is
21 22 23 24 25 26 27
12 13 14 15 16 17
14 17 20
15 16 17 18 19
6 7 8 9 10 11
7 8 9 10 10 11 11 12 13
0 1 2 3 4 5
0 1 2 3 4 5 6
21 22 23 24 24 25 26 27
12 13 14 15 16 17
17 17
14 20
15 16 common 18 19
6 7 8 nodes 9 10 11
10 10
7 8 9 11 11 12 13
0 1 2 3 4 5
0 1 2 3 3 4 5 6
Figure 4•33 A cylinder consists of eight blocks. Open circles in the left-hand-side are
control nodes. Tie nodes 164-132, 131-99, 98-66, 65-33, and 32-0 are shown in the right-
hand-side.
Five tie nodes “164-132”, “131-99”, “98-66”, “65-33”, and “32-0” (see right-hand-side of Eq. 4•33) are gener-
ated when the “tail” of the eighth block comes back to meet the “head” of the first block. The tie nodes are gen-
erated when different node number with same coordinates occurs. In fe.lib the nodes that are generated later is
“tied” to the nodes that are generated earlier. In this example nodes “0”, “33”, “66”, “99”, and “132” are gener-
ated when the first “block()” function call is made. When the eighth “block()” function call is made later, nodes
“32”, “65”, “98”, “131”, and “164” will be generated. The tie nodes are formed when the coordinates are found
to be the same as that of any node generated previously.
For heat conduction problem, if the boundary condition is symmetrical with respect to the center axis, it can
well be written with axisymmetrical formulation and solve as an one dimension problem such as in the subsec-
tion under the title of “Cylindrical Coordinates For Axisymmetrical Problem” on page 302. For the present case
of the hollow cylinder made of one material, the Eq. 4•111 expressed in cylindrical coordinates is1
1. p. 189 in Carslaw, H.S., and J.C. Jaeger, 1959, “Conduction of heat in solids”, 2nd ed. Oxford University Press, Oxford,
UK.
100
80
60
o
C
40
20
----- r ------ = 0
d du
Eq. 4•113
dr dr
The general solution is u = A+B ln r. The constants A and B are determined by imposing the boundary condi-
tions. For example, if at inner side of the cylinder of ri the temperature is kept at ui, and at outer side of the cylin-
der of ro the temperature is kept at uo, we have the solution as
ro
u i ln ---- + u o ln ---
r
r ri
u exact = ----------------------------------------------
ro
- Eq. 4•114
ln ----
ri
The finite element computation can be turned on using the same project “2d_heat_conduction” in project
workspace “fe.dsw” by setting macro definition “__TEST_CYLINDER” at compile time. The finite element
solution in the radial direction is compared to the analytical solution of Eq. 4•114 and shown in Figure 4•34.For
an additional exercise for function “block()”, we proceed with the fourth example of using three blocks to
approximate a quarter of a circle. In Chapter 3 on page 195, we approximate a quarter of a circle with three
“block()” function calls. In that case we do not have provision of repeated definitions of nodes. In the present
case, we try to minimize the number of the tie nodes by the following code
65 66
60 67
61 68
44 45 55 56 62
40 41 50 51 57 63
46 52 58 69
36 42 47 45
32 37 38 46 53 64
33 43 36 47 59
39 37 48 54 44
24 25 34 35 30
31 38 49 42 43
26 29 39 41 35
2728 23 27 40 34
16 17 22 28 33
18 19 2021 29 30 32
15 18 25 26
8 9 10 13 14 19 20
31
23
24
11 12 21 22 17
0 1 2 3 4 5 6 7 9 10 14 1516
11 12 13
element numbering 0 1 2 3 4 5 67 8
node numbering
Figure 4•35 Three block function calls to approximate a quarter of a circle. The right-hand-
side shows the element numbering scheme and the left-hand-side shows the node numbering
scheme.
The numbering of the elements and nodes for the first two blocks are similar to that of the second example. After
the third block has been generated, 9 tie-nodes will be generated including “45-36”, “46-37”, “47-38”, “48-39”,
“49-40”, “54-41”, “59-42”, “64-43”, and “69-44”.
Lines 17-24 are shape function definition for Lagragian 4-to-9-node element that we have already used in Chap-
ter 3. Lines 33, and 34 register the element formulations. The last element formulation register has the element
type number “0”. This number increases backwards to element(s) registered earlier. We can also use the
“block()” function call to define Lagrangian 9-node element as (see Figure 4•36)
Line 1 specified the elements generated are Lagragian 9-nodes elements. The last integer argument in line 3 to
line 10 indicate the element type number is 1, which corresponding to the “HeatQ9” element that we just regis-
tered. The computation of the Lagragian 9-node elements can be activated by setting macro definition
“__TEST_QUADRATIC_CYLINDER” for the same project “2d_heat_conduction” in the project workspace
file “fe.dsw”.
Figure 4•36 9-node Lagrangian quadrilateral elements generated by eight “block()” function
calls.
q = – κ ∇u
This step is often referred to as post-processing in finite element method. The derivatives of shape function,
∇N a ( ξ, η ) , on Gaussian integration points are available at the constructor of class “Element_Formulation”. The
gradients of temperature distribution are approximated by
Therefore,
Therefore, after the solutions of nodal values, û ea , are obtained, we can loop over each element to calculate the
heat flux on its Gaussian integration points, such as,
Substituting Eq. 4•117 and Eq. 4•116 into Eq. 4•118, we have
∫ Na N b dΩ q̂ eb = ∫ ( Na ( – κ ∇Nb ûeb ) ) dΩ Eq. 4•119
Ω Ω
We identify, in Eq. 4•119, the consistent mass matrix (with unit density), M, as
M ≡ ∫ N a N b dΩ Eq. 4•120
Ω
The nodal heat flux, q̂ea , can be solved from Eq. 4•119. This nodal solution procedure is described as smoothing
or projection in finite element.1 An approximation on Eq. 4•120 which alleviates the need for matrix solver is to
define lumped mass matrix as
ML ≡ ∑ ∫ Na Nb dΩ, a=b
Eq. 4•121
b Ω
0, a≠b
This is the row-sum method among many other ways of defining a lumped mass matrix.2
An alternative thinking on Eq. 4•118 of Galerkin weighting of the weighted-residual statement is that we can
write least-squares approximation of error as
1. p. 346 in Zienkiewicz, O.C., and R.L. Taylor, 1989, “The finite element method: basic formulation and linear problems”,
4the ed., vol. 1, McGraw-Hill, London, UK,
see also p. 226 in Hughes, T. J.R., “The finite element method: linear static and dynamic finite element analysis”, Prentice-
Hall, Inc., Englewood Cliffs, New Jersey.
2. see appendix 8 in Zienkiewicz, O.C., and R.L. Taylor, 1989, “The finite element method: basic formulation and linear
problems”, 4the ed., vol. 1, McGraw-Hill, London, UK.
21 int main() {
22 ...
23 Matrix_Representation::Assembly_Switch = Matrix_Representation::NODAL_FLUX;
24 mr.assembly(FALSE);
25 cout << "nodal heat flux:" << endl;
26 for(int i = 0; i < oh.total_node_no(); i++) {
27 int node_no = oh.node_array()[i].node_no();
28 cout << "{ " << node_no << "| "
29 << (mr.global_nodal_value()[i][0]) << ", "
30 << (mr.global_nodal_value()[i][1]) << "}" << endl;
31 }
32 ...
33 }
280
260
240
220
200 q
180
160
∂u ∂v
------ + ------ = 0 , Eq. 4•124
∂x ∂y
and an equation with zero vorticity component perpendicular to the x-y plane
∂u ∂v
------ – ------ = 0 Eq. 4•125
∂y ∂x
From the continuity equation Eq. 4•124, it follows that u dy - v dx is an total derivative, defined as
dψ = u dy - v dx Eq. 4•126
∂ψ ∂ψ
u = -------, and v = – ------- Eq. 4•127
∂y ∂x
Substituting Eq. 4•127 back to Eq. 4•124 gives the identity of cross derivatives of ψ to be equal. This is the con-
dition that ψ to be a potential function in calculus. Integration of Eq. 4•126 along an arbitrary path, as shown in
Figure 4•38a, gives the volume flux across the path. Along a stream line the volume flux across it is zero by def-
inition. That is along a streamline ψ is constant. Therefore, the scalar function ψ is known as the stream func-
tion.
Substituting Eq. 4•127 into the condition of irrotationality, Eq. 4•125, gives
∂2 u ∂ 2 v
div ( grad ψ ) ≡ ∇•( ∇ψ ) ≡ ∇2ψ = --------2 + --------2 = 0 Eq. 4•128
∂x ∂y
u dy C2
A
x
(a) (b)
Figure 4•38 (a) The volume flux across an arbitrary integration path is equal to u dy - v dx.
If the integration path coincides with the streamline, the volume flux across the integration
path should become zero by definition. (b) The circulation of a loop is zero for irrotational
flow. Therefore, a potential function φ can be defined which only depends on position.
°∫ u • dx = 0 Eq. 4•129
C
From Figure 4•38b, we have two different integration paths, C1 and C2, along any two points form a closed cir-
cle.
∫ u • dx + ∫ u • dx = 0, or ∫ u • dx = – ∫ u • dx Eq. 4•130
C1 C2 C1 C2
Therefore, any two paths of integration give the same result; i.e., the integration depends only on end-points.
Therefore, we can define a potential function φ, i.e.,
∂φ ∂φ
u = – ------, and v = – ------ Eq. 4•132
∂x ∂y
Again, substituting Eq. 4•132 back to Eq. 4•125 of condition of irrotationality, we have the cross derivatives of φ
which is identical to assert the exact differential nature of φ. Substituting Eq. 4•132 into the continuity equation
of Eq. 4•124, we have another Laplace equation that
This relation ensures that the gradients of stream function and velocity potential are orthogonal to each other,
since
∂φ ∂ψ ∂φ ∂ψ
∇φ • ∇ψ = ------ ------- + ------ ------- = 0 Eq. 4•135
∂x ∂x ∂y ∂y
The gradients are the normals to the equipotential lines of φ and the streamlines of ψ. Therefore, the “contours”
of φ and ψ are orthogonal to each others.
An example of finite element problem1 (a confined flow around a cylinder is shown in Figure 4•39) in both
stream function—ψ formulation and velocity potential—φ formulation are solved using VectorSpace C++
Library and “fe.lib” in the followings.
At the bottom-boundary ΓAB we choose the arbitrary reference value of ψ0 = 0. Therefore, along the left-bound-
ary ΓAE, Eq. 4•136 simplified to ψ(y) = U0 y. The streamline at boundary ΓBC follows from the boundary ΓAB
which has ψ =ψ0 (= 0). On the top-boundary ΓED, y = 2, we have ψ(2) = 2U0. Notice that the corner E is shared
by the boundaries ΓAE and ΓED. At the right-boundary ΓDC the horizontal velocity, u, is unknown, but the verti-
cal velocity v = 0; i.e., v = −∂ψ/∂x = 0.
The Program Listing 4•14 implements the Eq. 4•128 with the above boundary conditions. The only differ-
ence to the 2-D heat conduction problem is the post-processing of the derivative information.
1 if(Matrix_Representation::Assembly_Switch == Matrix_Representation::NODAL_FLUX) {
2 int velocity_no = 2;
3 the_element_nodal_value &= C0(nen*velocity_no, (double*)0);
4 C0 projected_nodal_velocity = SUBVECTOR("int, C0&", velocity_no, the_element_nodal_value);
5 H0 Velocity = INTEGRABLE_VECTOR("int, Quadrature", velocity_no, qp);
6 Velocity = 0.0;
7 for(int i = 0; i < nen; i++) {
1. p. 360-365 in Reddy, J.N., “An introduction to the finite element method”, 2nd ed., McGraw-Hill, Inc., New York.
From Eq. 4•127, the velocity is interpolated at the element formulation level as
U0 4
8
(a)
ψ = y U0 E ψ = 2U0 D E ∂φ/∂y = 0 D
∂ψ/∂x = 0 φ=0
C -∂φ/∂x = U0 C
ψ=0
∂φ/∂n = 0
A B
ψ=0 A ∂φ/∂y = 0 B
Figure 4•39(a) A confined flow around a circular cylinder. Only the upper left quadrant is
model due to symmetries of geometry, boundary conditions, and PDE. (b) stream
function B.C., and (c) velocity potential B.C.
#include "include\fe.h"
EP::element_pattern EP::ep = EP::QUADRILATERALS_4_NODES;
Omega_h::Omega_h() { const double PI = 3.141592653509, c = cos(PI/4.0), s = sin(PI/4.0),
c1 = cos(PI/8.0), s1 = sin(PI/8.0), c2 = cos(3.0*PI/8.0), s2 = sin(3.0*PI/8.0); define nodes and elements
double coord0[4][2] = {{0.0, 0.0}, {3.0, 0.0}, {1.0, 2.0}, {0.0, 2.0}},
coord1[5][2] = {{3.0, 0.0}, {4.0-c, s}, {3.0, 2.0}, {1.0, 2.0}, {4.0-c1, s1}},
coord2[5][2] = {{4.0-c, s}, {4.0, 1.0}, {4.0, 2.0}, {3.0, 2.0}, {4.0-c2, s2}};
int control_node_flag[5] = {TRUE, TRUE, TRUE, TRUE, TRUE};
block(this, 5, 5, 4, control_node_flag, coord0[0], 0, 0, 8, 8);
block(this, 5, 5, 5, control_node_flag, coord1[0], 4, 4, 8, 8);
block(this, 5, 5, 5, control_node_flag, coord2[0], 8, 8, 8, 8); }
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) { define B.C.
__initialization(df, omega_h); const double U0 = 1.0; const double h_y = 0.5;
for(int i = 0; i <= 12; i++) the_gh_array[node_order(i)](0) = gh_on_Gamma_h::Dirichlet;
for(int i = 52; i <= 64; i++) { the_gh_array[node_order(i)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(i)][0] = 2.0*U0; }
for(int i = 1; i <= 4; i++) { the_gh_array[node_order(i*13)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(i*13)][0] = (((double)i)*h_y)*U0; } }
class Irrotational_Flow_Q4 : public Element_Formulation { public:
define element formulation
Irrotational_Flow_Q4(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Irrotational_Flow_Q4(int, Global_Discretization&); };
Element_Formulation* Irrotational_Flow_Q4::make(int en, Global_Discretization& gd) {
return new Irrotational_Flow_Q4(en,gd); }
Irrotational_Flow_Q4::Irrotational_Flow_Q4(int en, Global_Discretization& gd) :
Element_Formulation(en, gd) { Quadrature qp(2, 4);
H1 Z(2, (double*)0, qp), Zai, Eta, 1
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 2, qp); N a ( ξ, η ) = --- ( 1 + ξ a ξ ) ( 1 + η a η )
4
Zai &= Z[0]; Eta &= Z[1]; N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4;
N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4;
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dv(d(X).det());
if(Matrix_Representation::Assembly_Switch == Matrix_Representation::NODAL_FLUX) { ∂N a ∂N a T
int v_no = 2; the_element_nodal_value &= C0(nen*velocity_no, (double*)0); u eh = --------- ψ̂ a, – --------- ψ̂ a
C0 projected_nodal_velocity = SUBVECTOR("int, C0&", v_no, the_element_nodal_value); ∂y ∂x
H0 Velocity = INTEGRABLE_VECTOR("int, Quadrature", v_no, qp); Velocity = 0.0;
for(int i = 0; i < nen; i++) { Velocity[0] += Nx[i][1]*(ul[i]+gl[i]);
Velocity[1] += - Nx[i][0]*(ul[i]+gl[i]); } û e = ( M L ) –1 ∫ ( Nu eh ) dΩ
for(int i = 0; i < nen; i++) { C0 lumped_mass(0.0);
Ω
for(int k = 0; k < nen; k++) lumped_mass += (((H0)N[i])*((H0)N[k])) | dv;
projected_nodal_velocity(i) = ( ((H0)N[i])*Velocity | dv ) / lumped_mass; }
∂x
} else stiff &= (Nx * (~Nx)) | dv; }
Element_Formulation* Element_Formulation::type_list = 0;
ke = ∫ ( ∇N ⊗ ∇N )det -----
∂ξ
- dξ
Element_Type_Register element_type_register_instance; Ωe
static Irrotational_Flow_Q4 flowq4_instance(element_type_register_instance);
int main() { int ndf = 1; Omega_h oh; gh_on_Gamma_h gh(ndf, oh); U_h uh(ndf, oh);
Global_Discretization gd(oh, gh, uh); Matrix_Representation mr(gd);
mr.assembly(); C0 u = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
assembly and matrix solver
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h(); cout << gd.u_h(); update free and fixed dof
Matrix_Representation::Assembly_Switch = Matrix_Representation::NODAL_FLUX; post-processing for nodal velocity
mr.assembly(FALSE); cout << "nodal velocity:" << endl;
for(int i = 0; i < uh.total_node_no(); i++)
cout << "{ " << oh.node_array()[i].node_no() << "| " <<
(mr.global_nodal_value()[i]) << "}" << endl;
return 0;
}
Listing 4•14 Stream function formulation potential flow problem(project “fe.ide”, project “potential_flow”
with macro definition “__TEST_STREAM_FUNCTION” set).
∂N a ∂Na T
u eh = --------- ψ̂ a, – --------- ψ̂ a Eq. 4•137
∂y ∂x
û e = ( M L ) –1 ∫ ( Nu eh ) dΩ Eq. 4•138
Ω
where ML is the lumped mass matrix. The results of this computation with element discretization, streamlines,
and nodal velocity vectors are shown in Figure 4•40.
2.0
1.75
1.5
1.25
ψ= 1.0
0.75
0.5
0.25
0.0
Figure 4•40 Finite element discretization (open circles are nodes), streamlines (ψ = 0-2.0 at 0.25
intervals), and nodal velocity vectors shown as arrows.
∂φ ∂φ
u = – ------, and v = – ------ Eq. 4•139
∂x ∂y
At the left-boundary ΓAE of Figure 4•39c, from u = - ∂φ/∂x, we have ∂φ/∂x = - U0. At the top and bottom-bound-
aries ΓAB and ΓED we have ∂φ/∂y = 0. On the cylinder surface ΓBC, ∂φ/∂n = 0, where n is its outward normal. At
the left-boundary ΓCD a reference value of φ is set to zero.
The code is implemented in the same project file without the macro definition
“__TEST_STREAM_FUNCTION” set at compile time. The results of this computation with element discretiza-
tion, velocity equipotential lines, and nodal velocity vectors are shown in Figure 4•41.
Inspecting Figure 4•40 and Figure 4•41, we see that the contours lines of the stream function ψ and velocity
potential φ is orthogonal to each others at every point. This is consistent with the orthogonality condition proved
φ= 5.0 4.5 4.0 3.5 3.0 2.5 2.0 1.5 1.0 0.5 0.0
0.5 0.0
1.0
1.5
2.0
Figure 4•41 Finite element discretization (open circles are nodes), velocity equi-potential lines
(φ = 0-5.0 at 0.5 intervals), and nodal velocity vectors shown as arrows.
in Eq. 4•135. The contours of stream function ψ and velocity potential φ make a smoothed mesh. Actually, this
is a popular method to generate a finite element mesh automatically.1
1. p.99-106 in George, P.L., 1991, “Automatic mesh generation: application to finite element methods”, John Wiley & Sons,
Masson, Paris, France.
where t is the traction and n is the outward unit surface normal. The weighted-residual statement of the Eq. 4•140
is
Integration by parts and then applying the divergence theorem of Gauss, we have
where the gradient operator, “grad”, and its relation to divergence operator, “div”, are
respectively. The trace operator, “tr”, is the summation of all diagonal entries. The operator “:”, in Eq. 4•143, is
the double contraction. Considering the variation of “w” is chosen to be homogeneous at Γg, the second term of
the boundary integral, in Eq. 4•143, can be restricted to Γh as
We first develop in tensorial notation for its clarity in physical meaning. The Cauchy stress tensor, σ, in Eq.
4•145 can be decomposed as
where λ and µ are the Lamé constants. µ is often denoted as G for the shear modulus. The operator def u is
defined as the symmetric part of grad u; i.e.,
where the superscript “s” denotes the symmetrical part of grad ( ≡ ∇ ), and ε is the (infiniteismal) strain tensor,
and the skew-symmetric part of grad u is defined as
1
rot u ≡ --- ( grad u – ( grad u ) T ) Eq. 4•149
2
def u and rot u are orthogonal to each other. From Eq. 4•148 and Eq. 4•149, we have the additative decomposi-
tion of grad u as
Recall the first term in Eq. 4•145, and substituting the constitutive equations Eq. 4•146 and Eq. 4•147
Note that,
The last identity is from the second part of the Eq. 4•144. With the Eq. 4•150 and the orthogonal relation of def
u and rot u, we can verify that
grad w : (2µ def u) = (def u + rot u) : (2µ def u) = 2 µ (def u : def u) Eq. 4•153
With Eq. 4•152 and Eq. 4•153, the Eq. 4•151 becomes
With the element shape function defined, e.g., as Eq. 4•104, the element stiffness matrix is
where indices {a, b} in superscripts and subscripts are the element node numbers.
In the indicial notation, we have the infinitesimal strain tensor εij(u) = def u = u(i,j) (with the parenthesis in
the subscript denotes the symmetric part), and the generalized Hooke’s law as
where δij is the Kronecker delta (δij = 1 if i = j, otherwise δij =0). The equivalence of Eq. 4•155 is
The last identity is due to the minor symmetry of cijkl. The element stiffness matrix for the indicial notation for-
mulation is
where the indices {i, j} are the degree of freedom numbers (0 ≤ i, j < ndf, where ndf is the “number degree of
freedoms” which equals to the nsd the “number of spatial dimension” in the present case; i.e., 0 ≤ k < nsd), and
the indices {a, b} are element node numbers (0 ≤ a, b < nen, where nen is the “element node number”). The rela-
tion of indices {p, q} and {i, a, j, b} are defined as
∂u ∂
------ ------ 0
εx ∂x ∂x σx
ε = εy = ∂v = 0 ----- ∂ u , and σ = σy Eq. 4•164
------ -
∂y ∂y v
γ xy ∂u ∂v ∂ ∂ τ xy
------ + ------ ------ ------
∂y ∂x ∂y ∂x
In plane strain case, we can show that the fourth-order tensor D becomes a matrix as
λ + 2µ λ 0
D = λ λ + 2µ 0 Eq. 4•166
0 0 µ
2λµ
λ = ---------------- Eq. 4•167
λ + 2µ
In engineering applications, the Young’s modulus, E, and Poisson’s ratio, ν, are often given instead of the Lamé
constants. They can be related as
νE E
λ = --------------------------------------, and µ = -------------------- Eq. 4•168
( 1 + ν ) ( 1 – 2ν ) 2(1 + ν)
rewritten Eq. 4•105 for a = 0, 1, ..., (nen - 1), and i = 0, ..., (ndf - 1)
∂N a
--------- 0
∂x
∂N a
Ba = 0 --------- , and B = B 0 B 1 B2 … B n – 1 Eq. 4•171
∂y
∂N a ∂N a
--------- ---------
∂y ∂x
PL 3 3(1 + ν)
v = – --------- 1 + -------------------
- Eq. 4•175
3EI L2
τy = 150 psi
2 in.
10 in.
10 11 12 13 14
fy,10 = -75 ux,14 = 0
4 5 6 7
fy,5 = -150 5 6 7 8 9 ux,9 = 0, and uy,9 = 0
0 1 2 3
fy,0 = -75 ux,4 = 0
0 1 2 3 4
10 11 12 13 14
fy,10 = -50 ux,14 = 0
fy,5 = -200 5 6 0 7 8 1 9 ux,9 = 0, and uy,9 = 0
fy,0 = -50 ux,4 = 0
0 1 2 3 4
1. p. 473 in Reddy, J.N. 1993, “ An introduction to the finite element method”, 2nd ed., McGraw-Hill, Inc., New York.
Line 17 is the computation of the derivatives of the shape function “Nx” (see Figure 4•43). The “Nx” is then par-
titioned into submatrix “w_x”. The regular increment submatrices wx &= w_x[0][0] and wy &= w_x[0][1] are
#include "include\fe.h"
static const double L_ = 10.0; static const double c_ = 1.0; static const double h_e_ = L_/2.0;
static const double E_ = 30.0e6; static const double v_ = 0.25;
static const double lambda_ = v_*E_/((1+v_)*(1-2*v_)); Young’s modulus and Poisson ratio
static const double mu_ = E_/(2*(1+v_)); plane stress λ modification
static const double lambda_bar = 2*lambda_*mu_/(lambda_+2*mu_);
EP::element_pattern EP::ep = EP::QUADRILATERALS_4_NODES;
Omega_h::Omega_h() {
double x[4][2] = {{0.0, 0.0}, {10.0, 0.0}, {10.0, 2.0}, {0.0, 2.0}}; int flag[4] = {1, 1, 1, 1};
block(this, 3, 5, 4, flag, x[0]);
}
generate nodes and elements
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) { __initialization(df, omega_h); B.C.
the_gh_array[node_order(4)](0) = the_gh_array[node_order(9)](0) = u4 = u9 = v9 = u14 = 0
the_gh_array[node_order(9)](0)=the_gh_array[node_order(14)](0)=gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(0)](1) = the_gh_array[node_order(5)](1) =
the_gh_array[node_order(10)](1) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(0)][1] = the_gh_array[node_order(10)][1] = -75.0; τy0 = τy10 = -75, τy5 = -150
the_gh_array[node_order(5)][1] = -150.0;
}
class ElasticQ4 : public Element_Formulation { public:
ElasticQ4(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
ElasticQ4(int, Global_Discretization&);
};
Element_Formulation* ElasticQ4::make(int en, Global_Discretization& gd) {
return new ElasticQ4(en,gd);
} 1
static const double a_ = E_ / (1-pow(v_,2)); N a ( ξ, η ) = --- ( 1 + ξ a ξ ) ( 1 + η a η )
4
static const double Dv[3][3] = {{a_, a_*v_, 0.0}, {a_*v_, a_, 0.0 }, {0.0, 0.0, a_*(1-v_)/2.0} };
∂N ∂x –1
∇N = ------- ------
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
ElasticQ4::ElasticQ4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) { ∂ξ ∂ξ
Quadrature qp(2, 4);
H1 Z(2, (double*)0, qp), Zai, Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 2, qp); ∂Na
Zai &= Z[0]; Eta &= Z[1]; --------- 0
N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4; ∂x
N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4; ∂N a
H1 X = N*xl;
Ba = 0 ---------
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), wx, wy, B;
∂y
wx &= w_x[0][0]; wy &= w_x[0][1]; ∂Na ∂N a
--------- ---------
B &= (~wx || C0(0.0)) &
∂y ∂x
(C0(0.0) || ~wy ) &
(~wy || ~wx );
stiff &= ((~B) * (D * B)) | dv;
} k e = e iT ∫ B aT D Bb dΩe j
Element_Formulation* Element_Formulation::type_list = 0;
Element_Type_Register element_type_register_instance; Ω
static ElasticQ4 elasticq4_instance(element_type_register_instance);
int main() { int ndf = 2; Omega_h oh; gh_on_Gamma_h gh(ndf, oh); U_h uh(ndf, oh);
Global_Discretization gd(oh, gh, uh); Matrix_Representation mr(gd); mr.assembly();
C0 u = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h();
cout << gd.u_h();
return 0;
}
Listing 4•15 Plane elastiticity (project workspace file “fe.dsw”, project “2d_beam” with Macro definition
“__TEST_B_MATRIX_CONCATENATE_EXPRESSION_SUBMATRIX” set at compile time).
Line 4 takes the size and type of the transpose of “wx”, then re-assigns its values to zero. Line 7 uses unary pos-
itive operator “+” to convert a Integrable_Nominal_Submatrix (of object type H0) into a plain Integrable_Matrix
(also of object type H0). We note that the expression “U[2][1]” can be written as “(e3[2] % e[1]) * (~E)” without
having to define the additional symbol “U = (e3%e)*(~E)”. One needs to set both macro definitions of
“__TEST_B_MATRIX_CONCATENATE_EXPRESSION_SUBMATRIX” and “__TEST_BASIS” for this
implementation at compile time
The semantics in the construction of B-matrix in the above is a bottom-up process. We first define the com-
ponents of the B-matrix than built the B-matrix with these pre-constructed components. The semantics of the
program code can be constructed in a reversed order; i.e., top-down process. We may want to construct the B-
matrix first, giving its size and initialized with default values (“0.0”). Then, we can assign each components of
the B-matrix with its intended values.
The B-matrix is constructed first, then, its components {εx, εy, γxy}T are assign according to the definition in the
first part of Eq. 4•164 and Eq. 4•170, where the strain “epsilon” is a submatrix referring to “B” matrix. For this
implementation the same project “2d_beam” in project workspace file “fe.dsw” can be used with only the macro
definition “__TEST_B_MATRIX_CONCATENATE_EXPRESSION_TOP_DOWN” set.
We see that this implementation takes direct image of the right-hand-side block in the Figure 4•43. In the above
code, no submatrix facility is used only the concatenate operator “|” is used to built the B-matrix from ground-up.
Comparing the bottom-up with the top-down algorithms, the only difference is the semantics. In the last algo-
rithm, we have flatten out the submatrix into simple matrix. In doing so, we can avoid using the requirement of
submatrix features supported by the VectorSpace C++ Library. We may want to optimize the rapid-proto-typing
code by eliminating the features supported in VectorSpace C++ Library step-by-step, such that the overhead
caused by the use of VectorSpace C++ Library can be alleviated.
A even more Fortran-like equivalent implementation is as the followings1 (set the macro definition to noth-
ing)
1. p. 153 in Thomas J.R. Hughes, 1987, “ The finite element method: Linear and dynamic finite element analysis.”, Prentice-
Hall, Englewood Cliffs, New Jersey.
In lines 2-14, provision is taken to eliminate the multiplication with “0” components in BTDB. Only the “nodal
submatrices”—keab in the diagonal and upper triangular matrix of ke is computed. The lower triangular part
matrix is then determined by symmetry with keab = (keba)T (lines 15-21). We recognize that this is the idiom of
using the low-level language expression with indices in accessing the submatrices of the matrix ke as “k[ndf a +
i][ndf b + j]”. By this way, we may avoid using the submatrix facility in VectorSpace C++ Library entirely. Cer-
tainly the optimized low-level code is much longer, less readable, and harder to maintain for programmers.
Nonetheless, this last version can be easily optimized even more aggressively in plain C language without using
the VectorSpace C++ Library at all. The last step is to have an numerical integration at the most outer loop where
we evaluate all values at Gaussian quadrature points and multiply these values with their corresponding weights.
∂N ∂N ∂N ∂N
---------a ---------b ---------a ---------b
∂x ∂x ∂y ∂x
λ ( N a, i Nb, j ) = λ Eq. 4•176
∂N ∂N ∂N ∂N
---------a ---------b ---------a ---------b
∂x ∂y ∂y ∂y
Note that λ may replace λ for the plane stress case in Eq. 4•167. The rest of the integrands of Eq. 4•161 is its
deviatoric part
µ ( δ ij ( Na, k N b, k ) + ( Na, j N b, i ) )
∂N ∂N ∂N ∂N ∂N ∂N ∂N ∂N
---------a ---------b + ---------a ---------b 0 ---------a ---------b ---------a ---------b
∂x ∂x ∂y ∂y ∂x ∂x ∂y ∂x
µ +µ
= ∂N ∂N ∂N ∂N ∂N ∂N ∂N ∂N
0 ---------a ---------b + ---------a ---------b ---------a ---------b ---------a ---------b
∂x ∂x ∂y ∂y ∂x ∂y ∂y ∂y
∂N a ∂N b ∂N a ∂N b ∂N ∂N
2 --------- --------- + --------- --------- ---------a ---------b
∂x ∂x ∂y ∂y ∂y ∂x
= µ Eq. 4•177
∂N ∂N ∂N ∂N ∂N ∂N
---------a ---------b ---------a ---------b + 2 ---------a ---------b
∂x ∂y ∂x ∂x ∂y ∂y
Eq. 4•176 and Eq. 4•177 are implemented as (by setting, at compile time, the macro definition of
“__TEST_INDICIAL_NOTATION_FORMULATION” )
Line 4-8 implements the integrand of the volumetric element stiffness by Eq. 4•176 and line 11-18 implements
the integrand of the deviatoric element stiffness by Eq. 4•177. Note that the unary positive operator in front of
both line 6 and line 13 are conversion operation to convert an Integrable_Nominal_Submatrix (of object type
H0) into an Integrable_Matrix (of type H0). An Integrable_Submatrix version of this implementation will be
The same implementation with one-by-one concatenation operations “||” and “&&” will be
1. p. 155 in Thomas J.R. Hughes, 1987, “ The finite element method: Linear and dynamic finite element analysis.”, Prentice-
Hall, Englewood Cliffs, New Jersey.
∂N ∂N ∂N ∂N
---------a ---------b ---------a ---------b
∂x ∂x ∂y ∂x
keab (temporary) = ∫ ∂N ∂N ∂N ∂N
dΩ Eq. 4•178
Ω ---------a ---------b ---------a ---------b
∂x ∂y ∂y ∂y
Then, ke is overwritten by the rest of the codes. Lines 13-34 will have no integrable objects involved. In both of
these two parts, the symmetry consideration is taken, and only the components of the diagonal nodal submatrix
and upper-triangular nodal submatrices belonging to the upper triangular part of ke are calculated, to reduce the
number of calculation. Firstly, line 17 calculates the following quantity and store in the variable “temp”
∂N ∂N ∂N ∂N
---------a ---------b + ---------a ---------b dΩ
∫ ∂x ∂x ∂y ∂y
Eq. 4•179
Ω
∂N a ∂N b ∂N a ∂N b
( λ + 2µ ) --------- --------- + µ --------- --------- ∅
∂x ∂x ∂y ∂y
∫ ∂N a ∂N b ∂N a ∂N b
dΩ Eq. 4•180
Ω
∅ ( λ + 2µ ) --------- --------- + µ --------- ---------
∂y ∂y ∂x ∂x
where the null symbol “ ∅ ” denotes the corresponding components in the matrix are not calculated. Lines 22-24,
and 25-30 get the off-diagonal components of nodal submatrices
∂Na ∂N b ∂N a ∂N b
∅ λ --------- --------- + µ --------- ---------
∂x ∂y ∂y ∂x
∫ ∂N a ∂N b ∂N a ∂Nb
dΩ Eq. 4•181
Ω
λ --------- --------- + µ --------- --------- ∅
∂y ∂x ∂x ∂y
Special care is taken in lines 22-23, when the nodal submatrices are diagonal nodal submatrices. In the case the
node number index is “a”, we have Na,x Na,y = Na,y Na,x. That is the off-diagonal components in the diagonals
nodal submatrices in Eq. 4•181 is reduced to
∂N a ∂Na
( λ + µ ) ∫ ∅ ---------
∂x ∂y dΩ
--------- Eq. 4•182
Ω ∅ ∅
For these diagonal nodal submatrices the off-diagonal components calculation is therefore further simplified to
lines 22-23. Notice that components in lower-left corner of Eq. 4•182 are not calculated, because these compo-
The inner product gives a scalar. The implementation for the coordinate free tensorial formulation will be based
on Eq. 4•156 which is
where N a ∈ V h , and superscripts and subscripts {a, b} are the element node numbers. The element variables,
e.g., in 2-D elasticity for bilinear 4-nodes element, are arranged in the order of u = {u0, v0, u1, v1, u2, v2, u3, v3}T.
The variable vector u has the size of (ndf × nen) = 2 × 4 =8. Therefore, we identify that the finite element space—
Vh(Ωe) has its inner product operation producing an element stiffness matrix, ke , of size (ndf × nen) × (ndf × nen)
= 8 × 8. We also observed that the differential operators “div”, “def”, and the double contraction “:” on the finite
element space, Vh(Ωe), all need to be defined. The closest thing to the finite element space, Vh(Ωe), in Vector-
Space C++ Library is the type H1 which is an integrable type differentiable up to the first order. However, H1 is
certainly not a finite element space. The inner product of objects defined by H1 will not generate a
(ndf × nen) × (ndf × nen) element stiffness matrix, neither does it has the knowledge of “div”, “def” or “:” opera-
tors. We may implement a customized, not intended for code reuse, class “H1_h” in ad hoc manner for the finite
element space—Vh(Ωe) as
The differential operators “div” and “def” are applied to the finite element space—Vh(Ωe) which can be imple-
mented as an abstract data type “H1_h”. The return values of these differential operators are of yet another
abstract data type “H0_h”. In the terminology of object-oriented analysis, H0_h “IS-A” H0 type. The “IS-A”
relationship between H0_h and H0 is manifested by the definition of class H0_h as publicly derived from class H0
(line 11). We can view class “H0_h” as an extension of class H0 to define the double contraction operation “:”.
The double contraction operator is defined as a public member binary operator “H0_h::operator ^ (const
H0_h&)” (line 14). We emphasize that with the public derived relationship, class H0_h inherits all the public
interfaces and implementations of class H0. Moreover, we design to have H1_h used in the element formulation
as close to the mathematical expression as possible. Lines 16-20 are auxiliary free functions defined to provide
better expressiveness, such that, we may write in element formulation as simple as
which is almost an exact translation of high-flown mathematical expression of Eq. 4•184. The constructor of
class H1_h take two arguments of type H1. The first argument is the shape functions—“N”, and the second argu-
ment is the physical coordinates— “X”. The derivatives of the shape function can be computed from these two
objects as
H0 Nx = d(N) * d(X).inverse();
These two objects have been defined earlier in the element formulation. Now we get to the definition of the
divergence operator “div” according to Eq. 4•144
Or in the form of the nodal subvector (row-wise) for the finite element space—Vh(Ωe) as
∂N a ∂N a
--------- --------- Eq. 4•186
∂x ∂y
1 H0_h H1_h::div_() {
2 H0 Nx = n.d() * x.d().inverse();
3 H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2, Nx);
4 H0 wx = (+w_x[0][0]), wy = (+w_x[0][1]);
5 C0 u = BASIS("int", 2), E = BASIS("int", 4);
6 H0 ret_val = wx(0)*(u[0]*E) + wy(0)*(u[1]*E); // Eq. 4•186
7 return ~(+ret_val);
8 }
This divergence operation will return an Integrable_Matrix of size 1 × 8. Therefore, the inner product,
“ div • div ”, not with respect to node number, will return an element stiffness matrix object (an
Integrable_Matrix of type H0) of size 8 × 8. The gradient operator “grad” is defined (also in Eq. 4•144)
∂u ∂v
------ ------
∂x ∂x
grad u = ∇ ⊗ u = u i, j = Eq. 4•187
∂u ∂v
------ ------
∂y ∂y
Notice that we arrange “u”, “v” in row-wise order to be compatible with the order of the variable vector in ele-
ment formulation. This special ordering makes the gradient tensor in Eq. 4•187 as the transpose of the ordinary
mathematical definition on grad u. The nodal submatrices of the return value of “grad” operator are
∂N a ∂N a
--------- ---------
∂x ∂x
Eq. 4•188
∂N a ∂N a
--------- ---------
∂y ∂y
Eq. 4•188, for “grad” operator on Vh, should return a 2 × 8 Integrable_Matrix, and it is implemented as
1 H0_h H1_h::grad_() {
2 H0 Nx = n.d() * x.d().inverse();
3 H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2, Nx), wx, wy;
The operator “gradT” is defined independently from “grad” for the finite element space—Vh(Ωe), which can not
be obtained by the transpose of the resulting matrix of “grad”. This is because that the transpose operation on
grad is with respect to its spatial derivatives only not with respect to element node number index—a. Both differ-
ential operators “grad” and “gradT” have return value, with the size of 2 × 8, of type H0_h which is derive from
Integrable_Matrix of type H0. The operator gradT has its nodal submatrices
∂N a ∂N a
--------- ---------
∂x ∂y
Eq. 4•189
∂N ∂N a
---------a ---------
∂x ∂y
which is implemented as
1 H0_h H1_h::grad_t_() {
2 H0 Nx = n.d() * x.d().inverse();
3 H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2, Nx), wx, wy;
4 wx &= ~(+w_x[0][0]); wy &= ~(+w_x[0][1]);
5 C0 eu = BASIS("int", 4),
6 e = BASIS("int", 2),
7 E1 = BASIS("int", 1),
8 E2 = BASIS("int", 4),
9 a = (e%eu)*(E1%E2);
10 H0 ret_val = wx*a[0][0] + wy*a[0][2] + //Eq. 4•189
11 wx*a[1][1] + wy*a[1][3];
12 return ret_val;
13 }
The operator “def”, for the finite element space—Vh(Ωe), is defined according to Eq. 4•148
1
def u ≡ --- ( grad u + ( grad u ) T ) Eq. 4•190
2
With both “grad” and “gradT” already defined, “def” can be implemented simply as
The differential operator def also return a 2 × 8 H0_h type object. The double contraction is defined in Eq. 4•154
The implementation of the binary operator “^” as double contraction operator is completely ad hoc. Under the
discretion of the programmer, it has assumed that the two operands of the binary operator are the return values of
the def operator. The return value has the size of 8 × 8. This is evident from the left-hand-side of Eq. 4•191.
1 H0 H0_h::operator^(const H0_h& a) {
2 H0 ret_val(8, 8, (double*)0, a.quadrature_point());
3 H0 ret_sub = INTEGRABLE_SUBMATRIX("int, int, H0&", 2, 2, ret_val);
4 H0 def_w = INTEGRABLE_SUBMATRIX("int, int, H0&", 2, 4, a);
5 for(int a = 0; a < 4; a++)
6 for(int b = 0; b < 4; b++) {
7 H0 def_wa = +def_w(0,a), def_wb = +def_w(0,b);
8 H0 def_def = (~def_wa)*def_wb; // (def u)Ta (def u)b
9 H0 dds = INTEGRABLE_SUBMATRIX("int, int, H0&", 2, 2, def_def);
10 ret_sub(a,b) = +(dds(0,0)+dds(1,1)); // trace of “(def u)Ta (def u)b”
11 }
12 return ret_val;
13 }
Line 3 is the nodal submatrices that we calculated according to Eq. 4•191, and upon which we loop over all
nodes. This implementation can be activated by setting, at compile time, the macro definition
“__TEST_COORDINATE_FREE_TENSORIAL_FORMULATION” for the same project “2d_beam” in project
workspace file “fe.dsw”.
The extension of H1 class in VectorSpace C++ Library to finite element space—Vh(Ωe) as H1_h class in the
above is an example of the so-call programming by specification in the object-oriented method.
The class “Matrix_Representation” has the member function “assembly()” which maps
“the_element_nodal_value” to the “mr.global_nodal_value()” used in the “main()” function. The reaction is not
computed in the present example of project “beam_2d”. The next project “patch_test”, in the next section, will
compute this quantity.
After the nodal displacements, ûea , are obtained, we can loop over each element to calculate the stresses on each
Gaussian integration point as,
σe ≡ Na ( ξ, η ) σ̂e
h a
Eq. 4•193
∫ Na ( σ e – σeh ) dΩ
h
= 0 Eq. 4•194
Ω
Substituting Eq. 4•192 and Eq. 4•117 into Eq. 4•118, we have
b
∫ N a N b dΩ σ̂ e = ∫ ( Na ( D Bûea ) ) dΩ Eq. 4•195
Ω Ω
The nodal stresses σ̂ e can be solved for from Eq. 4•195. Following the same procedure for the heat flux projec-
a
tion on node, in the previous section, Eq. 4•195 can be approximated similarly for the stress nodal projection by
implementing the following codes.
The computation of strains on Gaussian integration points and nodes is similar to the computation of stresses. In
place of Eq. 4•192 for stresses, we have strains computed according to ε eh = Bû ea . The flag
“Matrix_Representation::Assembly_Switch” is now set to “Matrix_Representation::STRAIN” and
“Matrix_Representation::NODAL_STRAIN” for Gauss point stresses and nodal stresses, respectively. The
results of relative magnitudes of displacements, nodal stresses and nodal strains of the 4-node quadrilateral ele-
ment are shown in Figure 4•44.
We introduce the notorious pathology of the finite element method by demonstrating (1) shear locking and
(2) dilatational locking for the bilinear four-node element in plane elasticity.
Figure 4•44Displacement (arrows), nodal stresses (crossed-hairs, solid line for compression, dashed
line for tension), and nodal strain (ellipsoidals) of the beam bending problem. The magnitudes of these
three quantities have all been re-scaled.
1
N a ( ξ, η ) = --- ( 1 + ξ a ξ ) ( 1 + η a η ) Eq. 4•196
4
We considered a special case of a rectangle (Eq. 4•45a), for simplicity, under applied bending moment as shown
in Figure 4•45. Therefore, the finite element space is spanned by the bases of P = {1, ξ, η, ξη}. Since referential
coordinates ξ- and η- axes of the rectangle is assumed to coincide with the physical coordinates x- and y- axes,
the finite element space is also spanned by {1, x, y, xy}. The solution to the displacement field u = [u, v]T for the
bending problem, in plane stress, is1
xy
u
u = = 1 2 υ 2 Eq. 4•197
v – --- x – --- y
2 2
This analytical solution is shown in Figure 4•45b with υ = 0 for simplicity. The horizontal displacement compo-
nent, u = xy, will be represented correctly by the bilinear four-node element, since the basis “xy” is included. The
quadratic terms, x2 and y2, in the solution of vertical displacement “v” will not be captured by the element. These
quadratic forms of solution will be “substituting” or “aliasing” to the linear combination of bases in P. For the
bilinear four-node element the shape functions Eq. 4•196 can be expressed in its generic form as “ Na = PC-1 ”.2
Therefore, from Eq. 4•196, we have
1 1 1 1
1 –1 1 1 –1
u eh ( ξ, η ) ≡ N a ( ξ, η )û ea = P ( ξ, η )C – 1 û ea, where C – 1 = --- Eq. 4•198
4 –1 –1 1 1
1 –1 1 –1
ξ
1
1. p.218 in MacNeal, R.H., 1994, “Finite elements: their design and performance”, Marcel Dekker, Inc., New York.
2. p. 116 in Zienkiewicz, O.C. and R.L. Taylor, 1989, “The finite element method: basic formulation and linear problems”,
vol. 1, McGraw-Hill book company, UK.
ξ 02
1
ξ 12 1
û ea = ( ξa ) 2 = = , Eq. 4•199
ξ 22 1
1
ξ 32
and,
1 1 1 1 1
= 1 ξ η ξη ---
–1 1 –1 1 1 –1 1
u eh = PC û ea = 1 Eq. 4•200
4 –1 –1 1 1 1
1 –1 1 –1 1
That is we have the alias of ξ 2 ⇒ 1 . By symmetry of the element we can also obtain the alias of η 2 ⇒ 1 . The
vertical displacement solution in the bending problem in Eq. 4•197 will then be aliased, considering the aspect
ratio “Λ” in the transformation of natural to physical coordinates in a rectangular element, into
Λ2 ν
u = xy, and v = – ------ – --- = cons tan t Eq. 4•201
2 2
With vertical displacement “v” as constant through out the element domain, the deformation becomes a “key-
stoning” or “x-hourglass” mode (see Figure 4•45c, where the constant “v” is set to zero for comparing to the
original configuration). That is the lower-order element, such as the bilinear 4-node element, exhibits locking
phenomenon, when a boundary value problem corresponding to a higher-order solution is imposed.
The analytical strain, derived from Eq. 4•197, corresponding to the bending problem is
∂u
------
εx ∂x
y
εy = ∂v = – νy Eq. 4•202
------
∂y
γxy ∂v ∂u
0
------ + ------
∂x ∂y
where u and v are solutions in Eq. 4•197. The bilinear 4-node element under the same bending condition
responds with the solution in Eq. 4•201, and we have the corresponding strains as
εx y
εy = 0 Eq. 4•203
γ xy x
Comparing Eq. 4•202 and Eq. 4•203, both εy and γxy are in error. With Poisson’s ratio in the range of ν = [0, 0.5],
γxy will be more serious than εy. The source of error is the interpolating failure of the bilinear four node element
which leads to the aliasing of x2 and y2 terms in Eq. 4•197 into constants in Eq. 4•201. A partial solution to this
locking problem is to evaluate γxy at ξ = 0, and η = 0. That is one Gauss point integration of in-plane shear strain
at the center of the element, and 2 × 2 integration for the remaining direct strain components εx and εy. A more
satisfactory treatment is to add back both x2 and y2 to the set of shape functions which is the subject of “non-con-
forming element” in page 502 of Chapter 5. We introduce the treatment by selective reduced integration on in-
plane shear strain γxy(at ξ = 0, η = 0) in the followings.
Eq. 4•176 and Eq. 4•177 are re-written as
∂N ∂N ∂N ∂N
---------a ---------b ---------a ---------b
∂x ∂x ∂x ∂y
λ ( N a, i N b, j ) = λ Eq. 4•204
∂N ∂N ∂N ∂N
---------a ---------b ---------a ---------b
∂y ∂x ∂y ∂y
and
µ ( δ ij ( N a, k Nb, k ) + ( N a, j N b, i ) )
∂N a ∂N b ∂N ∂N ∂N ∂N
2 --------- --------- 0 ---------a ---------b ---------a ---------b
∂x ∂x ∂x ∂x ∂y ∂x
= µ +µ Eq. 4•205
∂N a ∂N b ∂N ∂N ∂N ∂N
0 2 --------- --------- ---------a ---------b ---------a ---------b
∂y ∂y ∂x ∂y ∂y ∂y
Notice that the positions in the stiffness matrix corresponding to variables u and v and their variations u’ and v’
as
( u’u ) ( u’v )
Eq. 4•206
( v’u ) ( v’v )
The components in Eq. 4•204 and the first term in Eq. 4•205 only involve the direct strains εx(=u,x) and εy(=v,y).
These terms are evaluated with 2 × 2 points Gauss integration (the full-integration). The components in the sec-
ond term of Eq. 4•205 involve the in-plane shear strain γxy(=u,y+v,x), and these are to be evaluated at the center
of the element where ξ = 0, η = 0. This term is applied with 1-point Gauss integration (the reduced integration.)
In retrospect, had we apply 1-point integration to all terms, spurious modes (x-hourglass and y-hourglass)
will arise. That is the two hourglass modes become eigenvectors for the stiffness matrix that is evaluated at the
center of the element. This is evident from Figure 4•45c. The cross-hairs which parallel to the ξ, η axes are dis-
∂ξ ∂η
------ dy = ------ dx Eq. 4•207
∂y ∂x
This approximation is possible to make the shear term nearly invariant if we deal only with element shapes that
are very close to a square element. At the limit of infinitesimal coordinate transformation, Eq. 4•207 is to assume
the “spin” at the centroid vanishes, which is adopted in the “co-rotational” formulation in finite element method.
The invariance formulation, discussed in the above, can be activated by setting macro definition
“__TEST_HUGHES” at compile time.
Unfortunately, for an arbitrary element shape, the mapping from the reference element (in ξ, η) to physical
element (in x, y) is unlikely to be infinitesimal as can be approximated in Eq. 4•207. For an arbitrary element
shape, we can decomposed the shape distortion into eigenvectors as rectangular, parallelogram, and trapezoid
shapes (see Figure 4•48b). There is no practical invariance formulation that can remove the shape sensitivity if
the trapezoid component for a particular element shape is strong.2 In a finite element program, which often
implemented with sparse matrix technique, the node-ordering can be changed, for example, in order to minimize
the bandwidth of the global stiffness matrix. Sudden change of the node-ordering can therefore inadversarily
change the value of the global stiffness matrix dramatically. A practical fixed to remedy the frame dependent in-
1. see project in p. 261-262 from Hughes, T.J.R., 1987, “ The finite element method: linear static and dynamic finite element
analysis”, Prentice-Hall, Inc., Englewood Cliffs, New Jersey.
2. see p.241-248 in MacNeal, R.H., 1994, “Finite elements: their design and performance”, Marcel Dekker, Inc., New York.
#include "include\fe.h"
static const double L_ = 10.0; static const double c_ = 1.0; static const double h_e_ = L_/4.0;
static const double E_ = 30.0e6; static const double v_ = 0.25;
static const double lambda_ = v_*E_/((1+v_)*(1-2*v_));
Young’s modulus and Poisson ratio
static const double mu_ = E_/(2*(1+v_));
static const double lambda_bar = 2*lambda_*mu_/(lambda_+2*mu_); plane stress λ modification
static const double K_ = lambda_bar+2.0/3.0*mu_;
static const double e_ = 0.0;
Omega_h::Omega_h() {
Node *node; double v[2]; int ena[4]; Omega_eh *elem; define nodes
v[0] = 0.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node);
v[0] = h_e_-e_; node = new Node(1, 2, v); node_array().add(node);
v[0] = 2.0*h_e_-2.0*e_; node = new Node(2, 2, v); node_array().add(node);
v[0] = 3.0*h_e_-e_; node = new Node(3, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(4, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 1.0*c_; node = new Node(5, 2, v); node_array().add(node);
v[0] = 1.0*h_e_; node = new Node(6, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(7, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(8, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(9, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 2.0*c_; node = new Node(10, 2, v); node_array().add(node);
v[0] = h_e_+e_; node = new Node(11, 2, v); node_array().add(node);
v[0] = 2.0*h_e_+2.0*e_; node = new Node(12, 2, v); node_array().add(node);
v[0] = 3.0*h_e_+e_; node = new Node(13, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(14, 2, v); node_array().add(node);
ena[0] = 0; ena[1] = 1; ena[2] = 6; ena[3] = 5; define elements
elem = new Omega_eh(0, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 1; ena[1] = 2; ena[2] = 7; ena[3] = 6;
elem = new Omega_eh(1, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 2; ena[1] = 3; ena[2] = 8; ena[3] = 7;
elem = new Omega_eh(2, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 3; ena[1] = 4; ena[2] = 9; ena[3] = 8;
elem = new Omega_eh(3, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 5; ena[1] = 6; ena[2] = 11; ena[3] = 10;
elem = new Omega_eh(4, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 6; ena[1] = 7; ena[2] = 12; ena[3] = 11;
elem = new Omega_eh(5, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 7; ena[1] = 8; ena[2] = 13; ena[3] = 12;
elem = new Omega_eh(6, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 8; ena[1] = 9; ena[2] = 14; ena[3] = 13;
elem = new Omega_eh(7, 0, 0, 4, ena); omega_eh_array().add(elem);
} B.C.
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h); int row_node_no = 5, col_node_no = 3;
u4 = u9 = v9 = u14 = 0
the_gh_array[node_order(4)](0) = the_gh_array[node_order(14)](0) =
the_gh_array[node_order(4)](1) = the_gh_array[node_order(9)](1) =
Listing 4•16 Seletive reduce integration on the offending shear term (project workspace file “fe.dsw”,
project “invariance_formulation”.)
y’
y
θ2 θ2
θ1
x’
θ1
x
Figure 4•47 MacNeal’s local preferred coordinate system for selective reduced
integration on shear term.
plane shear (after reduced integration) is to implement an algorithm to select, for example, the longest edge of
the elements to begin element node numbering. 1 Then transform the global coordinate system, for computing the
stiffness matrix, under a preferred local coordinate system. After the stiffness is computed at the element level, it
is transformed back to the global coordinate system then assembled to the global stiffness matrix. The origin of
the local coordinate system is chosen as center at the intersection of the two diagonals of the quadrilateral. The x-
axis is chosen to be the bisector of the diagonal angle as shown in Figure 4•47. This implementation can be acti-
vated by setting macro definition “__TEST_MACNEAL”. Note that for simplicity we do not implements the
part of algorithm that choose the longest edge. We only implemented the more mathematical part of the algo-
rithm that demonstrates how to translate to the center of the intersection of the two diagonals and then rotate to
the local coordinate x’-axis, which is the bisector of the diagonals.
η y’
θ2 = η,x
x’
y ξ
θ1 = ξ,y
Figure 4•46 Hughes’s local preferred coordinate system for the invariance formulation of the
shear term under selective reduced integration. θ1 ||dy|| = θ2 ||dx||, or simply θ1 = θ2 , if ||dy|| ~ ||dx||
which is consistent with the infinitesimal mapping assumption.
Full Integration Selective Reduced Hughes’ local coord. MacNeal’s local coord. Analytical
-0.00311871 -0.0061448 -0.00535423 -0.00565686 -0.00518750
TABLE 4•2. Tip-deflections for selective reduced integration to prevent shear locking and
choices of local preferred coordinate system for invariance of the formulation.
1 δ δ
1 ν
u = xy, and v = – --- x 2 – -------------------- y 2 Eq. 4•208
2 2(1 – ν )
εx y
εy ν
= – ---------------- y Eq. 4•209
(1 – ν)
γ xy 0
where the bulk modulus K and Young’s modulus E, Poisson’s ratio ν are related as
E
K = ----------------------- Eq. 4•211
3 ( 1 – 2ν )
Notice that even when ν → 0.5 , we have K → ∞ (Eq. 4•211), and ε v → 0 (Eq. 4•210), while the pressure “p”
(Eq. 4•210) remains finite. For a 4-node rectangular element, the aliasing of solution in Eq. 4•208 leads to
1 ν
u = xy, and v = – --- Λ 2 – -------------------- Eq. 4•212
2 2(1 – ν)
εx y
εy = 0 Eq. 4•213
γxy x
1. p.216-217 in MacNeal, R.H., 1994 “Finite elements: their design and performance”, Marcel Dekker, Inc., New York.
A volumetric-deviatoric split1 is applied to the stiffness of Eq. 4•214 into the volumetric part and deviatoric part.
Define the volumetric strain εv as
ε v = ε x + εy = m • ε Eq. 4•215
In vector form of plane elasticity, m = [1, 1, 0]T and ε = [εx, εy, γxy]T. The mean stress or pressure is
p ≡ --- ( σ x + σ y + σ z ) = Kε v = K m • ε
1
Eq. 4•216
3
mε m⊗m
ε d ≡ ε – --------- = I – ----------------- ε
v
- Eq. 4•217
3 3
σd = µ D 0 ε d = µ D 0 – --- m ⊗ m ε
2
Eq. 4•218
3
where
2 00
D0 = 0 2 0 Eq. 4•219
0 01
1. p.334-352 in Zienkiewicz, O.C., and R.L. Taylor, 1989, “The finite element method: basic formulation and linear prob-
lems”, 4th ed., vol. 1, McGraw-Hill, London, UK.
= e iT ∫ B aT µ D 0 – --- m ⊗ m B b dΩ + ∫ B aT K ( m ⊗ m )B b dΩ e j
2
Eq. 4•220
Ω 3
Ω
k vol = e iT ∫ B aT K ( m ⊗ m )B b dΩe j
Ω
k dev = e iT ∫ B aT µ D 0 – --- m ⊗ m B b dΩ e j
2
Eq. 4•221
3
Ω
Therefore, the selective reduced integration can be applied to these two separate terms accordingly. The follow-
ing codes implemented Eq. 4•221 as
Lines 1-10 define 2 × 2 points integration, and lines 11-20 define 1-point integration. The deviatoric stiffness is
implemented in line 33, and the volumetric stiffness in line 40. This computation can be done with macros
“__TEST_PLAIN_STRAIN”,“__NEARLY_INCOMPRESSIBLE”,“__TEST_B_MATRIX_VOLUMETRIC_D
EVIATORIC_SPLIT”, and “__TEST_SELECTIVE_REDUCED_INTEGRATION” defined at compile time.
The result of tip deflection with standard integration scheme is “-0.000149628” (i.e., sever locking compared to
tip deflection of ElasticQ4 element with ν = 0.25 in TABLE 4•2.). With the selective reduced integration on the
volumetric term, under B-matrix formulation, the tip-deflection is “-0.00305825”.
For the coordinate-free tensorial formulation of Eq. 4•156,
k eiajb = λ ∫ N a, i N b, j dΩ + µ δ ij ∫ Na, k N b, k dΩ + ∫ N a, j Nb, i dΩ Eq. 4•223
Ω
Ω Ω
2
K = λ + --- µ Eq. 4•224
3
1. see p.129-130 in Fung, C.Y., 1965, “ Foundations of solid mechanics”, Prentice-Hall, Inc., Englewood Cliffs, N.J.
σ x νσ y σx
εx = ------ – --------- = ------ = 0.002 ⇒ u = 0.002x
E E E
νσ x σ y νσ x
εy = – --------- + ------ = – --------- = – 0.0006 ⇒ v = – 0.0006 y
E E E
2 ( 1 + ν )τ xy
γ xy = ---------------------------- = 0 Eq. 4•225
E
We observe that the imposing displacement field for the patch test is therefore linear. This gives a simple exact
solution the nodal displacements, nodal stresses, and nodal reactions shown in TABLE 4•4.
Node # u v σx σy τxy rx ry
0 0.0000 0.0000 2 0 0 2 0
1 0.0040 0.0000 2 0 0 -3 0
2 0.0040 -0.00180 2 0 0 -2 0
3 0.0000 -0.00120 2 0 0 3 0
4 0.0008 -0.00024 2 0 0 0 0
5 0.0028 -0.00036 2 0 0 0 0
6 0.0030 -0.00120 2 0 0 0 0
7 0.0006 -0.00096 2 0 0 0 0
TABLE 4•4. Nodal displacement, nodal stresses and nodal reactions of the element patch.
1. Taylor, R.L., O.C. Zienkiewicz, J.C. Simo, and A.H.C. Chan, 1986, “The patch test--a condition for assessing f.e.m. con-
vergence”, International Journal of Numerical Methods in Engineering, vol., 22, pp. 39-62, or, for more availability, an abbre-
viated representation as Chapter 11 in Zienkiewicz, O.C., and R.L. Taylor, 1989, “The finite element method: basic
formulation and linear problems”, McGraw-Hill, London., UK.
Consistency
E = 1x103, ν = 0.3 Stability
(Test A) (Test B) (Test C)
σx = 2, σy = τxy =0 2 fx =2
(2, 3)
(0, 2)
3 6
(1.5, 2.0)
(0.3, 1.6) 7
4 5 (1.4, 0.6)
(0.4,0.4)
0 1 u = 0.002x fx =3
(0, 0) (2, 0) v = -0.0006y
free d.o.f.s fixed d.o.f.s
Consistency Requirement demands the governing partial differential equation to be satisfied exactly. The
matrix form of the weak statement derived from the governing partial differential equation is
where Kij is the global stiffness matrix and fi is the global nodal force vector. We first specify all nodes with the
linear displacement calculated from u = 0.002x, and v = -0.0006y, where uj = [uj, vj]T is the solution vector, and
x = [x, y]T is the nodal coordinates. Since no loading, fi in Eq. 4•226, is specified for the internal nodes (# 4, 5,
6, 7), the “reaction” calculated according to “-Kijuj” should be identically zero, if the governing partial differen-
tial equation is to be satisfied. This is the “Test A” in Figure 4•49. The Test A is useful in checking the correct-
ness of program statements in implementing the stiffness matrix. The Program Listing 4•17 implements the test
suite for the Test A described in the above. The standard (full-) integration (2 × 2) for Test A is the default setting
of this program. The uniform reduced integration (1-point Gauss integration) can be performed on this program
by setting macro definition “__TEST_UNIFORM_REDUCED_INTEGRATION” at compile time. Both the
standard integration and uniform reduced integration produce the exact reaction, up to machine accuracy, as
listed in TABLE 4•4.
In the “Test B” in Figure 4•49, a second step for checking the consistency requirement, we specified only
nodes on the boundaries. Then, the unknown uj on internal nodes (# 4, 5, 6, 7) can be calculated according to
This step requires the matrix solver to “invert” the stiffness matrix Kij. The matrix solver is a fixture in “fe.lib”.
Assuming the matrix solver chosen is appropriate to solve the problem at hand, the “Test B” checks the accu-
racy of the stiffness matrix maintained in the process of matrix solution step. A problematic stiffness matrix, or
improper matrix solver, will lose accuracy significantly and may give out erroneous solution. The Test B can be
#include "include\fe.h"
static const double E_ = 1.0e3; static const double v_ = 0.3;
static const double lambda_=v_*E_/((1+v_)*(1-2*v_)); static const double mu_=E_/(2*(1+v_));
static const double lambda_bar = 2*lambda_*mu_/(lambda_+2*mu_);
Omega_h::Omega_h() { double v[2]; Node* node; int ena[4]; Omega_eh* elem;
v[0] = 0.0; v[1] = 0.0; node = new Node(0, 2, v); the_node_array.add(node);
v[0] = 2.0; v[1] = 0.0; node = new Node(1, 2, v); the_node_array.add(node);
define nodes
v[0] = 2.0; v[1] = 3.0; node = new Node(2, 2, v); the_node_array.add(node);
v[0] = 0.0; v[1] = 2.0; node = new Node(3, 2, v); the_node_array.add(node);
v[0] = 0.4; v[1] = 0.4; node = new Node(4, 2, v); the_node_array.add(node);
v[0] = 1.4; v[1] = 0.6; node = new Node(5, 2, v); the_node_array.add(node);
v[0] = 1.5; v[1] = 2.0; node = new Node(6, 2, v); the_node_array.add(node);
v[0] = 0.3; v[1] = 1.6; node = new Node(7, 2, v); the_node_array.add(node);
ena[0] = 0; ena[1] = 1; ena[2] = 5; ena[3] = 4;
elem = new Omega_eh(0, 0, 0, 4, ena); the_omega_eh_array.add(elem);
define elements
ena[0] = 5; ena[1] = 1; ena[2] = 2; ena[3] = 6;
elem = new Omega_eh(1, 0, 0, 4, ena); the_omega_eh_array.add(elem);
ena[0] = 7; ena[1] = 6; ena[2] = 2; ena[3] = 3;
elem = new Omega_eh(2, 0, 0, 4, ena); the_omega_eh_array.add(elem);
ena[0] = 0; ena[1] = 4; ena[2] = 7; ena[3] = 3;
elem = new Omega_eh(3, 0, 0, 4, ena); the_omega_eh_array.add(elem);
ena[0] = 4; ena[1] = 5; ena[2] = 6; ena[3] = 7;
elem = new Omega_eh(4, 0, 0, 4, ena); the_omega_eh_array.add(elem); }
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) { __initialization(df, omega_h);
for(int i = 0; i < 8; i++) define boundary conditions
for(int j = 0; j < 2; j++) the_gh_array[node_order(i)](j) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(1)][0] = 0.004; the_gh_array[node_order(2)][0] = 0.004;
the_gh_array[node_order(2)][1] = -0.0018; the_gh_array[node_order(3)][1] = -0.0012;
the_gh_array[node_order(4)][0] = 0.0008; the_gh_array[node_order(4)][1] = -0.00024;
the_gh_array[node_order(5)][0] = 0.0028; the_gh_array[node_order(5)][1] = -0.00036;
the_gh_array[node_order(6)][0] = 0.003; the_gh_array[node_order(6)][1] = -0.0012;
the_gh_array[node_order(7)][0] = 0.0006; the_gh_array[node_order(7)][1] = -0.00096; }
class ElasticQ4 : public Element_Formulation { public: define element “ElasticQ4”
ElasticQ4(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
ElasticQ4(int, Global_Discretization&); };
Element_Formulation* ElasticQ4::make(int en, Global_Discretization& gd) {
return new ElasticQ4(en,gd); }
static const double a_ = E_ / (1-pow(v_,2));
static const double Dv[3][3] = { {a_, a_*v_, 0.0}, {a_*v_, a_, 0.0}, {0.0, 0.0, a_*(1-v_)/2.0} };
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
ElasticQ4::ElasticQ4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
Quadrature qp(2, 4); H1 Z(2, (double*)0, qp), Zai, Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 4, 2, qp);
Zai &= Z[0]; Eta &= Z[1]; N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4;
N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4; H1 X = N*xl; J dv(d(X).det());
for(int b = 0; b < nen; b++) { B1 &= Nx[b][0]; B2 &= Nx[b][1];
DB[0][0] = Dv[0][0]*B1; DB[0][1] = Dv[0][1]*B2; DB[1][0] = Dv[0][1]*B1;
DB[1][1] = Dv[1][1]*B2; DB[2][0] = Dv[2][2]*B2; DB[2][1] = Dv[2][2]*B1;
for(int a = 0; a <= b; a++) { B1 &= Nx[a][0]; B2 &= Nx[a][1];
K[2*a ][2*b] = B1*DB[0][0] + B2*DB[2][0];
K[2*a ][2*b+1] = B1*DB[0][1] + B2*DB[2][1];
K[2*a+1][2*b] = B2*DB[1][0] + B1*DB[2][0];
K[2*a+1][2*b+1] = B2*DB[1][1] + B1*DB[2][1]; } }
for(int b = 0; b < nen; b++) for(int a = b+1; a < nen; a++) {
K[2*a ][2*b] = K[2*b ][2*a ]; K[2*a ][2*b+1] = K[2*b+1][2*a ];
K[2*a+1][2*b] = K[2*b ][2*a+1]; K[2*a+1][2*b+1] = K[2*b+1][2*a+1];
}
Listing 4•17 Patch test A(project workspace file “fe.dsw”, project “patch_test” with Macro definition
“__PATCH_TEST_A” set at compile time).
(d) y-hourglass mode (for a square) (e) x-hourglass mode (for a square)
Figure 4•50 Deformation of the element patch magnifies 50 times in (a) solution with
standard 2 × 2 integration points, (b) solution with uniform reduced (1 × 1) integration, (c)
solution with uniform reduced integration using pseudo (Moore-Penrose) inverse for matrix
solver; i.e., with singular value decomposition, (d) and (e) are compared to two zero-energy
hourglass modes of a square bilinear element (the eigenvectors designated as the x-
hourglass and y- hourglass modes), associated with the signular values of the uniform
reduced (1 × 1) integration stiffness matrix.
1. see p.242 in Hughes, T. J.R., 1987, “The finite element method: linear static and dynamic finite element analysis”, Pren-
tice-Hall Inc., Englewood Cliffs, New Jersey.
h h
3 4 5
h
0 1 2 r
r=1
∂w
-------
∂z
εz
∂u
εr ------
∂r
ε = = Eq. 4•229
εθ u
---
γ rz r
∂u ∂w
------ + -------
∂z ∂r
Therefore, with εeh = B a û ea , where the B-matrix for the axisymmetrical case becomes
1. Taylor, R.L., O.C. Zienkiewicz, J.C. Simo, and A.H.C. Chan, 1986, “The patch test--a condition for assessing f.e.m. con-
vergence”, International Journal of Numerical Methods in Engineering, vol., 22, pp. 39-62.
2. Chapter 12 in Timoshenko, S.P., and J.N. Goodier, 1970, “ Theory of elasticity”, McGraw-Hill, Inc., London, U.K.
∂N a
0 ---------
∂z
∂N a
--------- 0
∂r û a
Ba = , and ûea = e Eq. 4•230
N v̂ ea
-----a- 0
r
∂N a ∂N a
--------- ---------
∂z ∂r
ν ν
1 ------------ ------------ 0
1–ν 1–ν
ν ν
------------ 1 ------------ 0
E(1 – ν) 1 – ν 1–ν
D = -------------------------------------- Eq. 4•231
( 1 + ν ) ( 1 – 2ν ) ν ν
------------ ------------ 1 0
1–ν 1–ν
1 – 2ν
0 0 0 --------------------
2( 1 – ν )
The infinitesimal volume is taken over the whole ring of material as dV = 2πr dr dz. For the selective reduced
integration, the volumetric and deviatoric split of the stiffness matrix as in Eq. 4•221 is still valid
with two simple modifications for axisymmetrical consideration that m = [1, 1, 1, 0]T, and
2 0 0 0
D0 = 0 2 0 0
Eq. 4•234
0 0 2 0
0 0 0 1
For the current problem, the material constants are given as E = 1, and υ = 0, for simplicity. This gives
We notice that the implementation of the B-matrix for the axisymmetrical problem is implemented according to
Eq. 4•230 as
Lines 5-8 use matrix concatenation operation to capture the semantics of B-matrix directly.
#include "include\fe.h"
static const double E_ = 1.0;
static const double v_ = 0.0;
static const double lambda_=v_*E_/((1+v_)*(1-2*v_));
static const double mu_ = E_/(2*(1+v_));
static const double K_ = lambda_ + 2.0/3.0 * mu_;
static const double h_=0.8;
static const double r_=1.0;
static const double PI_=3.14159265359;
Omega_h::Omega_h() {
double v[2];
Node* node;
int ena[4];
Omega_eh* elem;
v[0] = r_-h_; v[1] = 0.0;
node = new Node(0, 2, v);
the_node_array.add(node);
v[0] = r_;
node = new Node(1, 2, v);
the_node_array.add(node);
v[0] = r_+h_;
node = new Node(2, 2, v);
the_node_array.add(node);
v[0] = r_-h_; v[1] = h_;
node = new Node(3, 2, v);
the_node_array.add(node);
v[0] = r_;
node = new Node(4, 2, v);
the_node_array.add(node);
v[0] = r_+h_;
node = new Node(5, 2, v); the_node_array.add(node);
ena[0] = 0; ena[1] = 1; ena[2] = 4; ena[3] = 3;
elem = new Omega_eh(0, 0, 0, 4, ena);
the_omega_eh_array.add(elem);
ena[0] = 1; ena[1] = 2; ena[2] = 5; ena[3] = 4;
elem = new Omega_eh(1, 0, 0, 4, ena);
the_omega_eh_array.add(elem); }
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h);
the_gh_array[node_order(1)](1) = gh_on_Gamma_h::Dirichlet;
double sigma_r, r, f_r;
sigma_r = 2.0; r = 1.0-h_;
f_r = -2.0*PI_*r*h_*sigma_r;
the_gh_array[node_order(0)][0] = f_r / 2.0;
the_gh_array[node_order(3)][0] = f_r / 2.0;
r = 1.0+h_;
f_r = 2.0*PI_*r*h_*sigma_r;
the_gh_array[node_order(2)][0] = f_r / 2.0;
the_gh_array[node_order(5)][0] = f_r / 2.0;
}
class ElasticAxisymmetricQ4 : public Element_Formulation {
public:
ElasticAxisymmetricQ4(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
ElasticAxisymmetricQ4(int, Global_Discretization&);
};
Element_Formulation* ElasticAxisymmetricQ4::make(int en, Global_Discretization& gd) {
return new ElasticAxisymmetricQ4(en,gd);
}
Listing 4•18 Axisymmetrical patch test (project workspace file “fe.dsw”, project
“axisymmetrical_patch_test” with Macro definition
Shape Sensibility: Consider two quadratic elements. Either eight-nodes or nine-nodes elements as shown in Fig-
ure 4•52. The common edge of the two elements is slanted with the distortion, away from axes of Cartesian
coordinates, denoted as “d”, and shown in Figure 4•52.
d=0
E = 103, ν = 0.3
15
2 d=1
-15
d
10
d=2
d
Figure 4•52 Beam subject to bending moement on the left. Three amount of element
distortion away from rectangular shape (d = 0).
There is no new program implementation needed for the higher-order patch test. The project
“higher_order_patch_test” implemented program for the present test. The eight-nodes and nine-nodes elements
are activated by setting macro definition “__TEST_Q8” and “__TEST_Q9”, respectively. The distortion factor
is a static constant “d_” in the very beginning of the program. The uniform reduced integration can be achieved
by setting all qaudrature point to 2 × 2 in the program. The tip deflection on the middle point of the left edge is
listed in TABLE 4•6.
Convergence of bilinear 4-node element: We show the convergence of bilinear 4-node element at (1) Poisson ratio
ν = 0.3 in plane stress and (2) ν = 0.4999 in plane strain (with the same boundary value problem in Figure 4•52).
The options of (a) the selective reduce integration on the shear term of the deviatoric stiffness and (b) the volu-
metric stiffness are also tested. The same problem is divided with successively finer meshes, and is shown in Fig-
ure 4•53. The test suite is implemented in project “higher_order_q4” in project workspace file “fe.dsw”. For total
element number greater than 8, the macro definitions “__TEST_Q4_32”, “__TEST_Q4_128”, and
“__TEST_Q4_512”, with the last numbers indicate the total element number, can be set at compile time. For the
selective reduced integration on the offending shear terms and dilatational term in incompressible materials, the
corresponding macro definitions are “__SHEAR_SELECTIVE_REDUCED_INTEGRATION” and
“__INCOMPRESSIBLE_ SELECTIVE_REDUCED_INTEGRATION”.
The results with various combinations of the options are shown in TABLE 4•7. For Poisson ratio ν = 0.3, in
plane stress, the convergence is clear with increasing number of element used in the computation. The successive
results agree on more digits after the decimal points. This convergence is guaranteed by the patch test for the 4-
nodes bi-linear element, since it pass the consistency and stability parts of the patch test. Both the full integration
and selective reduced integration on the offending shear treatment converge to exact solution of 0.75. For ν =
0.4999, the nearly incompressible condition, in plane strain case, the solution shows significant locking without
signs of convergence, when applied with the full integration. The solution and its convergence are obtainable
with the selective reduced integration schemes as shown in the last two columns, which both converge to value
of ~0.56 comparing to “0.5625” in mixed u-p formulation (ν = 0.5 in Chapter 5).
1. p. 167-169 in Zienkiewicz, O.C., and R.L. Taylor, 1989, “The finite element method: basic formulation and linear prob-
lems”, McGraw-Hill, London., UK.
2. p. 164-165 in Bathe, K.-J. and W.L. Wilson, 1976, “ Numerical method in finite element analysis”, Prentice-Hall, Inc.,
Englewood Cliffs, New Jersey.
8 elements
32 elements
128 elements
512 elements
Du
ρ -------- = div
Dt
σ+f Eq. 4•236
where divergence of interal stresses, div σ, equals the external surface force, and f is the body force. The Du/Dt
in the left-hand-side is the fluid particle in Lagragian (material) description, in which u(x, t) can be differentiated
with respect to time “t” (by first applying the Lebniz rule, i.e., d(xy) = x dy + y dx, and then the chain rule, d f(x)
/ dt = (df / dx) (dx / dt), on the second term of the Lebniz rule)
Du ( x, t ) ∂u ∂u ∂x ∂u
-------------------- = ------ + ------ ------ = ------ + u • grad u Eq. 4•237
Dt ∂t ∂x ∂t ∂t
where we have applied the definitions of the velocity, u ≡ ∂x/∂t, and the velocity gradient, grad u ≡ ∂u/∂x. The
stress in the first term of the right-hand-side of Eq. 4•236 can be expressed as in Eq. 4•146 that
σ = –p I+τ
where p is the pressure, I is the unit tensor, and τ is the viscous stress. The constitutive equations is
where µ is the fluid viscosity, and λ' is the second viscosity (this term gives the deviatoric stress caused by the
volumetirc deformation which is a process attributed to molecular relaxation). For monatomic gas λ' = -2µ/3,
and it can be proved as the lower bound for λ' thermodynamically. In most applications, λ' div u , is nearly
completely negligible compared to the pressure, “p”.
A popular treatment for the incompressible condition is to use penalty method where the pressure variable is
eliminated by taking
Now λ and µ are equivalent to the Lamé constants in elasticity. As discussed earlier (see page 409), near the
incompressible condition K ≈ λ >> µ. In the penalty method in the stokes problem, the penalty parameter, λ, is
usually taken as
to approximate the nearly incompressible condition.1 Substituting Eq. 4•237 and viscous stress of Eq. 4•238 into
Eq. 4•236, we have the Navier-Stokes equation
1. p.520 in Zienkiewicz and R.L. Taylor, 1991, “The finite element method”, 4th ed., vol. 2. McGraw-Hill, Inc., UK.
We have dropped out the second viscosity λ' and use the identity that “div(p I) = grad p”. For steady incom-
pressible viscous fluid, the Navier-Stokes equation simplifies to
From Eq. 4•242, the Reynolds number (denoted as Re) is the dynamic similarity of the inertia force
“ ρu • grad u “ to the viscous force “div(2µ def u)” as1
ρu • grad u ρUL
------------------------------------------ ≈ ----------- ≡ Re Eq. 4•243
div ( 2µ def u ) µ
At very low Reynolds number (Re << 1) the inertia force is negligible compared to the viscous force. The Eq.
4•242 can be simplified to
Therefore, the resultant equation is completely identical to Eq. 4•140 with the constitutive equation of Eq. 4•146
and Eq. 4•147 for elasticity. The physical interpretation is different in that instead of regarding u as the displace-
ment, it is the velocity in the stokes flow. µ now plays the role of fluid viscosity instead of the shear modulus G
in elasticity. λ is now the penalty parameter we take λ = 108 µ, and certainly with the selective reduced integra-
tion for the volumetric term, in the computation. The finite element formulation in the last section for plane elas-
ticity can be applied to the stokes flow problem without modification. Considering the B-matrix formulation for
plane elasticity
Since at the incompressible limit, λ ≈ K , and λ = 108 µ for the penalty method, Eq. 4•245 becomes2
λλ0 2µ 0 0
k dev ≅ e iT ∫ B aT D µ B b dΩ e j , and k vol ≅ e iT ∫ BaT D λ Bb dΩe j where D = Eq. 4•246
λ λ λ 0 , D µ = 0 2µ 0
Ω Ω
0 0 0 0 0 µ
1. p. 97 in Tritton, D.J., 1988, “ Physical fluid dynamics”, 2nd ed., Oxford University Press, Oxford, UK.
2. see Hughes, T.J.R., W.K. Liu, and A. Brooks, 1979, “Review of finite element analysis of incompressible viscous flows
by the penalty function formulation”, Journal, of Computational Physics, vol. 30, no. 1, p. 1-60.
G Uy
u ( y ) = ------ y ( d – y ) + ------- Eq. 4•247
2µ d
This solution can be derived from Eq. 4•244 from the superposition of two solutions of the viscous flow induced
by the pressure gradient and by the bounding plates separately. That is the first term corresponding to the Poi-
seuille flow caused by the applied horizontal pressure gradient, the second term corresponding to the Couette
flow induced by the relative motion of the two bounding plates. In these test cases, the Couette flow provides an
assumed linear solution, and the Poiseuille flow provides an assumed higher-order (quadratic) solution.
Program Listing 4•19, in the project “plane_couette_poiseuille_flow” in project workspace file “fe.dsw”, is
implemented for these tests. To emphasize its relation to plane elasticity, we use “elasticq9.cpp” as a separate
compilation unit, as a dependent source file for this project. The “elasticq9.cpp” is the implementation very close
to of Lagrangian 9-node element for plane elasticity.
The plane Couette flow can be activated by setting macro definition “__TEST_PLANE_COUETTE_FLOW”
and the plane Poiseuille flow can be activated by setting macro definition “__TEST_PLANE_POISEUILLE_
FLOW”. The default is a combined flow with both pressure gradient applied on the entrance and relative motion
of bounding plates. The results of the computation are shown in Figure 4•55. The finite element solutions are
shown in dashed curves with arrows to indicate the velocity profiles in the middle of the channel to avoid the
entrance and exit effects. The exact solution are shown in solid curves. We notice that the solution for the plane
Poiseuille flow, quadratic in nature, is less accurate compared to the solution for the plane Couette flow, which is
linear.
U=1
L = 10
1. p. 182 in Batchelor, G.K., 1967, “An introduction to fluid dynamics”, Cambridge University Press, UK.
1 1
0.8 0.8
0.6 0.6
0.4 0.4
0.2 0.2
0.8
0.6
0.4
0.2
k dev ≅ e iT ∫ B aT D µ B b dΩ e j ,
wx &= w_x[0][0]; wy &= w_x[0][1]; b &= (~wx|| C0(0.0)) & (C0(0.0) || ~wy ) & (~wy || ~wx);
C0 stiff_vol = ((~b) * (D_lambda * b)) | dv;
double d_mu[3][3] = { {2*mu_, 0.0, 0.0}, {0.0, 2*mu_, 0.0}, {0.0, 0.0, mu_} }; Ω
C0 D_mu = MATRIX("int, int, const double*", 3, 3, d_mu[0]);
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), Wx, Wy, B;
k vol ≅ e iT ∫ B aT D λ Bb dΩe j
Wx &=W_x[0][0]; Wy &=W_x[0][1]; B &= (~Wx|| C0(0.0)) & (C0(0.0) || ~Wy) & (~Wy|| ~Wx );
C0 stiff_dev = ((~B) * (D_mu * B)) | dV; Ω
#else standard λ−µ formulation
C0 e = BASIS("int", ndf), E = BASIS("int", nen), U = (e%e)*(E%E);
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, nx), wx, wy;
wx &= w_x[0][0]; wy &= w_x[0][1];
C0 stiff_vol = lambda_* (
+( wx*~wx*U[0][0]+wx*~wy*U[0][1] +wy*~wx*U[1][0]+wy*~wy*U[1][1] ) | dv);
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), Wx, Wy;
Wx &= W_x[0][0]; Wy &= W_x[0][1];
C0 stiff_dev = mu_* (
+( ((2*Wx*~Wx)+(Wy*~Wy))*((e[0]%e[0])*(E%E))+(Wy*~Wx) *((e[0]%e[1])*(E%E))
+(Wx*~Wy) *((e[1]%e[0])*(E%E))+((2*Wy*~Wy)+(Wx*~Wx))*((e[1]%e[1])*(E%E)) )
| dV);
#endif
stiff &= stiff_vol + stiff_dev;
}
x
(0, 0) (1,0)
(a) (b)
Figure 4•56(a) Flow in square cavity with sixteen 9-nodes Lagrangian elements. (b) velocity
vectors.
1. p. 462-465 in J.N. Reddy, 1986, “Applied functional analysis and variational methods in engineering”, McGraw-Hill, Inc.,
New York.
2. such as corner node treatments described in p.231 in Hughes, T.J.R., “The finite element method: linear static and dynamic
finite element analysis”, Prentice-Hall, Inc., Englewood Cliffs, New Jersey.
#include "include\fe.h"
static const int row_node_no = 9; static const int col_node_no = 9;
static const int row_element_no = (row_node_no-1)/2;
static const int col_element_no = (col_node_no-1)/2;
static const double h_e_ = 1.0/((double)row_element_no*2);
static const double v_e_ = 1.0/((double)col_element_no*2);
static const double mu_ = 1.0; static const double lambda_ = 1.e8 * mu_;
EP::element_pattern EP::ep = EP::LAGRANGIAN_9_NODES;
Omega_h::Omega_h() {
double x[4][2] = {{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}};
int control_node_flag[4] = {1, 1, 1, 1};
block(this, row_node_no, col_node_no, 4, control_node_flag, x[0]);
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h);
for(int i = 0; i < col_node_no; i++) { right; u = v = 0
the_gh_array[node_order((i+1)*row_node_no-1)](0) =
the_gh_array[node_order((i+1)*row_node_no-1)](1) = gh_on_Gamma_h::Dirichlet;
}
for(int i = 0; i < col_node_no; i++) { left; u = v = 0
the_gh_array[node_order(i*row_node_no)](0) =
the_gh_array[node_order(i*row_node_no)](1) = gh_on_Gamma_h::Dirichlet;
}
for(int i = 1; i < row_node_no-1; i++) { bottom; u = v = 0
the_gh_array[node_order(i)](0) =
the_gh_array[node_order(i)](1) = gh_on_Gamma_h::Dirichlet;
}
for(int i = 1; i < row_node_no-1; i++) {
int nn = (col_node_no-1)*row_node_no+i; top, forced B.C.; u = 4(1-x)x, v = 0
double x = ((double)i)*h_e_, u = 4.0 * (1.0-x) * x;
the_gh_array[node_order(nn)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(nn)][0] = u;
the_gh_array[node_order(nn)](1) = gh_on_Gamma_h::Dirichlet;
}
}
class ElasticQ9 : public Element_Formulation { declare “ElasticQ9” class
public:
ElasticQ9(Element_Type_Register);
Element_Formulation *make(int, Global_Discretization&);
ElasticQ9(int, Global_Discretization&);
};
Element_Formulation* Element_Formulation::type_list = 0;
register “ElasticQ9” as element # 0
Element_Type_Register element_type_register_instance;
static ElasticQ9 stokesq9_instance(element_type_register_instance);
int main() {
int ndf = 2; Omega_h oh;
gh_on_Gamma_h gh(ndf, oh);
U_h uh(ndf, oh); solution phase
Global_Discretization gd(oh, gh, uh);
Matrix_Representation mr(gd);
mr.assembly();
C0 u = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h();
cout << gd.u_h() << endl;
return 0;
}
Listing 4•20 Driven cavity flow (in project: “square_cavity_flow” in project workspace file “fe.dsw”.).
∂
------ 0
εx ∂x
ε = εy = – z 0 ----- ∂ θ x = – zL θ Eq. 4•249
-
∂y θ y
γ xy ∂ ∂
------ ------
∂y ∂x
Sx
w = w0
Mx
midsurface Mxy
fiber Myx
Sy My
θα
uα = uα0-θαz
Figure 4•57 (a) the displacements of plate under deformation, (b) the shear forces (Sx, Sy),
the normal momenets (Mx, My), and the twisting moments (Mxy, Myx) of a plate.
1. p.8 in Zienkiewicz and R.L. Taylor, 1991, “The finite element method”, 4th ed., vol. 2. McGraw-Hill, Inc., UK.
∂w
γx θx -------
∂x
γ = = – + = – θ + ∇w Eq. 4•250
γy θy ∂w
-------
∂y
From the Figure 4•57b, the normal moments (Mx, My) and the twisting moment (Mxy) are
t
---
Mx 2 σx
M = My = –∫ σ y z dz = D L θ Eq. 4•251
M xy t
– --- τ xy
2
1 ν 0
Et 3 ν 1 0
D = ------------------------- Eq. 4•252
12 ( 1 – ν 2 ) 1–ν
0 0 ------------
2
Sx
S = = βGt ( – θ + ∇w ) ≡ α ( – θ + ∇w ) Eq. 4•253
Sy
5
where α = βGt, and the correction factor β = --- is for rectangular homogeneous section with parabolic shear
6
stress distribution.
Parallel to the equilibrium equations, Eq. 4•26 and Eq. 4•27 for 1-D beam bending problem, we have in plate
bending problem
∂ ∂
------ 0 ------ M x
∂x ∂y Sx 0 ∂ ∂ Sx + qx = 0
My + = , and ------ ------ Eq. 4•255
∂ ∂ Sy 0 ∂x ∂y S y qy 0
0 ------ ------ M
∂y ∂x xy
θ = ∇w Eq. 4•256
Substituting first part of Eq. 4•254 into the second part of it, we get
–∇ T L T M + q = 0 Eq. 4•257
Then, use Eq. 4•251 to substitute M in Eq. 4•257, and substitute θ, with thin plate assumption, θ = ∇w in Eq.
4•256, we get
From the definition of operators L and ∇, we have the combined operator “L∇” as
∂ ∂2
------ 0 --------
∂x ∂ ∂x 2
------
∂ ∂x ∂2
L∇ = 0 ----- - = -------- Eq. 4•259
∂y ∂ ∂y 2
------
∂ ∂ ∂y ∂2
------ ------
∂y ∂x 2 -------------
∂x∂y
For constant D, the Eq. 4•258 becomes the well-known classical biharmonic equation1
The homogeneous solution for a simply supported rectangular plate with lengths of “a” and “b” has the simple
form of
1. e.g., Airy’s stress function satisfies the biharmonic equation as described in p.32, and p.538 in Timoshenko, S.P., and J.N.
Goodier, 1970, “ Theory of elasticity”, 3rd ed., McGraw-Hill Book Company.
h a
M e = D B a ŵ e Eq. 4•263
a
where ŵ e is the nodal deflection vector. The element stiffness matrix has no difference from Eq. 4•173; i.e.,
k epq = k eiajb = e iT ∫ B aT D B b dΩe j , with p = ndf (a-1) + i, and q = ndf (b-1)+j Eq. 4•264
Ω
wa
û ea ≡ θ̂ xa Eq. 4•265
θ̂ ya
where
∂w ∂w
θ̂ xa = – ------- , and θ̂ ya = ------- Eq. 4•266
∂y a ∂x a
The nonconforming element defines a 12-terms polynomial for the deflection “w” as
w = α0 + α1 x + α2 y + α3 x2 + α4 xy + α5 y2 +
α6 x3 + α7 x2y + α8 xy2 + α9 y3 + α10 x3y + α11 xy3
≡ Pα Eq. 4•267
where
2
P = 1 x y x 2 xy y x 3 x 2 y xy 2 y 3 x 3 y xy 3 Eq. 4•268
Notice that the polynomial is not complete up to the third-order. For each of four nodes on the corner of the rect-
angle (a = 0, 1, 2, 3), we have twelve equations
wa α 0 + α 1 x a + α 2 y a + α 3 x a2 + α 4 x a y a + α 5 y a2 + α 6 x a3 + α 7 x a2 y a + α 8 x a y a2 + α 9 y a3 + α 10 x a3 y a + α 11 x a y a3
θ̂ xa = – α 2 – α 4 x a – 2α 5 y a – α 7 x a2 – 2α 8 x a y a – 3α 9 y a2 – α 10 x a3 – 3α 11 x a y a2
θ̂ ya α 1 + 2α 3 x a + α 4 y a + 3α 6 x a2 + 2α 7 x a y a + α 8 y a2 + 3α 10 x a2 y a + α 11 y a3
≡ Ca α Eq. 4•269
1 x a y a x a2 x a y a y a2 x a3 x a2 y a x a y a2 y a3 x a3 y a x a y a3
Ca ≡ 0 0 –1 0 – x a – 2y a 0 – x a2 – 2x a y a – 3y a2 – x a3 – 3x a y a2 Eq. 4•270
0 1 0 2x a y a 0 3x a2 2x a y a y a2 0 3x a2 y a y a3
for a = 0, 1, 2, 3. Therefore, C is a 12 × 12 matrix. The vector α can be obtained by inverting Eq. 4•269 as
α = C – 1 û ea Eq. 4•271
The Program Listing 4•21 implements the generic procedure in the above to derive the nonconforming shape
function (Eq. 4•273) for the thin-plate bending rectangular element. Eq. 4•262 and Eq. 4•264 are then taken to
define the B-matrix and the stiffness matrix, respectively. The plate is clamped at four sides and with uniform
unit loading. Only a quarter (upper-right) of the plate is modeled due to the symmetry of the geometry and the
boundary conditions. 4 × 4 (= 16) elements are used in the computation. At the right and the top edges of the
model the boundary conditions are w = ∂ w/ ∂ x = ∂ w/ ∂ y = 0 (clamped). At the bottom and the left edges are
taken as ∂ w/ ∂ y =0, and ∂ w/ ∂ x =0, respectively (see Figure 4•58a). The solution of the vertical deflection is
shown in Figure 4•58b.
The maximum deflection is at the center of the plate, or at the lower-left corner of the finite element model.
The exact solution is 226800.1 The results are shown in TABLE 4•8., which shows the convergence toward the
exact solution when the mesh size is refined.
1. The exact solution is computed from formula provided in p. 31 in Zienkiewicz, O.C. and R.L. Taylor, 1991, “The finite
element method”, 4th ed., vol. 2. McGraw-Hill, Inc., UK, and reference therein.
#include "include\fe.h"
static row_node_no = 5;
EP::element_pattern EP::ep = EP::QUADRILATERALS_4_NODES;
Omega_h::Omega_h() {
double coord[4][2] = {{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}};
int control_node_flag[4] = {TRUE, TRUE, TRUE, TRUE};
block(this, row_node_no, row_node_no, 4, control_node_flag, coord[0]);
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h);
for(int i = 0; i < row_node_no-1; i++) bottom B.C. ∂ w/ ∂ y =0
the_gh_array[node_order(i)](1) = gh_on_Gamma_h::Dirichlet;
for(int i = 0; i < row_node_no-1; i++)
the_gh_array[node_order(i*row_node_no)](2) = gh_on_Gamma_h::Dirichlet; left B.C. ∂ w/ ∂ x =0
for(int i = 1; i <= row_node_no; i++) {
the_gh_array[node_order(i*row_node_no-1)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(i*row_node_no-1)](1) = gh_on_Gamma_h::Dirichlet;
top B.C. w = ∂ w/ ∂ x = ∂ w/ ∂ y =0
the_gh_array[node_order(i*row_node_no-1)](2) = gh_on_Gamma_h::Dirichlet; }
for(int i = 0; i < row_node_no-1; i++) { right B.C. w = ∂ w/ ∂ x = ∂ w/ ∂ y =0
the_gh_array[node_order(row_node_no*(row_node_no-1)+i)](0) =
gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(row_node_no*(row_node_no-1)+i)](1) =
gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(row_node_no*(row_node_no-1)+i)](2) =
gh_on_Gamma_h::Dirichlet;
}
}
class PlateR4 : public Element_Formulation {
public:
PlateR4(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
PlateR4(int, Global_Discretization&);
};
Element_Formulation* PlateR4::make(int en, Global_Discretization& gd) {
return new PlateR4(en,gd);
}
static const double E_ = 1.0; static const double v_ = 0.25; static const double t_ = 0.01; 1 ν 0
static const double D_ = E_ * pow(t_,3) / (12.0*(1-pow(v_,2)));
Et 3 ν 1 0
static const double Dv[3][3] = { {D_, D_*v_, 0.0 }, D = -------------------------
{D_*v_, D_, 0.0 }, 12 ( 1 – ν 2 ) 1–ν
{0.0, 0.0, D_*(1-v_)/2.0} }; 0 0 ------------
2
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
PlateR4::PlateR4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
int ndf = 3;
Quadrature qp(2, 16);
H0 dx_inv;
H2 X;
{
H2 z(2, (double*)0, qp),
n = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE( coordinate transformation rule
"int, int, Quadrature", 4/*nen*/, 2/*nsd*/, qp), zai, eta;
zai &= z[0]; eta &= z[1];
n[0] = (1-zai)*(1-eta)/4; n[1] = (1+zai)*(1-eta)/4;
n[2] = (1+zai)*(1+eta)/4; n[3] = (1-zai)*(1+eta)/4;
X &= n*xl;
}
dx_inv &= d(X).inverse();
J dv(d(X).det());
Listing 4•21 Plate bending using nonconformming rectangular element (project workspace file “fe.dsw”,
project “rectangular_plate_bending” with Macro definition
“__GENERIC_NONCONFORMING_SHAPE_FUNCTION” set at compile time).
w = ∂w/∂x = ∂w/∂y =0
∂w/∂x = 0
200000
0
5
∂w/∂y =0 1500000
100000
00
50000
00 4
0
1.0 1 3
2
3 2
4
51
1.0
(b)
(a)
Figure 4•58 Clamped boundary conditions and nodal deflections for rectangular plate bending
elements (4 × 4 mesh are shown) using non-conformming shape function.
Alternatively, we can substitute the explicit shape functions1 in Eq. 4•262 with
( ξξ a + 1 ) ( ηη a + 1 ) ( 2 + ξξ a + ηη a – ξ 2 – η 2 )
1
N a ≡ --- – b η a ( ξξa + 1 ) ( ηη a + 1 ) 2 ( ηη a – 1 ) Eq. 4•274
8
aξa ( ξξa + 1 ) 2 ( ξξa – 1 ) ( ηη a + 1 )
where “2a” and “2b” are the lengths of a rectangular element, and the nodal normalized coordinates are [ξa, ηa]
= {(-1, -1), (1, -1), (1, 1), (-1, 1)}. Implementation of Eq. 4•274, to be substituting in Eq. 4•262, is straight for-
ward as
1. see p. 17 in Zienkiewicz, O.C. and R.L. Taylor, 1991, “The finite element method”, 4th ed., vol. 2. McGraw-Hill, Inc.,
UK, and reference therein.
On the other hand, the Eq. 4•272 is quite generic especially when no one is deriving an explicit formula like Eq.
4•274 for us. The computation is done on the same project (“rectangular_plate_bending” in project workspace
file “fe.dsw”) with macro definition “__EXPLICIT_NONCONFORMING_SHAPE_FUNCTION” set at com-
pile time. The solutions is certainly identical to the one with generic procedure for computing the shape function.
wa
∂w
-------
∂y a
û ea ≡ Eq. 4•275
∂w
-------
∂x a
∂2 w
------------
-
∂x∂y a
with four nodes at each corner of the rectangle we have totally 16 degree of freedoms. Therefore, a complete
third-order polynomial can be used to represent the deflection w, with P defined as
2 2 2
P = 1 x y x 2 xy y x 3 x 2 y xy 2 y 3 x 3 y x 2 y xy 3 x 3 y x 2 y 3 x 3 y 3 Eq. 4•276
1 x a y a x a2 x a y a y a2 x a3 x a2 y a x a y a2 y a3 x a3 y a x a2 y a2 x a y a3 x a3 y 2 a x a2 y 3 a x a3 y 3 a
0 0 1 0 x a 2y a 0 x a2 2x a y a 3y a2 x a3 2x a2 y a 3x a y a2 2x a3 y a 3x a2 y 2 a 3x a3 y 2 a
Ca ≡ Eq. 4•277
0 1 0 2x a y a 0 3x a2 2x a y a y a2 0 3x a2 y a 2x a y a2 y a3 3x a2 y 2 a 2x a y 3 a 3x a2 y 3 a
0 0 0 0 1 0 0 2x a 2y a 0 3x a2 4x a y a 3y a2 6x a2 y a 6x a y a2 9x a2 y 2 a
Eq. 4•276 and the inverse of Eq. 4•277 can be substituted in Eq. 4•272 to define the B-matrix. The explicit shape
functions for the conforming rectangular element, , is defined as
( ξ + ξ a ) 2 ( ξξ a – 2 ) ( η + η a ) 2 ( ηη a – 2 )
1 – a ξ a ( ξ + ξ a ) 2 ( ξξ a – 1 ) ( η + η a ) 2 ( ηη a – 2 )
N a ≡ ------ Eq. 4•278
16 – b ( ξ + ξ ) 2 ( ξξ – 2 )η ( η + η ) 2 ( ηη – 1 )
a a a a a
abξ a ( ξ + ξ a ) 2 ( ξξ a – 1 )η a ( η + η a ) 2 ( ηη a – 1 )
where “2a” and “2b” are the lengths of the rectangular element, and the subscript a = 0, 1, 2, 3 are the nodal
numbers (developed by Bogner et al.1,2). The same project “rectangular_plate_bending” can be used with macro
definition “__EXPLICIT_CONFORMING_SHAPE_FUNCTION” set at compile time for using Eq. 4•278, or
no macro definition set at compile time for its generic counterpart via Eq. 4•277. The results of center deflection
of the conforming rectangular plate are shown in TABLE 4•9. .
2 2 2
P = L0 L 1 L2 L0 L1 L 1 L 2 L2 L0 L 0 L 1 L1 L 2 L 2 L0 Eq. 4•279
Three third order terms are chosen in addition to the first six complete second order terms. The explicit shape
function for the first node is (with cyclic permutation of 0, 1, 2 for other two nodes)
1. see p. 49 in Zienkiewicz and R.L. Taylor, 1991, “The finite element method”, 4th ed., vol. 2. McGraw-Hill, Inc., UK, and
reference therein.
2. see also p. 419, Table 9.1 for the “Hermite cubic element” in Reddy, J.N., 1993, “An introduction to the finite element
method”, 2nd ed., McGraw-Hill, Inc., New York.
3. see p. 244 in Zienkiewicz, O.C., 1977, “The finite element method”, 3rd ed., McGraw-Hill, Inc., UK.
L 0 + L 02 L 1 + L 02 L 2 – L 0 L 12 – L 0 L 22
2 1
- L L L – b 1 L 2 L 02 + --- L 0 L 1 L 2
1
N 0 ≡ b 2 L 0 L 1 + --
2 0 1 2 2 Eq. 4•280
where b0 = y1- y2, and c0 = x2-x1. The explicit shape function for the triangular element can be implemented as
#include "include\fe.h"
static row_node_no = 5;
EP::element_pattern EP::ep = EP::SLASH_TRIANGLES;
Omega_h::Omega_h() {
double coord[4][2] = {{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}};
int control_node_flag[4] = {TRUE, TRUE, TRUE, TRUE};
block(this, row_node_no, row_node_no, 4, control_node_flag, coord[0]);
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h); bottom B.C. - ∂ w/ ∂ y =0
for(int i = 0; i < row_node_no-1; i++)
the_gh_array[node_order(i)](1) = gh_on_Gamma_h::Dirichlet;
for(int i = 0; i < row_node_no-1; i++)
left B.C. ∂ w/ ∂ x =0
the_gh_array[node_order(i*row_node_no)](2) = gh_on_Gamma_h::Dirichlet;
for(int i = 1; i <= row_node_no; i++) { right B.C. w = ∂ w/ ∂ x = ∂ w/ ∂ y =0
the_gh_array[node_order(i*row_node_no-1)](0) =
the_gh_array[node_order(i*row_node_no-1)](1) =
the_gh_array[node_order(i*row_node_no-1)](2) = gh_on_Gamma_h::Dirichlet;
} top B.C. w = ∂ w/ ∂ x = ∂ w/ ∂ y =0
for(int i = 0; i < row_node_no-1; i++) {
the_gh_array[node_order(row_node_no*(row_node_no-1)+i)](0) =
the_gh_array[node_order(row_node_no*(row_node_no-1)+i)](1) =
the_gh_array[node_order(row_node_no*(row_node_no-1)+i)](2) =
gh_on_Gamma_h::Dirichlet;
}
}
class PlateT3 : public Element_Formulation {
public:
PlateT3(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
PlateT3(int, Global_Discretization&);
};
Element_Formulation* PlateT3::make(int en, Global_Discretization& gd) {
return new PlateT3(en,gd);
}
static const double E_ = 1.0; static const double v_ = 0.25; static const double t_ = 0.01;
static const double D_ = E_ * pow(t_,3) / (12.0*(1-pow(v_,2))); 1 ν 0
static const double Dv[3][3]={
Et 3 ν 1 0
{D_, D_*v_, 0.0 }, D = -------------------------
{D_*v_, D_, 0.0 }, 12 ( 1 – ν 2 ) 1–ν
{0.0, 0.0, D_*(1-v_)/2.0 } 0 0 ------------
2
};
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
PlateT3::PlateT3(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
int ndf = 3;
Quadrature qp(2, 16);
H0 dx_inv;
H1 X;
{
H1 l(2, (double*)0, qp), coordinate transformation rule
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 3, 2, qp),
l0 = l[0], l1 = l[1], l2 = 1.0 - l0 - l1;
n[0] = l0; n[1] = l1; n[2] = l2;
X &= n*xl;
}
dx_inv &= d(X).inverse();
J dv(d(X).det());
Listing 4•22 9 dof triangular plate bending using nonconformming rectangular element (project workspace
file “fe.dsw”, project “triangular_plate_bending”).
P = L0 L 1 L2 L0 L1 L 1 L 2 L2 L0 Eq. 4•281
A triangular element can be conceived with six degree of freedoms, with three deflection variables “w” on the
corner nodes and three normal derivatives “ ∂ w/ ∂ n” on the three middle points of the triangle sides as depicted
in Figure 4•59.
w2
(∂w/∂n)4 (∂w/∂n)3
w0
w1
(∂w/∂n)5
Parallel to the derivation of Eq. 4•269 for a generic shape function, we have
The normal derivatives to the node number “3” can be obtained according to the formula1
∂
-----
l0 ∂ ∂ ∂ ∂ ∂
- --------- + --------- – 2 --------- + µ 0 --------- – ---------
- = ------ Eq. 4•283
∂n 3 4∆ ∂L 1 ∂L 2 ∂L0 ∂L 2 ∂L 1
where l0 is the length of the edge opposing to node number “0”, ∆ is the area of the triangle, and µi is defined as
l 22 – l12 l 02 – l 22 l12 – l 02
µ 0 = --------------
- , µ 1 = --------------
- , and µ 2 = --------------
- Eq. 4•284
l 02 l 12 l 22
Similarly we can define for the other two normal derivatives ( ∂ ⁄ ∂n ) 4 and ( ∂ ⁄ ∂n ) 5 . The derivatives of “Pα”
with respect to L0, L1, and L2 are
1. p.27 in Zienkiewicz, O.C. and R.L. Taylor, 1991, “The finite element method”, 4th ed., vol. 2. McGraw-Hill, Inc., UK.
∂w
l0
------- = ------
- [ – 2α 0 + ( 1 – µ 0 )α 1 + ( 1 + µ 0 )α 2 – α 3 + α 4 – α 5 ]
∂n 3 4∆
∂w
l1
-------
∂n 4 = 4∆ [ – 2α 1 + ( 1 – µ 1 )α 2 + ( 1 + µ 1 )α 0 – α 4 + α 5 – α 3 ]
------
-
∂w
------
l2
- [ – 2α 2 + ( 1 – µ 2 )α 0 + ( 1 + µ 0 )α 1 – α 5 + α 3 – α 4 ]
- = ------ Eq. 4•286
∂n 5 4∆
1 0 0 0 0 0
0 1 0 0 0 0
0 0 1 0 0 0
– 2l 0 l0 ( 1 – µ0 ) l0 ( 1 + µ0 )
---------- ------------------------ ------------------------ –1 1 – 1
C≡ 4∆ 4∆ 4∆ Eq. 4•287
l1 ( 1 + µ 1 ) – 2l 1 l1 ( 1 – µ1 )
------------------------ ---------- ------------------------ –1 –1 1
4∆ 4∆ 4∆
l2 ( 1 – µ2 ) l2 ( 1 + µ2 ) – 2l 2
------------------------ ------------------------ ---------- 1 –1 – 1
4∆ 4∆ 4∆
The shape function is defined as N = PC-1. We can still use the definition of stiffness matrix from Eq. 4•264,
∂w ∂w
θ̂ x = – -------, and θ̂ y = ------- Eq. 4•289
∂y ∂x
that improves the symmetry of plate theory equations. The relation of θ n to θ̂ x and θ̂ y can be expressed as
∂w ∂w ∂w
θ n = ------- = n x ------- + n y ------- = ( – n y )θ̂ x + n x θ̂ y Eq. 4•290
∂n ∂x ∂y
#include "include\fe.h"
static row_node_no = 9;
Omega_h::Omega_h() {
int row_segment_no = (row_node_no - 1)/2;
double v[2]; int ena[6];
for(int i = 0; i < row_node_no; i++)
for(int j = 0; j < row_node_no; j++) {
int nn = i*row_node_no+j;
v[0] = (double)j/(double)(row_node_no-1); v[1] = (double)i/(double)(row_node_no-1);
Node* node = new Node(nn, 2, v); the_node_array.add(node);
}
for(int i = 0; i < row_segment_no; i++)
for(int j = 0; j < row_segment_no; j++) {
int nn = i*row_node_no*2+j*2;
ena[0] = nn; ena[1] = ena[0]+row_node_no*2+2; ena[2] = ena[1]-2;
ena[3] = ena[2] + 1; ena[4] = ena[0]+row_node_no; ena[5] = ena[4]+1;
int en = i*row_segment_no*2+j*2;
Omega_eh* elem = new Omega_eh(en, 0, 0, 6, ena); the_omega_eh_array.add(elem);
ena[0] = nn; ena[1] = nn+2; ena[2] = ena[1] + row_node_no*2;
ena[3] = ena[1] + row_node_no; ena[4] = ena[3] -1; ena[5] = ena[0] +1;
elem = new Omega_eh(en+1, 0, 0, 6, ena); the_omega_eh_array.add(elem);
}
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) { __initialization(df, omega_h);
for(int i = 1; i < row_node_no-1; i+=2) bottom B.C. ∂ w/ ∂ n =0
the_gh_array[node_order(i)](0) = gh_on_Gamma_h::Dirichlet;
for(int i = 1; i < row_node_no-1; i+=2)
the_gh_array[node_order(i*row_node_no)](0) = gh_on_Gamma_h::Dirichlet;
left B.C. ∂ w/ ∂ n =0
for(int i = 0; i < row_node_no; i+=2) {
the_gh_array[node_order(i*row_node_no-1)](0) = right B.C. w = ∂ w/ ∂ n =0
the_gh_array[node_order((i+1)*row_node_no-1)](0) = gh_on_Gamma_h::Dirichlet;
}
for(int i = 0; i < row_node_no-1; i+=2) { top B.C. w = ∂ w/ ∂ n =0
the_gh_array[node_order(row_node_no*(row_node_no-1)+i)](0) =
the_gh_array[node_order(row_node_no*(row_node_no-1)+i+1)](0) =
gh_on_Gamma_h::Dirichlet;
}
the_gh_array[node_order(row_node_no*row_node_no-1)](0) = gh_on_Gamma_h::Dirichlet;
}
class PlateMorley6 : public Element_Formulation {
public:
PlateMorley6(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
PlateMorley6(int, Global_Discretization&);
};
Element_Formulation* PlateMorley6::make(int en, Global_Discretization& gd) {
return new PlateMorley6(en,gd);
}
static const double E_ = 1.0;
static const double v_ = 0.25;
static const double t_ = 0.01;
static const double D_ = E_ * pow(t_,3) / (12.0*(1-pow(v_,2))); 1 ν 0
static const double Dv[3][3] = {
Et 3 ν 1 0
{D_, D_*v_, 0.0 }, D = -------------------------
{D_*v_, D_, 0.0 }, 12 ( 1 – ν 2 ) 1–ν
{0.0, 0.0, D_*(1-v_)/2.0} 0 0 ------------
2
};
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
Listing 4•23 Morley’s 6-dof triangular plate bending(project workspace file “fe.dsw”, project
“morley_plate_bending”).