Você está na página 1de 647

What is the VectorSpace is a C++ library which enables the C++ language with mathematical liter-

VectorSpace acy. We explain the reasons for using the VectorSpace C++ Library from the perspective
C++ Library of programming language for numerical computation in followings.

Fortran Fortran language is very limited in expressing high-flown mathematics. It provides


only basic arithmetic operations on scalars (the use of transcendental functions are indi-
rectly supported by the way of standard library function calls). Mathematics certainly is
much more than just scalar operations. Fortran programmers are forced to translate the
mathematical expressions they face in their applications into basic arithmetic operations in
Fortran. The result is the program is very fast, since only a minimum set of basic arith-
metic operations is used. This result contributes a lot to Fortran’s long reign in the numeri-
cal computation, where speed is always highly appreciated. However, the process taken to
develop a Fortran program is rather abstruse, and the finished program are usually quite
unreadable and unmaintainable. The developing process is abstruse because the program-
mers are asked to do low-level optimization, with only basic arithmetic operations, right at
the beginning of their coding. The program is unreadable and unmaintainable because the
codes do not closely resemble the concise mathematical expressions. Fortran is a language
that enables you to “translate the formula” into computer codes by yourself, unfortunately,
not the language itself directly support you with the power of “For-mula tran-slation”.

Proprietary On the other hand, many symbolic languages arrived not long after the advent of For-
Mathematical tran more than three decades ago. Symbolic languages, many specialized in mathematics,
Oriented are well suited for expressing mathematics. However, symbolic languages failed misera-
Symbolic bly. After all these years, Fortran is still the number one language for numerical computa-
tion. The symbolic languages, by the nature of their construction, are just too slow for
Languages large-scale numerical computation. Moreover, the dilemma faced by a practical user of
symbolic languages is that if he doesn’t know much mathematics he will not use the sym-
bolic languages to do symbolic computation; on the other hand, if he knows mathematics
well, he would most likely derive solutions by hand than be distracted by the nitty-gritty of
the symbolic language.

Recent resurrection of mathematical-oriented symbolic languages is most amazing.


The major reason is that the graphic user interface in the software industry has improved
dramatically in the last decade. Therefore, the symbolic languages got a face-lift, acquir-
ing a very attractive front-end. In fact, many graphics in the first three chapters of this
workbook is done with one of such tools. However, if you really do serious numerical
computation, symbolic languages is simply not practical. For example, there are many
attempts to implement finite element method using symbolic languages. The finite element
method provides us a strict test case for the applicability of a language. It not only
demands expressive power of the high-flown mathematics, but also requires the ease of
implementation for complicated non-mathematical algorithms. The results of implement-
ing finite element with symbolic languages, show that they only deal with very limited,
most superficial problems in finite element method. Not mentionning the computation
speed is significantly penalized. No one in the core of finite element community will be
persuaded by these implementations to abandon Fortran and move to adopt the symbolic
languages!

C C language grew with the domination of the UNIX operation system in the computer
industry. Up to the 80’s, C had become the industrial flagship language. A professional
programmer must be very fluent in C. C is not very different from Fortran. They are both
type-compiled languages. They should run equally fast in principle. Fortran programmers,
however, are never given enough incentives to migrate from Fortran to C. The major rea-
son is that many legacy numerical packages and libraries are already written in Fortran.
Why the hassle to translate them all into C? The odd is that many C enthusiasts did re-
write these legacy numerical package in C in the last decade. However, even in the hey
day of C, scientists and engineers in most prestigious universities and institutes did their
numerical analysis in Fortran. The reason is simple. C does not provide dramatic improve-
ments to convince the community of numerical computation to change. Despite all that, C
was the most popular langauge in the 80’s because it was the industrial flagship general
purpose language, everyone else uses it (the majority—professional software engineers).

In the turn of 80’s to 90’s, C++ has grown out of the nut-shell of C as the new industrial
C++ flagship general purpose language. We are yet to see if C++ meets the criterion of offering
the dramatic improvements over Fortran to convince the community of numerical compu-
tation to move away from Fortran to C++. Be conservative to stay with Fortran! If C++ is
not, it deserves to be ignored by the community as C was. Experiences have shown that the
adoption of a premature standard may only lead to waste of time and energy.

C++ is a highly extensible general purpose language. This extensibility on top of C is


the data abstraction and the object-oriented programming of the C++ language. The
strong extensibility of C++ means that this general purpose language can be adapted to
many specialized fields with considerable versatility. The “Workbook of Applications in
VectorSpace C++ Library” is to show you that, as the flagship general purpose language
nowadays, C++ can be extended to express mathematics easily with the aid of the Vector-
Space C++ Library. You be the judge of VectorSpace C++ Library, or other similar devel-
opment in the coming years, to decide if they lift C++ up to the standard for your
acceptance.

VectorSpace C++ The gap between the mathematical expression and the computer code is diminished by
Library vs. the use of VectorSpace C++ Library objects, while the underlining modeling philosophy of
Symbolic VectorSpace C++ objects is still kept close to, unlike symbolic languages, the computa-
tional algorithms used in Fortran and C. This offers a tremendous advantage over symbolic
Languages
languages that it can serve as a rapid-proto-typing tool for numerical programming. If fur-
ther optimization is necessary, the rapid-proto-type can be reverse-engineered into plain C,
step by step under the consistent modeling philosophy. The process of optimization to
plain C is done seamlessly all in one language—C++, which is a powerful and reliable lan-
guage, and yet its compilers are so cheap and well-supported by the major vendors in the
software industry.
Other C++ Most of the other C++ mathematical libraries in the current market are provided as a
Libraries “wrapped-package” of conventional-style Fortran/C libraries which are based on the pro-
cedure programming method. In contrast, the object-oriented design of the VectorSpace
C++ Library is a complete overhaul based on a theoretically sound conceptual framework
of integrable-differentiable mathematical objects. The capability of differentiation and
integration is essential to many numerical computation. Many advanced numerical sub-
jects such as (1) computational linear algebra (matrix computation), (2) unconstrained
and constrained optimization, (3) variational methods, and (4) finite element method, have
all been shown, in this application workbook, to be revolutionary easier with the Vector-
Space C++ Library to implement than those with Fortran or C.

The C++ program written with VectorSpace C++ Library can be used to produce an
Rapid Proto-
end-product of your program development. For most of the numerical subjects demon-
Typing using strated in this application workbook, even if you are using a laptop PC, you do not need
VectorSpace further optimization at all to make a practical computation. These example programs run
C++ Library reasonably fast, mostly in a matter of a few seconds. However, the speed can be an impor-
tant factor in numerical computations. With the problem that you are facing with, you
might want to increase its number of variables (denoted as “n”) tens to hundreds of times.
The computation time and memory space might grow, in the order of O(n2) to O(n3),
respectively.1 This means that a possible increase of more than a million times in comput-
ing time and memory space for a large-scale problem (e.g, with n > 104). Similarly, for
matrix iterative solution method, transient problems and nonlinear problems, the same
program segment may need to be repeated over tens or hundreds of times. Program for
such problems, which originally runs within the acceptable range of a few second, could
easily run up to a few hours or days and becomes increasingly unacceptable. When that is
the case, the VectorSpace C++ Library can be used to generate rapid-proto-typing (or
intermediate-code) for further optimization, or reverse-engineering. Experiences show
that, for a non-trivial problem with the VectorSpace C++ Library proto-typing, the pro-
gram development time is always much shorter than if you would code in plain C directly.
The proto-typing not only help you grasp the mathematical idea quickly, but it also pro-
vides intermediate results to help you debug the final optimized code. At the end of a pro-
gram development, the intermediate-code can be commented out of the final code or
enclosed in conditional compilation segments. These disabled segments in the source files
may serve to improve the readability and maintainability of the final code. Therefore, after
you have reverse-engineered the prototype program in VectorSpace C++ objects, it is pos-
sible that your final code can be completely free of VectorSpace C++ Library objects. The
final product can be as pure as a crystal ball that it only has the ANSI/ISO C and C++.

Computer world is moving fast and everything is quite ephemeral. According to the
Moore’s Law, by the Intel co-founder Gordon Moore, the computer chip doubles its speed
every eighteen months. This basically defines the time scale for the computer industry
evolution. We doubt that VectorSpace C++ Library may have a life span as long as that of

1. as far as the matrix solver is concerned.


Fortran (~40 years so far), but we can guarantee you that your optimized code will last as
long as ANSI/ISO C/ C++ remain unchallenged in the software industry. VectorSpace is
so realistically humble that it even expects you to throw it out, at some point, without
causing too much trouble on you! VectorSpace C++ library can be used as a rapid proto-
typing tool for helping you construct your program. It does not set any standard to domi-
nate your world, except that it wants you to recognized the present-day industry standard
is ANSI/ISO C and C++—this little assumption is probably hard for anyone to argue oth-
erwise. We conclude that the adoption of C++ now is a right move.
Table of Contents
Preface i
Table of Contents v
Part I. Numerical Methods
Chapter 1 Computational Linear Algebra Using C0 Type Objects 1
1.1: C0 Type Objects 1
1.1.1: Scalar 2
Constructors 2
Operators and Functions 5
1.1.2: Vector 9
Consturctors 9
Operators and Functions 12
1.1.3: Matrix 18
Constructors 21
Operators and Functions 22
1.1.4: Matrix Algebra 29
LU Decomposition 29
Cholesky Decomposition 31
QR Decomposition 33
Eigenvalue Problem 37
Singular Value Decomposition 38
1.1.5: Subvector and Submatrix 41
Constructors and Selectors 42
Other Operators and Functions 47
1.1.6: Basis 48
1.2: Applications of Computational Matrix Algebra 60
1.2.1: Solution of Simultaneous Linear Equations: LU Decomposition 60
Electric Circuit—Wheatstone Bridge 60
Finite Difference for 2-D Heat Conduction 63
1.2.2: Linear Least Squares: Cholesky and QR Decompositions 69
Car Sale Market Prediction 69
Computer Tomography (CT) 75
1.2.3: Eigenvalue Problems 84
Buckling of a Rod 84
Sigular Value Decomposition and the Principal Components Analysis 87
Chapter 2 Numerical Optimization Using C1 and C2 Type Objects 95
2.1: C1 Type Objects 95
2.1.1: Tangent Bundle 96
Constructors 97
Operators and Functions 98
2.1.2: Vector of Tangent Bundle 99
Constructors 100
Operators and Functions 102
2.2: C2 Type Objects 104
2.2.1: Tangent of Tangent Bundle 104
Constructors 105
Operators and Functions 105
2.2.2: Vector of Tangent of Tangent Bundle 108
Constructors 108
Operators and Functions 109
2.3: Linear Programming and Non-linear Optimization 113
2.3.1: Linear Programming 113
Basic Set Method 113
Active Set Method 118
2.3.2: Unconstrained Optimization 123
Classic Newton’s Method 123
Steepest Descent Method 127
Combined Newton and Steepest Descent Method 129
Conjugate Gradient Method 131
Quasi-Newton Method 133
2.3.3: Constrained Optimization 137
Reduced Gradient Method 137
Gradient Projection Method 141
Lagrange Methods 145
Range Space and Null Space Methods 149
Penalty Methods 153
Augmented Lagrangian Method 162
Perturbed Lagrangian Method 162
Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects 165
3.1: H0 Type Objects 165
3.1.1: Quadrature 165
3.1.2: Integrable_Scalar 169
Constructor 170
Operators and Functions 174
3.1.3: Integrable_Vector 176
Constructors 176
Operators and Functions 178
3.1.4: Integrable_Matrix 180
Constructors 180
Operators and Functions 181
3.1.5: Utility Integrable Objects—Integrable_Subvector and
Integrable_Submatrix 181
3.2: H1 Type and H2 Type Objects 183
3.2.1: Integrable_Tangent_Bundle / Integrable_Vector_of_Tangent_Bundle 183
Constructors 183
3.2.2: Integrable_Tangent_of_Tangent_Bundle /
Integrable_Vector_of_Tangent_of_Tangent_Bundle 198
Constructors 198
3.3: Variational Methods 201
3.3.1: Rayleigh-Ritz Method 202
Second-order Differential Equation 202
Fourth-order Differential Equation 206
Poisson Equation 223
3.3.2: Weighted-Residual Method 227
Point-Collocation Method 227
Subdomain-Collocation Method 230
Method of Moment 231
Galerkin Method: Bubnov-Galerkin / Petrov-Galerkin Methods and
Weak Formulation 233
Least Squares Method 240
3.3.3: Boundary Solution Methods 242
Trefftz Method 242
Boundary Element Method 246
3.3.4: Transient Problems--Initial Value Problem 253
Parabolic Equation 253
Hyperbolic Equation 256
Part II. Finite Element

Chapter 4 Finite Element Method Primer 265


4.1: Basics of Finite Element Method 266
4.1.1: Mathematical Abstraction of Finite Element Method 266
Finite Element—A Systematic Treatment for Complex Geometry 266
Finite Element Approximation 268
Global Matrix and Solution Phase—Systematic Treatment for
Large-Size Problems 270
4.1.2: Object-Oriented Model of the Finite Element Method 270
Step 1. Discretization of Global Domain— 271
Step 2. Free and Fixed Variables 274
Step 3. Element Formulation 277
Step 4. Matrix Representation and Solution Phase 283
4.1.3: Object-Oriented Analysis and Design of Finite Element Method 285
Dependancy Graph 285
Graph Level Structure 286
Composite Class from a Dependency Graph 291
4.1.4: A Program Template for Using fe.lib 293
4.2: One Dimensional Problems 295
4.2.1: A Second-Order Ordinary Differential Equation (ODE) 295
Linear Line Element 296
Quadratic Line Element 300
Cylindrical Coordinates For Axisymmetrical Problem 302
4.2.2: A Fourth-Order ODE —the Beam Bending Problem 305
Irreducible Formulation—Piecewise Cubic Hermite Shape Functions 306
Mixed Formulation 312
Lagrange Multiplier Formulation 318
Penalty Function Formulation 324
4.2.3: Nonlinear ODE 327
Galerkin Formulation 327
Least Squares Formulation 331
4.2.4: Transient Problems 336
Parabolic Equation 336
Hyperbolic Equation 339
4.2.5: The Mixed Formulation Revisited—Matrix Substructure Method 343
Matrix Substructuring 344
Object-Oriented Modeling for Matrix Substructuring 345
4.3: Two Dimensional Problems 351
4.3.1: Heat Conduction 351
Basic Physics and Element Formulation 351
An Example with Bilinear 4-nodes Element 353
A 2-D Geometrical Tool — “block()” 356
Lagrange 9-Node Element for Heat Conduction 361
Post-Processing—Heat Flux on Gauss Points 363
Post-Processing—Heat Flux Nodal Projection 364
4.3.2: Potential Flow 368
Basic Physics and Element Formulation 368
Stream Function—ψ Formulation 370
Velocity Potential—φ Formulation 373
4.3.3: Plane Elasticity 375
Impelementations for B-Matrix Formulation: 380
Implementations for Indicial Notation Formulation: 384
Implementation for Coordinate-Free Tensorial Formulation: 388
Post-Processing—Nodal Reactions 393
Post-Processing—Stresses on Gauss Points 393
Post-Processing—Stress Nodal Projection Method 394
Shear Locking of Bilinear 4-Node Element 397
Quadratic Element 405
Dilatation Locking of
Nearly Incompressible Elasticity in Plane Strain 406
4.3.4: Patch Tests—Finite Element Test Suites for Software Quality
Assurance (SQA) 411
Patch Tests—Consistency and Stability 411
Weak Patch Test for an Axisymmetrical Problem 417
Higher-Order Patch Test 422
4.3.5: Stokes Flow 425
Plane Couette-Poiseuille Flow 427
Driven Cavity Flow 431
4.3.6: Plate Bending Problems 433
Basic Plate Theory 433
Kirchhoff (Thin-) Plate Thoery and Finite Element Formulation 435
Nonconforming Rectangular Element 436
Conforming Rectangular Element 441
9-dof Triangular Element 442
Morley’s 6-dof Triangular Element 446
Chapter 5 Advanced Finite Element Methods 451
5.1: Mixed and Hybrid Finite Element Methods 451
5.1.1: Heat Conduction 451
Mixed Formulation 451
Hourglass Element 464
5.1.2: Mixed Formulation for Plane Elastiticity 468
Hellinger-Reissner Variational Principle 468
Hu-Washizu Variational Principle 475
Iterative Method for Hu-Washizu Mixed Formulation 484
Incompressible u-p Formulation 485
Incompressible u-p-εv Formulation 495
B Method 502
Non-conforming Element 509
Hourglass Element 514
5.1.3: Hybrid Formulation for Plane Elasticity 517
Irreducible Subdomains 517
Pian-Sumihara Element 524
5.1.4: Reissner-Mindlin Plate Formulation 529
θ-w Irreducible Formulation 529
θ-S-w Mixed Formulation 536
Collocation of Shear Constraint on Reissner-Mindlin Plate 546
5.2: Contact Mechanics 551
5.2.1 Baisc Physics 551
5.2.2 Frictionless Rigid Punch on Elastic Foundation 553
5.2.3 Frictionless Rigid Sled on Elastic Foundation 565
5.3: Elastoplasticity 573
5.3.1: Basic Theroy 573
Non-linear Problem with Incremental Loading and
Global Newton-Raphson Iteration 573
Implementation of Incremental Loading and
Global Newton-Raphson Iteration 574
5.3.2: Radial Return Mapping Algorithm 576
Theory of Elsatoplasticity 576
Stress-strain Path Integration Algorithm—Closest Point Projection 577
Consistent Tangent Moduli 578
5.3.3: Object-oriented Model for Stress-strain Integration Scheme 579
Implementation of Radial Return Mapping with Consistent
Tangent Moduli 581
5.3.4: Perforated Stripe Under Uni-axial Extension 584
Conjugate Gradient Method 585
Quasi-Newton BFGS Method 588
5.4: Finite Deformation Elastoplasticity 590
5.4.1: Counter Examples of Infinitesimal Assumption 590
Strain Measures 590
Stress Rates 591
Material Moduli 592
5.4.2: Basic Theory of Finite Deformation Elastoplasticity 592
Kinematics 593
Deformation Gradient 593
Lagrangian Strain Tensor and Eulerian Strain Tensor 594
Symmetric Positive Definiteive Tensor and Orthogonal Tensor 594
Polar Decomposition Theorem 595
Deviatoric-Spherical Kinematic Split 596
Lie Derivatives and Objective Rates 597
Piola Transformation, Stresses, and Stress Rates 598
Consititutive Equations 599
5.4.3: An Isotropic Material with Uncoupled Volumetric and
Deviatoric Response 602
Elasticity 602
Multiplicative Decomposition of Elastic and Plastic Deformation 603
Plastic Metric Tensor and the Form of Elastoplastic Free Energy 603
Principle of Maximum Plastic Dissipation and Plastic Flow Law 604
Volume-Preserving Plastic Flow Rule 604
Consistent Tangent Moduli 606
Three-Field Hu-Washizu Variational Principle for Finite Deformation
Elastoplasticity 606
5.4.4: Implementations of Stress-strain Path Integration for
Finite Deformation 607
Implementation 1: Cutting-plane Method 608
Implementation 2: Closest-point-projection Method 614

Index 623
Part. I. Numerical Methods

Chapter 1. Numerical Linear Algebra Using


C0 Space Objects
Chapter 2. Numerical Optimization Using
C1 and C2 Space Objects
Chapter 3. Variational Methods Using
H0, H1, and H2 Space Objects
Part. II. Finite Element

Chapter 4. Finite Element Method Primer

Chapter 5. Advanced Finite Element Methods


Workbook of Applications
in VectorSpace C++ Library

Numerical Linear Algebra,


Optimization (Constrained/Unconstrained),
Variational Methods, and
Finite Element Methods
CHAPTER

Computational Linear Algebra


One Using C0 Type Objects

Cn spaces, as in usual mathematical definition, are continuous (linear) vector spaces with derivatives up to
order n. This chapter deals with objects in C0 space.

1.1 C0 Type Objects


In VectorSpace C++ Library, objects in C0 space is represented by C0 type. C0 type contains many funda-
mental objects in numerical computation. We subdivide them into primary and utility objects. The primary
objects include Scalar, Vector and Matrix, which are fundamental objects in linear algebra. The utility objects
include Subvector, Submatrix and Basis. In engineering practice, Subvector and Submatrix are very popular in
giving out explicit formula. In linear algebra, the expression can be written in Basis. The applications in this
chapter focus on matrix algebra, while for each application we have the choice of using either (1) Subvector/Sub-
matrix or (2) Basis expression to achieve the equivalent mathematical expressions in C++ program.
VectorSpace C++ Library has many extended definitions that are not in ANSI/ISO C++. A simple program
using VectorSpace C++ Library is shown in Program Listing 1•1. The first line is to include a header file “vs.h”.
This file is in the sub-directory “vs\include”.1 The programs in this chapter are all in project workspace file
“C0.dsw” under directory “vs\ex\C0”.

1. The include file enables C++ complier to understand the extended definitions in the VectorSpace C++ Library.
You also need to use the VectorSpace C++ Library— “vs.lib” under directory “vs\lib”, for the linker to resolve
the external references of your program to the library in order to construct an executable file.

Workbook of Applications in VectorSpace C++ Library 1


Chapter 1 Computational Linear Algebra Using C0 Type Objects

#include “include\vs.h”
int main() {
C0 a(0.0);
dedicated constructor; value = 0.0
cout << a << endl;
C0 b = SCALAR(“const double&”, 1.0),
c =C_0 (“const double&”, 2.0);
virtual constructors; the string severs as
cout << b << endl a memonic for the parameter that is sup-
<< c << endl; plied to it.
try (C0 d = SCALAR(“wrong string”, 0.0);
} catch(const xmsg& e) { exception handling
cout << “Excetption: “ << e.what() << “ at “ << e.where() << “ line “ << e.line() << endl;
}
}

Listing 1•1 Scalar object constructor (project: “scalar_examples”)

1.1.1 Scalar
The concept of data abstraction in C++ organizes data and the operations on the data in a coherent unit—
class. The class of a Scalar defines the simplest data abstraction in VectorSpace C++ Library. A Scalar is a class
with a number s ∈ as its private member data, represented as double type in C++, associated with its con-
structors, operators and member functions (see Figure 1•1). The private member data, in this case a double type,
is shielded by the operators and the member functions through which the access from the outside world to the
private member data is only possible. In mathematics, a group, with a set G and operator ◊ , is denoted as (G ,
◊ ). Here a class in C++ defined with the concept of data abstraction closely resembles the concept of a group in
mathematics.

...
...

double
+
Access from outside world
=
operator=() only possible through member
functions and operators
sin()

Figure 1•1 Data abstraction organizes data and its operators and member functions in a coherent

Constructors
A scalar object in C++ program is declared as a C0 type with either dedicated constructor or virtual con-
structor as in Program Listing 1•1. The advantage of using a dedicated constructor is that it is very concise. It
only specifies a double value as the argument for the C0 constructor. C0 constructor knows the result is a Scalar
object by identifying that there is only one argument of type double supplied to the C0 constructor. In the Pro-

2 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
gram Listing 1•1 (the project: “scalar_examples” in porject workspace file “C0.dsw”), a double value “0.0” is
used to initialized the value of the Scalar object of C0 type as
C0 a(0.0);

Supplying arguments of different types to the dedicated constructor of C0 type may result in other kinds of
objects, e.g., a Vector, a Matrix ... etc., in the C0 type family. The philosophy of the dedicated constructor is that
it is in accordance with the style of the C language. The syntax of using dedicated constructor saves a few
punches on the keyboard for programmers. Especially beneficial to professional programmers who work on
computer so often that tedious acts can be very annoying. However, the flexibility of the dedicated constructor is
compromised. What would happen, if we want to supply a pointer to a double to initialize the Scalar object?
How do we instantiate a reference to a double value, a “by-value” from a Scalar object, a “by-reference” from a
Scalar object, or a pointer to a Scalar object ... etc. ?
To extend such flexibility, virtual constructor1 is used to instruct the C0 type constructor to know exactly
what kind of object to generate, a Scalar, a Vector, or a Matrix, ... etc. This kind of constructor is called virtual
because it mimics the function dispatching behavior of the virtual function—a salient objected-oriented pro-
gramming feature in C++. Through the virtual function mechanism in C++, a call on the virtual function (“foo()”
in Figure 1•2) in the base class can be dispatched to the same function in the derived class. Actually, critics of
C++ often say that C++ is not an orthodox object-oriented language. The implementation of the virtual construc-
tor, which is not supported by C++, steps in the direction closer to an orthodox object-oriented language with the
use of VectorSpace C++ Library.

C0
Base class
call foo() virtual foo();
...

inheritance
dispatch route
Derived
classes Scalar Vector Matrix etc.
foo(); foo(); foo();
... ... ...

Figure 1•2 Object-oriented class inheritant relationship and virtual function dispatching
mechanism.

A concrete object, for example a Scalar, is generated at run-time by the virtual constructor of its base class C0
as (see Program Listing 1•1)
C0 b = SCALAR(“const double&”, 1.0);

1. J.O. Coplien, 1992, “Advanced C++ — Programming Styles and Idioms”, Addison-Wesley, p. 143.

Workbook of Applications in VectorSpace C++ Library 3


Chapter 1 Computational Linear Algebra Using C0 Type Objects
The object, “b”, is of type C0. C0 type constructor generates a Scalar object, by explicitly specifying “SCA-
LAR”. The first argument supplied is a constant string—“const double&”, with “&” meaning that it is a refer-
ence, which in C++ is used to improve the efficiency of argument passing, and “const” to mean that the content
can not be changed for data integrity. The second argument is a double constant—“1.0”, consistent with the
semantics of the string in the first argument. The purpose of the string is not only for the constructor to identify
the kinds of arguments to be passed following it, but it also serves to increase readability of the program. Many
different types of arguments are possible to be supplied to for the Scalar object. This set of virtual constructor
strings furnishes the flexibility for the virtual constructor.

virtual constructor string VectorSpace C++ library definition priority

by reference
“C0&” C0 type Scalar object 1
“C0*” a pointer to C0 type Scalar object 2
“double&” double 3
“double*” double pointer 4

by value
“const double&” double 5
“const double*” double pointer 6
“const C0&” C0 type Scalar object 7
“const C0*” pointer to C0 type Scalar object 8
Strings in C0 virtual constructor for Scalar object.

Some of the string contains more than one word. In VectorSpace C++ library, the string for the constructor
are parsed in free-format. Free-format uses one or more spaces or commas to separate words. For example, two
words separated by one space “const^double&” and two words separated by two spaces “const^^double&” will
be recognized as the same by the virtual constructors.
Instead of explicitly specifying “SCALAR” for the C0 type constructor, one can also use “C_0” in place of
the “SCALAR” to construct a Scalar object. For example (see also Program Listing 1•1),
C0 b = C_0(“const double&”, 2.0);

In such a case, the C0 type constructor searches for a matching string according to the priority ranking of the
strings (see Figure 1•2, searching in the order of left to right through the branches of object-hierarchy tree).
When the constructor finds the first match it generates a corresponding object, a Scalar, a Vector, a Matrix ... etc.
A specification of “C_0” instead of any specific type of object being committed in the time of program writing,
is a late-binding technique used in the VectorSpace C++ Library. The determination of the actual kind of object
to generate can be delayed until run-time. You can code logic control statements in your program to determine
what kind of object to generate, then pass an appropriate string to the constructor. Therefore, the object and its
type are both created on the fly. In VectorSpace C++ Library, this particular kind of virtual constructor is called
autonomous virtual constructor1. However, the power of flexibility comes as a frontal assault on the security of

1. see “autonomous generic exemplar idiom” in J.O. Coplien, 1992, “Advanced C++—Programming Styles and Idioms”,
Addison-Wesley, p. 291.

4 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
C++ as a type-compiled language. The programmer’s own discretion is advised for the use of the virtual con-
structor.
We have provided with many kinds of constructors—dedicated constructor, virtual constructor, and the most
dynamic autonomous virtual constructor. This does not mean that you have to use them all in order to have your
program up and running. You can use dedicated constructor exclusively to maintain the strong type-complied
C++ language tradition for the sake of safety. The additional virtual constructors are provided to unleash the
power of the object-oriented programming. The limitation is only a programmer’s imagination not the C++ pref-
erence on not fully committed to the object-oriented paradigm. In complement, the exception handling can be
used to tame potentially rampant situations. Exception handling can be used as (see Program Listing 1•1)
try (C0 d = SCALAR(“wrong string”, 0.0); // cause an exception to be thrown
catch(const xmsg& e) {
cout << “Exception: “ << e.what() << “ at “ << e.where() << “ line “ << e.line() << endl;
}

The try-catch statement is standard in C++ language. The xmsg object simulates the standard C++ library
exception handling1. The details and location (function name and line) of an error, if any, caused by the C0 con-
structor enclosed in try clause will be reported to the standard output in the above code segment.

Operators and Functions


There are three kinds of operators in VectorSpace C++ library. The symbolic operator, arithmetic operator,
and logic operator.
Symbolic operator in Scalar object has two kinds of assignment operators. The assignment by reference oper-
ator “&=” and assignment by value operator “=” (see Program Listing 1•2).

#include “include/vs.h”
int main() {
C0 a(0.0), b, a = 0.0; b not initialized
c(1.0), d; c = 1.0; d not initialized
cout << a << endl; 0.0
b &= a; assignment by reference
c =a; assignment by value
a = 10.0; reset to 10.0
cout << b << endl
10.0
<< c << endl;
0.0
garbage collection behind the scene
a = c;
d = a;
make a new Scalar object
}

Listing 1•2 Assignment by reference and assignment by value (project: “scalar_examples”).

1. P.J. Plauger, 1995, “The draft standard C++ library”, Prentice Hall, Inc., p.53.

Workbook of Applications in VectorSpace C++ Library 5


Chapter 1 Computational Linear Algebra Using C0 Type Objects

a
b &= a;
b a = 10.0;
Scalar
0.0 10.0
label

double c c = a;

1.0 =

Figure 1•3 Assignment by reference (upper part) and by value (lower part).
In Program Listing 1•2 (see also Figure 1•3 ), variable “b” is declared as an object of type C0. The variable
“b” actually acts more like a label (or a symbol), because it has no concrete data type, e.g., a Scalar, a Vector or a
Matrix, associated with it. The label “b” is then assigned to share the same concrete data, the Scalar object with
“a” as its label, and “0.0” as its content. This is done by the assignment by reference operator as (illustrated in
the upper part of Figure 1•3)

b &= a;

We can think this expression as an operation to attach the label “b” to the Scalar object that has already been
labeled as “a”. Therefore, changes made to the content of “a” to 10.0, by “a = 10.0;” later, will also change the
content of “b”, because “a” and “b” are referring to the same memory location. The assignment by value (see the
lower part of Figure 1•3) in Program Listing 1•2 is

c = a;

In this case, “c” already has its own copy of a Scalar object with its content initialized as “1.0”. The “assignment
by value” operation then reassigns its value to that of “a” object—“0.0”. Later on, changing “a=10.0”; does not
change the value of “c”. The value of “c” remains as “0.0”.
What then will happen if we write (see also upper part of the Figure 1•3)

a &= c

The result is that the label “a” will be peeled off from the Scalar object that it is attached to. Then, some house-
cleaning chore needed to be done. The VectorSpace C++ Library will check if the detached Scalar object has any
other label refers to it. If there is, in this case the label “b” is still referring to it, the Scalar object will survive. If
there isn’t, the Scalar object will be killed. This is done by reference-counting1, a popular garbage collection
technique for memory management in C++. The next step, after garbage collection, is that the label “a” is then

1. J.O. Coplien, 1992, “Advanced C++—Programming Styles and Idioms”, Addison-Wesley, p. 58.

6 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
a &= c;
a c

0.0
b
10.0

d = a;
d

?=

Figure 1•4 Assignment by reference (upper part) and by value (lower part).

reassigned to the Scalar object that label “c” is pointing to. In short, “a” is striped off its original associated Sca-
lar object and re-assigned to point to the Scalar object that “c” is appointed to. A contrary scenario is the case of
assigning a label “d” by value as

d = a;

In this case (see the lower part of Figure 1•3), “d” is only a label, with no concrete object associated with it. Upon
the assignment by value, label “d” will be instantiated with a concrete object with the same type of object as “a”,
a Scalar object in this case. Then this newly instantiated Scalar object will be assigned a value of “0.0”. In short,
a newly constructed Scalar object (since “c” pointed to a Scalar object) is created for label “d”, and its value is
also set to the Scalar object associated with “a”. That is both the object type and its content of label “d” is deter-
mined according to what label “a” is referring to.
Two symbolic operators “&” and “&&” are column-wise-concatenation operators. Their return values are
Vector objects. The operator “|” and “||” are row-wise-concatenation operators. They return row-vectors which
are represented as Matrix of row-length = 1 in VectorSpace C++ Library. We defer discussion of these operators
until Vector and Matrix are introduced.
The use of member arithmetic operators, logic operators and functions for the Scalar object are straight-for-
ward. They are defined to be consistent with C++ without much explanations (see box in page 8). The actual set
of operators and functions is many times greater than the partial listing here. Many operators and functions not
listed here are actually proliferation due to the promotion among different types of a binary operator. For exam-
ple, (in project: “scalar_examples”)

C0 a(1.0);
C0 b = a + 2.0; // “a” a Scalar object of type C0 plus a constant double “2.0”
C0 c = 2.0 + a; // “2.0” a constant double plus a Scalar object of type C0

Workbook of Applications in VectorSpace C++ Library 7


Chapter 1 Computational Linear Algebra Using C0 Type Objects
That is all the double(s) in the above have been promoted, by VectorSpace C++ Library not by C++ per se, to a
Scalar object of C0 type before they are added to the other Scalar object of C0 type. Many other transcendental
functions can be expressed using this small set of functions. However the function pow(int) takes only integer
argument. It is illegal, for example, to write “a.pow(-2/3)” to express a-2 / 3, because -2/3 is a fractional argument.
However, one can express it with the available functions exp() and log(), as “exp(log(a)*(-2/3))”.

operator or function VectorSpace C++ library definition remark

symbolic operators
C0& operator &= ( ) assignment by reference
C0& operator = ( ) assignment by value
C0 operator & ( ) const column concatenation
C0 operator && () const one-by-one column concatenation
C0 operator | ( ) const row concatenation
C0 operator || () const one-by-one row concatenation

arithmetic operators
C0 operator + ( ) const positive unary
C0 operator - ( ) const negative unary
C0 operator + (const C0&) const addition
C0 operator - (const C0&) const subtraction
C0 operator * (const C0&) const multiplication
C0 operator / (const C0&) const multiplication
C0& operator += (const C0&) replacement addition
C0& operator -= (const C0&) replacement subtraction
C0& operator *= (const C0&) replacement multiplication
C0& operator /= (const C0&) replacement division

logic operators
int operator == (const C0&) const equal TRUE == 1
int operator != (const C0&) const not equal FALSE == 0
int operator >= (const C0&) const greater or equal
int operator <= (const C0&) const less or equal
int operator > (const C0&) const greater
int operator < (const C0&) const less

functions
C0 pow(int) const power
C0 sqrt(const C0&) const square root
C0 exp(const C0&) const exponent
C0 log(const C0&) const log
C0 sin(const C0&) const sin
C0 cos(const C0&) const cos
Partial listing of scalar object arithmetic operators, logic operators and functions.

8 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
1.1.2 Vector
The design of data abstraction for a Vector is represented as two private data members (“length”, “vi”) where
“length” is an integer number—“length” ∈ I, and an array of real number— “vi” ∈ , with 0 ≤ i < “length”. In
C++ a variable size array can not be declared as an array of double. Therefore, vi is actually represented by an
array of pointers to double.
Please notice that the differences of “array of pointers to double” and an “array of double”. Following tradi-
tions of C language, C++ makes pointer syntax looks like array from user’s perspective through the so-called
pointer-arithmetic. There are situations where subtle differences exist, and from compiler writer’s perspective
these two are in fact completely different animals.1 The internal implementation of VectorSpace C++ Library
uses “array of pointers to double” exclusively.
In VectorSpace C++ Library a vector is always viewed as a column-wise vector, while the row-wise vector,
when the distinction is necessary, is represented as a matrix of “row-length” = 1. Before we go any further on the
introduction of Vector, we re-visit two column-wise-concatenation operators left un-explained on page 7. The
column-wise-concatenation operator “&” is used for two Scalars as (project: “vector_examples”)

C0 a(0.0), b(1.0); // two Scalars of type C0


C0 c = a & b; // concatenation of two Scalars return a Vector of length =2.
cout << c << endl; // {0.0, 1.0}T

The return value of “c” is a Vector object of C0 type with its value as {0.0, 1.0}T, column-vector is denoted with
“transpose” superscript. Using “&&” makes no difference in the case with two Scalar objects as operands. How-
ever, if Vector is used as either of the two operands of the binary operators “&” and “&&”, we will see different
results from these two operators (see Figure 1•6 in page 14).

Constructors
Now let’s get on with “Vector” in VectorSpace C++ Library. The dedicated constructor for the Vector can be
written as (see Program Listing 1•2)

#include “include\vs.h”
int main() {
double a[3] = {0.0, 1.0, 2.0};
C0 b( 3, a); array name “a” is treated as double*
cout << b << endl; {0.0, 1.0, 2.0}T by reference
C0 c(3, (double*)0);
null pointer “0” is cast as double*
cout << c << endl;
{0.0, 0.0, 0.0}T, with default value and
return 0;
}
its own memory.

Listing 1•3 Dedicated constructor of a Vector object (project: “vector_examples”).

1. Chapters 4 and 9 in P. V. Linden, 1994, “Expert C programming: deep C secrets”, Prentice-Hall Inc.

Workbook of Applications in VectorSpace C++ Library 9


Chapter 1 Computational Linear Algebra Using C0 Type Objects
double a[3] = {0.0, 1.0, 2.0};
C0 b( 3, a); // array name “a” is cast as double* by C++
cout << b << endl; // {0.0, 1.0, 2.0}T
In the second line of the above codes, the array name “a” is treated as a double*, and passed, as a reference,
to the dedicated constructor of C0 type for getting a Vector object. Changes on the values of double array “a”
will change the content of Vector object pointed to by “b”. From this example, it is clear that the dedicated con-
structor is obtained by being given two arguments to the C0 type constructor, with the first one as an int type and
the second one as a pointer to double type. We can avoid declaring array “a” at all, if the dedicated constructor is
not going to refer to an outside memory space as (see Program Listing 1•2)

C0 c( 3, (double*)0); // null pointer “0” is cast as double*


cout << c << endl; // {0.0, 0.0, 0.0}T

In this case, a Vector “c” will have its own copy of memory space and its values are all initialized to a default
value—“0.0”. This is the most concise way of initializing a Vector object. In VectorSpace C++ we call this spe-
cific style as default dedicated constructor. The default value can be easily reset to other values with a statement
as c = 2.0. In this case all three elements of the Vector “c” will be set to have the value of “2.0”. This says that
the assignment by value operator takes a double value as its argument. Such implicit type conversion happens, in
VectorSpace C++ Library, only when it makes unambiguous intuitive sense.
The dedicated constructor can be used to make a new kind of object that we have not introduced in section
1.1.1 about Scalar. This new kind of object is a subvector which refers to an existing Vector object. The subvec-
tor can start from and end at any index within the index range of the referenced Vector object “a”, provided that
Vector “a” has to be continuous in its physical memory space. For example,

double d[8] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
C0 a(8, d),
b(4, a, 3);
cout << (+b) << endl;

In Figure 1•5, Vector “a” is constructed as a vector of length 8 by “C0 a(8, d);” where double array “d” is
declared as “double d[8] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};”. A reference Vector “b” is initialized to have its
length “4”, the referenced object “a”, and the index to start from “a” being “3” by writing the following state-
ment.

C0 b(4, a, 3);

We consider the reference Vector as a special kind of Vector object instead of a Subvector. The word Subvector
(with capital “S”) in VectorSpace C++ Library will be reserved for subvectors that can have its referenced vector
equal-partitioned as will be discussed in Section 1.1.5.
A reference Vector, as its name implies, always references to another Vector object with continuous physical
memory space. It doesn’t own the memory space of its data. A common practice in VectorSpace C++ Library is
to use the unary positive operator “+” to cast a reference Vector into a new independent Vector object such as
“+b”. A temporary Vector object will be generated in this case to have its own copy of memory with the same

10 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

C0 a(8, d);
double d[8] = 0.0 0
1.0 1
C0 b(4, a, 3); +(b)
2.0 2

3.0 3 0 3.0 0

4.0 4 1 4.0 1

5.0 5 2 5.0 2

6.0 6 3 6.0 3

7.0 7
Figure 1•5 Referenced Vector “b”, and “primary casting” by unary operator +().
length (= 4), and with the contents of the memory to be set to have the same values as “b”. The use of the unary
positive operator “+” to convert a specialized object, in this case a reference Vector, into a more primitive object
will be encountered many times in VectorSpace C++ Library. This operation is defined as primary casting, Vec-
torSpace C++ Library, by the operator “+”.
Similar to the case for Scalar, the constant strings used for the plain virtual constructors (used macro defini-
tion “VECTOR” in place of “SCALAR”) and the autonomous virtual constructor are shown in the following box
Some of the constant strings do not have a priority number. That is because those strings clash with the strings in
the Scalar. The C0 type autonomous virtual constructor will first find a match in Scalar object; it will never have
a chance to be dispatched up to Vector object virtual constructor to make it. This means the strings in Vector are
hidden (or masked) by the same strings in Scalar. Therefore, these strings although useful for the plain virtual
constructor will not be useful for the autonomous virtual constructor to make any Vector object of C0 type.

virtual constructor string VectorSpace C++ library definition priority

by reference
“C0&” C0 type Vector —
“C0*” a pointer to C0 type Vector —
“int, double*” length, double* != 0 10
“int, double*, int, int” length, double*, m_row_size, m_col_size 11

by value
“int” length 9
“int, double*” length, double* = 0 10
“int, const double*” length, double* 12
“int, const C0*” length, C0* of a Scalar 13
“const C0*” C0* —
“int, C0&, int” length, C0, starting index 14
(the only one for reference Vector)
Strings in C0 virtual constructor for Vector object.

Workbook of Applications in VectorSpace C++ Library 11


Chapter 1 Computational Linear Algebra Using C0 Type Objects
Priority number 10 appears both in the by reference and by value categories because they can be distin-
guished by passing a pointer to double or a casted double* of a null pointer. The semantics of constructing an
object with default value by the virtual constructors is exactly the same here as that in the case for dedicated con-
structors.
The last string “int, C0&, int” is for constructing a reference Vector. The argument passing of the virtual con-
structor for the reference Vector matches exactly with that of the dedicated constructors.

Operators and Functions


The symbolic operators for Vector have two assignment operators, one selector—“operator [] (int)”, and
four concatenation operators (see box in page 13).
The assignment operators, if using only Vector objects as operands, will have exactly the same behavior as
we have introduced in Program Listing 1•2 for the case of two Scalars. The complexity begins to fold on when
we assign a Vector object with different type objects as in the following (see Program Listing 1•2.)

#include “include/vs.h”
int main() {
C0 a(1.0), a = 0.0; a Scalar
b(3, (double*)0); b = {0.0, 0.0, 0.0}T; a Vector
b = a; assignment by value with a Scalar
cout << b << endl; {1.0, 1.0, 1.0}T
b = 2.0;
assignment by value with a double
cout << b << endl;
{2.0, 2.0, 2.0}T
b &= a;
cout << b << endl;
assignment by reference with a Scalar
return 0;
1.0; “b” is now a Scalar!
}

Listing 1•4 Assignment operators with different types of operand.

Assigning Vector “b” by value with either a Scalar object of C0 type or a double type in C++ is defined as
setting all of the components of “b” to have the value of the Scalar or the double. Assignment by reference for a
variable “b” of type C0, in this case a Vector, by a Scalar results in a Scalar (as in b &= a). The type of object
associated with label “b” has been changed from a Vector to a Scalar. The garbage collection mechanism
explained in page 6 will be activated to check if the memory space for the Vector object needs to be released.
The brand new symbolic operator—selector is the operator [](int). For example, in Program Listing 1•2,
“a” is a Vector of length = 8, with its value refers to a double array “d”. “b” is a reference Vector that has length
= 4, with the first index (“0”) of “b” pointing toward the fourth index (i.e., “off-set”—3 from the first position)
of “a”. Therefore, the selectors of a[3] and b[0] will both return a Scalar with the value “3.0”, and both return
Scalar objects pointing toward the same memory position (see Figure 1•5 in page 11).
The remaining symbolic operators for the Vector objects are the column-wise concatenation operators “&”
and “&&”, and row-wise concatenation operators “|” and “||”. The row-wise concatenations will return a Matrix

12 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

operator or function VectorSpace C++ library definition remark

symbolic operators
C0& operator &= ( ) assignment by reference
C0& operator = ( ) assignment by value
C0& operator [] (int) selector return a Scalar
C0 operator & ( ) const column concatenation
C0 operator && () const one-by-one column concatenation
C0 operator | ( ) const row concatenation return a Matrix
C0 operator || ( ) const one-by-one row concatenation return a Matrix

arithmetic operators
C0 operator ~ ( ) const transposed (into a row vector) return a Matrix
C0 operator + ( ) const positive (primary casting) unary
C0 operator - ( ) const negative unary
C0 operator + (const C0&) const addition
C0 operator - (const C0&) const subtraction
C0 operator * (const C0&) const multiplication by a scalar; scalar product of two Vectors
C0 operator %(const C0&) const tensor product of two Vectors return a Matrix
C0 operator / (const C0&) const division (by a Scalar or a Matrix only) return a Vector
C0& operator += (const C0&) replacement addition
C0& operator -= (const C0&) replacement subtraction
C0& operator *= (const C0&) replacement multiplication (by a Scalar only)
C0& operator /= (const C0&) replacement division (by a Scalar only)

logic operators
int operator == (const C0&) const equal TRUE == 1
int operator != (const C0&) const not equal FALSE == 0
int operator >= (const C0&) const greater or equal
int operator <= (const C0&) const less or equal
int operator > (const C0&) const greater
int operator < (const C0&) const less

functions
int length() const length of the Vector
double norm(int = 2) const 1-norm or 2-norm
double norm(const char*) const infinite-norm takes strings “infinity”, or “maximum”
C0 pow(int) const power (applied to each element of the Vector)
C0 sqrt(const C0&) const square root (applied to each element of the Vector)
C0 exp(const C0&) const exponent (applied to each element of the Vector)
C0 log(const C0&) const log (applied to each element of the Vector)
C0 sin(const C0&) const sin (applied to each element of the Vector)
C0 cos(const C0&) const cos (applied to each element of the Vector)
Partial listing of Vector object arithmetic operators, logic operators and functions.

Workbook of Applications in VectorSpace C++ Library 13


Chapter 1 Computational Linear Algebra Using C0 Type Objects

#include “include/vs.h”
int main() {
double d[8] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
C0 a(8, d),
a = {0.0, 1.0, ..., 7.0}T; a Vector
b(4, a, 3);
b; a reference Vector
cout << a[3] << endl;
cout << b[0] << endl;
a[3] = 3.0
return 0;
b[0] = a[3] = 3.0
}

Listing 1•5 Selector for Vector and reference Vector objects (project: “vector_examples”).

object, and we will discuss them in Section 1.1.3. The column-wise concatenation operations for two Scalar
objects have been introduced in page 9 of this section. For simplicity we first focus on the case for column-wise
concatenations of two Vector objects (see Figure 1•6). The column-wise-concatenation of two Vectors, by
“operator &(const C0&)” appends the second vector after the first as in the left-hand side of Figure 1•6. The
lengths of the two Vectors (say len1 and len2) do not have to be the same. The return Vector has the length of
(len1+len2). The one-by-one column-wise-concatenation, by “operator &&(const C0&), of two Vectors
requires that the two Vectors to have the same length (= len) as shown in the right-hand-side of Figure 1•6. The
values of the two Vectors are interlaced to form a new Vector of length = 2 len. If the two Vectors do not have the
same length, an C++ exception will be thrown. If not handled by a catch clause, the default behavior of C++
exception handling mechanism will cause your program to crash.
What if one of the operands for “&” or “&&” operators is a double or a Scalar object? (see left-hand-side of
Figure 1•7) For the column-concatenation operator “&”, the double or Scalar will be added in front or appended
after the Vector object (with length = len) according to the order of the operands. In Figure 1•7, “a” and “c” are
either a double or a Scalar. “a & b” adds the value 1.0 in front of the values of “b” to form a new Vector. “b & c”

1.0 1.0
1.0 1.0
a 2.0 3.0
2.0 2.0
c
3.0 5.0
3.0 3.0
7.0
4.0 4.0
4.0
5.0 5.0
5.0 2.0
b 6.0 6.0
6.0 4.0
d 7.0
7.0
7.0 6.0
8.0
a&b 8.0
c && d
Figure 1•6 Column-concatenation and one-by-one column concatenation of two Vectors.

14 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

d && e

a 1.0 1.0 0.0


d
2.0 0.0 1.0
3.0 a&b e && f 0.0
4.0 2.0
2.0 e
5.0 0.0
3.0 1.0
b 1.0 3.0
4.0 2.0 0.0 2.0
0.0
5.0 3.0 2.0 3.0
4.0
4.0 b&c 0.0 4.0

5.0 3.0
6.0 0.0 f
c 6.0
4.0 0.0
0.0

Figure 1•7 Column-concatenation “&” and one-by-one column concatenation “&&” of one
Scalar and one Vector.

appends the value of “6.0” after the values of “b” as shown. Both of their return objects are new Vectors with
length = len + 1.
For the one-by-one column-wise-concatenation operator “&&” with mixed-type operands, VectorSpace C++
library defines the return new Vector object to have the length = 2 len, with the value of the double or the Scalar
interlaced in front or after the values of the original Vector object. In the right-hand-side of Figure 1•7, “d” and
“f” are either a double or a Scalar. “e && f” and “d && e” interlace the value of “f” after and the value of “d” in
front of the values of the Vector “e”, according to the order of operands for the “&&” operator.
The explanations on two operators, the transpose by “~” and the tensor product by “%”, will be delayed until
the next section on the subject of Matrix, because they both return a Matrix object.
The member operator “*” of a Vector object may take a Scalar or a double as its argument. For example,

double a[4] = {1.0, 2.0, 3.0, 4.0}; // (project: “vector_examples”)


C0 v(4, a), s(2.0);
cout << (3.0*v) << endl; // {3.0, 6.0, 9.0, 12.0}T
cout << (v*s) << endl; // {2.0, 4.0, 6.0, 8.0}T

Workbook of Applications in VectorSpace C++ Library 15


Chapter 1 Computational Linear Algebra Using C0 Type Objects
in VectorSpace C++ Library this operation is defined to return a new Vector object with the value of every ele-
ment of the Vector multiplied by the value of the Scalar or the double. Applying an operator or a function to
every component of an object is consistent with the “distribution rule”. We say that the pre- or post- multiplica-
tion of a Vector with either a Scalar or a double obeys the distribution rule in VectorSpace C++ Library.
When the operator “*” of a Vector object takes another Vector object, with the same length, as its argument,
this operation is defined as the scalar inner product.

double a[4] = {1.0, 2.0, 3.0, 4.0}, // (project: “vector_examples”)


b[4] = {2.0, 4.0, 6.0, 8.0};
C0 v(4, a), w(4, b);
cout << (v * w) << endl; // 60

If the two Vectors do not have the same length, an exception will be thrown. Three different notations in mathe-
matics for scalar inner product are

Indicial Notation Matrix Algebra Tensor Algebra


vi wi T
v w v•w
TABLE 1•1. Three popular mathematical notations for scalar inner product.

In the indicial notation the scalar inner product is indicated by using repeated indices “i”. The repeated indices is
defined to imply summation—the summation convention. In the matrix algebra notation, the scalar inner product
is achieved by re-orienting the column-vector v into a row-vector vT, and the row vector multiplied by the col-
umn vector w of the same length gives a scalar result. This is consistent with a matrix of row-length = 1 multi-
plied by a column-vector gives a scalar. In these two notations the expressions for the scalar inner product are
defined implicitly using the multiplication operation. The operands need to be tempered with by either adding
repeated indices or imposing transpose to define the scalar inner product. In the tensor algebra, the two vectors
do not need to be manipulated. The “•” operator is defined as scalar inner product per se. The operator, in this
case, is defined to have the knowledge of how each component of the two vectors should be multiplied and
summed together. For the reason of inclusiveness for all three conventions, VectorSpace C++ Library defines the
“Vector::operator *(const C0&)” as

(~v) * w = v * w. Eq. 1•1

The left-hand-side fits in matrix algebra, and the right-hand-side fits in tensor algebra. The notations in tensor
algebra have the most uncluttered expressions, in which physical meaning of an expression is usually less dis-
tracted by the trivia.
The Vector::operator / (const C0&) accepts either a Scalar or a Matrix as its argument. When the argument is
a Scalar, or a double as well, the operator obeys the distribution rule as in the multiplication operator; i.e., the
division by the Scalar value is applied to every component of the Vector object. For the division operator to
accept an argument of a Matrix, let’s first look at the solution of a set of simultaneous equation in Matrix form as

Mv = w Eq. 1•2

16 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
v = M-1 w = w / M; Eq. 1•3

Where M is a matrix of size m × n, v is a vector of size n, and w is a vector of size m. In this case, the division of
a vector w by a Matrix M is naturally defined as the solution v of the simultaneous equation M v = w.
Replacement multiplication ( *=) and division ( /=) operators have semantic issues to be clarified. The
replacement operator means the l-valued object (the object in the left-hand-side) is to be operated on and then
reassigned to itself. However, if the argument taken for “*=” is a Vector, the replacement multiplication will
mean that it is a scalar inner product of the two Vectors and it will have to return a Scalar object instead of a Vec-
tor. This is inconsistent with the semantics of a replacement operator. You might also want to consider the “/=” to
accept a Matrix object, since it returns a Vector object. However, the original Vector object that calls the “/=”
operator is the right-had-side vector (w) of Eq. 1•2, and the return Vector is the vector (v) of Eq. 1•3. Although
they are both Vector objects, they are not the same vector as required by the semantics of replacement operation.
Exactly the same situation occurs for “*=” to have a Matrix object as argument. Consequently, in VectorSpace
C++ library, we define that both “*=” and “/=” can only take a Scalar or a double argument. If other types are
used, an exception will be thrown.
The logic operator, “Vector::operator ==(const C0&)”, define the condition for equal as two Vector objects
have the same length and values for every element.
The function Vector::length() returns the size of the Vector. In the box for Scalar on page 8, we did not men-
tion there is Scalar::length() function available, since it doesn’t make much sense to ask the length of a Scalar
object. Actually it exists. The return value is always 1. Another example is the transpose operator “~”. It is also
applicable to the Scalar object. It always returns a Scalar of the same value. In VectorSpace C++ library we call
this kind of functions backward compatible. The existence of the backward compatible functions enlarges the
function set for an object type. It is useful for a more dynamic programming method (see the example discussed
on page 41).
Functions “Vector::norm(int)” and “Vector::norm(const char*)” are defined to take either the values of 1 or 2
with the integer argument version, and to take either “infinity”, “Infinity”, “maximum” or “Maximum” with the
constant string version. In mathematics a p-norm or the Hölder norm of a vector v of length n is defined by1

v p = ( v1 p + v2 p + … + vn p ) 1 / p Eq. 1•4

Therefore, for p = 1, the 1-norm (or sum norm) is defined as

v 1 = v1 + v2 + … + v n Eq. 1•5

For p = 2, the 2-norm (or the Euclidean norm) is defined as

v 2 = v 12 + v 22 + … + v n2 Eq. 1•6

1. B.N. Datta, 1995, “Numerical Linear Algebra, and applications”, Brooks/Cole Publishing Company, p. 25.

Workbook of Applications in VectorSpace C++ Library 17


Chapter 1 Computational Linear Algebra Using C0 Type Objects
For, p = ∞, the infinite (or maximum norm), is

v ∞ = max v i Eq. 1•7

Free functions of the forms of “C0 norm(const C0&, int = 2)” and “C0 norm(const C0&, const char*)” can be
used for retrieving the norms of a vector “v”. A 2-norm is written as

“norm(v)”, or “norm(v, 2)”.

The omission of second argument in “norm(v)” implies that “2” is the default value for the second argument of
the function norm( ). A 1-norm is

norm(v, 1),

and infinite or maximum norm is written as

norm(v, “infinity”) = max(v, “Infinity”) = max(v, “maximum”) = norm(v, “Maximum”).

The norm functions are also backward compatible with a Scalar object, which simply returns the absolute value
of the Scalar.
For the remaining transcendental functions it is suffice to say that these functions perform the distribution
rule as discussed in page 16; i.e., applying these functions to a Vector results in returning a new Vector with the
values obtained by applying these functions to every element of the Vector. For example, applying a trigonomet-
ric function “sine” to a Vector “v” of length n is defined as a vector with every element of it the result of apply-
ing the trigonometric function “sine” to that element.

sin(v) = {sin(v1), sin(v2), ... , sin(vn) }T

1.1.3 Matrix
The data abstraction for a Matrix is represented as two integer numbers, “row-length” ∈ I and “column-
length” ∈ I, and a value-array mij ∈ . Since the “row-length” and “column-length” are variables, the memory
space for mij has to be managed dynamically (see Figure 1•8).
The “value-array” of mij is represented by an “array of pointers to double” (“m[0]” of type double*) of size
= “row-length” × “column-length”, while an “index-array” is an “array of pointer to pointer to double” (“m” of
type double**) to simulate “array of double” like syntax. The index of the value-array representing mij has the
following relation:

mij = m[i][j] = value-array[i × column-length + j],

where 0 ≤ i < “row-length”, and 0 ≤ j < “column-length”.


A simple memory management scheme of mij can be implemented for the Matrix object of C0 type. At the
time of construction,

18 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

“value-array”: m[0] = new double* [“row-length” * “column-length”];


m[0][0] m[0][1] . . .

double* m[0] m00 m01 m02 m03 m04 m10 m11 m12 m13 m14 m20 m21 m22 m23 m24 m30 m31 m32 m33 m34

“row-length” = 4, “column-length” = 5
m[0] m[1] m[2] m[3] m 00 m 01 m 02 m 03 m 04
double** m m0 m1 m2 m3
m 10 m 11 m 12 m 13 m 14
“index-array”: m = new double* [“row-length”]; mij =
m 20 m 21 m 22 m 23 m 24
for(int i = 1; i < “row-length”; i++)
m[i] =m[i-1] + “column_length”; m 30 m 31 m 32 m 33 m 34

Figure 1•8 Memory space management of a Matrix.

double** m;
m = new double* [row_length]; // index-array instantiation
m[0] = new double [row_lengh * column_length]; // value-array instantiation
for(int i = 1; i < row_length; i++) // setup index-array to point to the
m[i] = m[i-1] + column_length; // beginning of each row

Notice that according to the pointer arithmetic in C language, the semantics of “m[i]” can be explained in two
steps: (1) the braces after m performs casting as m[] ≡ (double*) m, and (2) the index “i” in the braces indicates
the off-set from the first position as m[i] ≡ ((double*) m)+i. At the time of destruction,

if(m && m[0]) delete [] m[0]; // release value-array


if(m) delete [] m; // release index-array

The purpose of this introduction on memory management is not asking you to master the internal working of the
VectorSpace C++ Library. The data abstraction has wrapped all these details of how to maintain the “value-
array” and the “index-array” in the constructors and destructors of each class. However, from user’s perspective
we need to understand that if we are passing an array by reference to an object, it will always be passed as a dou-
ble*—the value-array, and the value array should always be thought of as an one dimensional array, exactly as
how it is organized in memory. (see double* array m[0] in Figure 1•8). This general concept remains valid for
even more complicated classes in VectorSpace C++ Library.
We have defined that a “row-vector”, vT, where v is a (column-) vector, is represented as a Matrix of “row-
length” = 1, in VectorSpace C++ Library. Now we recall the two row-wise concatenation operators left undefined
on page 7. The row-wise-concatenation operator “|” is used for two Scalars as (project: “matrix_examples”)

C0 a(0.0), b(1.0); // two Scalars of type C0


C0 c = a | b; // row-concatenation returns a “row-vector”
cout << c << endl; // {{0.0, 1.0}}; a Matrix of row-length = 1

Workbook of Applications in VectorSpace C++ Library 19


Chapter 1 Computational Linear Algebra Using C0 Type Objects
The return value is a Matrix of row-length = 1 with the content as {{0.0, 1.0}}. The double braces are used for
expressing the content of a Matrix. Using “||” instead of “|” makes no difference in the case with two Scalar
objects as operands. The row-wise concatenation operators for two Vectors are defined accordingly, as (project:
“matrix_examples”)

double d1[4] = {0.0, 1.0, 2.0, 3.0},


d2[4] = {10.0, 11.0, 12.0, 13.0};
C0 a(4, d1), b(4, d2);
C0 c = a | b;
cout << c << endl;

The output will be a Matrix (expressed in multi-dimensional array format in C language)

{ {0.0, 10.0},
{1.0, 11.0},
{2.0, 12.0},
{3.0, 13.0} }

Again, using “||” instead of “|” for two Vectors makes no difference. For mixed type operands with one Scalar
and one Vector the row-wise concatenation operator “|” and the one-by-one row-wise concatenation operator “||”
are defined completely parallel to “&” and “&&” as illustrated in Figure 1•7. The only difference is the source
and the return (column) Vectors are now a vector represented as a Matrix of row-length = 1.
For operator “%” to take two Vectors, tensor product is written in VectorSpace C++ Library as

double a[3] = {1.0, 2.0, 3.0}, // (project: “matrix_examples”)


b[4] = {4.0, 5.0, 6.0, 7.0};
C0 v(3, a), w(4, b);
cout << (v % w) << endl;

The result of the tensor product is called a dyad, and is represented as a Matrix object of C0 type as

{ {4.0, 5.0, 6.0, 7.0 }, {{ v0w0, v0w1, v0w2, v0w3},


{8.0, 10.0, 12.0, 14.0 }, or, { v1w0, v1w1, v1w2, v1w3},
{12.0, 15.0, 18.0, 21.0 } } { v2w0, v2w1, v2w2, v2w3} }

The two Vector objects in the example above, do not have to have the same length. Three different popular nota-
tions for the tensor product are

Indicial Notation Matrix Algebra Tensor Algebra


T
vi wj vw v⊗ w
TABLE 1•2. Three popular mathematical notations for tensor product.

20 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
Parallel to the definitions for scalar inner product as in Eq. 1•1 on page 16, the tensor product is written in two
ways

v * (~w) = v % w Eq. 1•8

The left-hand-side is consistent with “v wT” in the matrix algebra, and the right-hand-side is consistent with
“v ⊗ w” in the tensor algebra.

Constructors
The examples of dedicated constructors for the Matrix can be written as in Program Listing 1•2. The Matrix
object of C0 type is constructed by defining its “row-length” = 4 and “column-length” = 8. The double* array
m1[0] is the value array passed as a reference to the Matrix “a”. Notice the semantics of m1[0] = ((double*)
m1)+0. Just as in the dedicated constructor for Vector object, the Matrix can be constructed to have its own mem-
ory by passing an argument of “(double*)0”, a null pointer cast to a pointer of double, to the third argument of the
dedicated constructor as

C0 a(4, 8, (double*)0);

However, in doing so all the elements in the Matrix object will be initialized to have the value “0.0”.

The reference Matrix can be constructed by the dedicated constructor of Matrix class as (see Figure 1•9)

C0 b(2, 3, a, 1, 2);

where the first two arguments say that the reference Matrix “b” has “row-length” = 2 and “column-length” = 3.
The third argument is the referenced Matrix “a”, and the last two arguments are the starting indices which are the
second row-index “1” and the third column-index “2” in “a”. Again, the unary positive operator of Matrix “+”
serves the function of primary casting. For example, “+b” constructs an independent temporary Matrix object of

#include “include/vs.h”
int main() {
double m1[4][8] = { { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0},
{10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0},
{20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0},
{30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0} };
C0 a( 4, 8, m1[0]); { { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0},
cout << a << endl; {10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0},
{20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0},
{30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0} };
C0 b(2, 3, a, 1, 2);
cout << b << endl; { {12.0, 13.0, 14.0},
return 0;
{22.0, 23.0, 24.0} };
}

Listing 1•6 Dedicated constructors of Matrix objects (project: “matrix_examples”).

Workbook of Applications in VectorSpace C++ Library 21


Chapter 1 Computational Linear Algebra Using C0 Type Objects

a: 4 x 8
a12 a13 a14 +b

a22 a23 a24

b00 b01 b02 b: 2 x 3


b10 b11 b12

Figure 1•9 Reference Matrix “b” and referenced matrix “a”. a12 is the starting element.
C0 type. The temporary object “+b” will be instantiated with the size of “b”, and its value is initialized to that of
“b”.
The constant strings for Matrix virtual constructors (use macro definition “MATRIX”) and autonomous vir-
tual constructors are shown in the following box

virtual constructor string VectorSpace C++ library definition priority

by reference
“C0&” C0 type Matrix —
“C0*” a pointer to C0 type Matrix —
“int, int, double*” row-length, column-length, double* != 0, 16
“int, int, double*, int, int” row-length, column-length, double* != 0,
memory-row-length, memory-column-length 17

by value
“int, int” row-length, column-length 15
“int, int, double*” row-length, column-length, double* = 0, 16
“int, int, const double*” row-length, column-length, double*, 18
“int, const C0*” length, C0* of a Vector 19
“const C0*” C0* type of a Matrix* —
“int, int, C0&, int, int” row-length, column-length, C0&,
starting row-index, starting column-index 20
(the only one for reference Matrix)
Strings in C0 virtual constructor for Matrix object.

Operators and Functions


The symbolic operators for Matrix have two assignment operators, three selectors and four concatenation
operators (see box in page 23.).

22 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
operator or function VectorSpace C++ library definition remark

symbolic operators
C0& operator &= ( ) assignment by reference
C0& operator = ( ) assignment by value
C0& operator [ ] (int) row selector return a Vector
C0& operator( )(int) column selector return a Vector
C0& operator ( )(int, int) element selector return a Scalar
C0 operator & ( ) const column concatenation
C0 operator && () const one-by-one column concatenation
C0 operator | ( ) const row concatenation
C0 operator || ( ) const one-by-one row concatenation

arithmetic operators
C0 operator + ( ) const positive (primary casting) unary
C0 operator - ( ) const negative unary
C0 operator + (const C0&) const addition
C0 operator - (const C0&) const subtraction
C0 operator * (const C0&) const multiplication
C0 operator / (const C0&) const division (by a Scalar or a Matrix only)
C0& operator += (const C0&) replacement addition
C0& operator -= (const C0&) replacement subtraction
C0& operator *= (const C0&) replacement multiplication (by a Scalar only)
C0& operator /= (const C0&) replacement division (by a Scalar only)

matrix algebra operators


C0 operator ~ ( ) const transpose
C0 operator !( ) const matrix decomposition

logic operators
int operator == (const C0&) const equal TRUE == 1
int operator != (const C0&) const not equal FALSE == 0
int operator >= (const C0&) const greater or equal
int operator <= (const C0&) const less or equal
int operator > (const C0&) const greater
int operator < (const C0&) const less

functions
int row_length() const row-length of the Matrix
int col_length() const column-length of the Matrix
double norm(in) const 1 (maximum column-sum)-norm or 2 (spectral)- norm
double norm(const char*) const “infinity” (max row-sum),“Forbenisu”(Forbenius-norm)

Partial listing of Matrix object arithmetic operators, logic operators and functions (continued on next page).

Workbook of Applications in VectorSpace C++ Library 23


Chapter 1 Computational Linear Algebra Using C0 Type Objects
operator or function VectorSpace C++ library definition remark

functions
C0 pow(int) const power (applied to each element of the Matrix)
C0 sqrt(const C0&) const square root (applied to each element of the Matrix)
C0 exp(const C0&) const exponent (applied to each element of the Matrix)
C0 log(const C0&) const log (applied to each element of the Matrix))
C0 sin(const C0&) const sin (applied to each element of the Matrix)
C0 cos(const C0&) const cos (applied to each element of the Matrix

matrix algebra fucntions


int rank() const rank of a Matrix
C0 identity() const identity Matrix
C0 cond() const condition number of a Matrix
C0 inverse() const inverse of a Matrix
C0 det() const determinant of a Matrix
Partial listing of Matrix object arithmetic operators, logic operators and functions.

For the two assignment operators, which are applied with two Matrices, the results are intuitively straight-
forward and the behavior is similar to that for two Scalars or two Vectors. Again, complexity increases when dif-
ferent types are taken as their arguments (see Program Listing 1•2). If “Matrix::operator = (const C0&)” takes a
Scalar, it is defined as to have all the elements in the Matrix set to the value of the Scalar. For example,
C0 a(2, 3, (double*)0),
b(3.0);
a = b;
#include “include/vs.h”
int main() {
double d[2] = {0.0, 1.0}; b = 3.0
C0 a(2, 3, (double*)0), c = {0.0, 1.0} T
b(3.0);
c(2, d); a=
a = b;
{ { 3.0, 3.0, 3.0 },
cout << a << endl;
{ 3.0, 3.0, 3.0 } }
a = c;
a=
cout << a << endl;
{ { 0.0, 0.0, 0.0},
{1.0, 1.0, 1.0} }
a &= b; a = 3.0; a Scalar
cout << a << endl;

a &= c;
cout << a << endl; a = {0.0, 1.0} T; a Vector
return 0;
}

Listing 1•7 Assignment operators with different types of operand (project: “matrix_examples”).

24 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
All the elements in “a”, totally six of them, will be assigned the value of “3.0” that the Scalar “b” has. This defi-
nition is consistent with that in “Vector::operator = (const C0&)” taking a Scalar argument. What is new is when
the “Matrix::operator = (const C0&)” takes an argument of type Vector. We define that every column of the
Matrix “a” is assigned with the values of Vector “b”. Therefore, the “row-length” of Matrix “a” and the “length”
of Vector “b” should be the same. Otherwise an exception will be thrown. The assignment by reference
“Matrix::operator &=(const C0&)” is defined exactly the same as that of Vector’s, and is very straight-forward.
The Matrix “a” upon being re-assigning to other types by reference just changes both the type and value of “a” as
in Program Listing 1•2 for the Vector objects. We say that the label “a” is peeled off from the original Matrix
object and re-attached to whatever kinds of object it is referring to. Of course garbage collection discussed on
page 6 will be activated to see if the original Matrix object must go down the drain.
The row selector uses the Matrix::operator [](int), and the column selector uses the Matrix::operator ( )(int).
Both of these two operators return a Vector object. For example, (see Figure 1•10)

row selector: a[1] a: 3x4


0.0 1.0 2.0 3.0 element selector:
12.0 a(2, 1)
10.0
10.0 11.0 12.0 13.0 = a[1][2] = a(2)[1]
11.0
20.0 21.0 22.0 23.0 2.0
12.0
12.0 column selector: a(2)
13.0
22.0

Figure 1•10 Row selector, column selector and element selector.

double d[3][4] = { { 0.0, 1.0, 2.0, 3.0}, // (project: “matrix_examples”)


{10.0, 11.0, 12.0, 13.0},
{20.0, 21.0, 22.0, 23.0} };
C0 a(3, 4, d[0]);
cout << a[1] << endl; // row selector; {10.0, 11.0, 12.0, 13.0}T
cout << a(2) << endl; // column selector{ 2.0, 12.0, 22.0} T
cout << a[1][2] << endl; // 12.0
cout << a(2)[1] << endl; // 12.0
cout << a(2, 1) << endl; // element selector; 12.0

The returned Vectors for row and column selectors can again be applying “Vector::operator [](int)”, the selector
of the Vector, to access a single element. However, for single element access, we can by-pass the row and column
selectors to use element selector, “Matrix::operator ( )(int, int)”, directly with two int arguments for higher effi-
ciency. The element selector has the semantics close to Fortran language. So it is also called the Fortran-style
selector.
The result of the row-wise-concatenation operators “&” and “&&”, when two Matrix objects are taken as
arguments, are intuitively comprehensible (see Figure 1•11). Their definitions are consistent with those for the
two Scalars or two Vectors. We notice that, for the regular row-wise-concatenation operator, the “column-
lengths” of the two concatenated Matrices must be the same. For the one-by-one row-wise-concatenation opera-

Workbook of Applications in VectorSpace C++ Library 25


Chapter 1 Computational Linear Algebra Using C0 Type Objects

1 2 3
1 2 3
1 2 3 4 5 6
1 2 3 1 2 3 1 2 3 1 2 3
& 4 5 6 = 1 2 3 && =
4 5 6 4 5 6 4 5 6 4 5 6
7 8 9 4 5 6
4 5 6
7 8 9

Figure 1•11 Row concatenation and one-by-one row concatenation of two Matrices.

1 2 3
1 2 3
1 2 3 1 2 3 1 1 1
& 1 = 4 5 6 && 1 =
4 5 6 4 5 6 4 5 6
1 1 1
1 1 1

1 2 3 1 2 3
1 4 5 6 1 2 3 1 1 1 1
1 2 3 && =
& 2 = 1 1 1 5 6
4 5 6 2 4
4 5 6
3 2 2 2 2 2 2
3 3 3

Figure 1•12 Row-wise concatenation and one-by-one row concatenation with Scalar or Vector
tor, both “row-length” and “column-length” must be the same. We also define the row-wise-concatenation of a
matrix with a Scalar or a Vector consistently as those of previous ones (see Figure 1•11). Row-wise-concatena-
tion with a Scalar is to have every column of the Matrix concatenates row-wisely with the Scalar. One-by-one
row-wise-concatenation has every element of the Matrix concatenates row-wisely with the Scalar. Row concate-
nation of the matrix with a Vector is to have every column of the Matrix concatenates row-wisely with the Vec-
tor, and one-by-one row-wise-concatenation with a Vector has every element of the Matrix concatenates row-
wisely with the Vector. In the last case the “row-length” of the Matrix and the “length” of the Vector should be
the same.
The behaviors of the column-wise-concatenation operators “|” and “||” for two Matrices are also straight-for-
ward (see Figure 1•11). Similarly, for the column concatenation operators the “row-length” must be the same.
For the one-by-one column-wise-concatenation operator both the “row-length” and “column length” of the two
matrices must be the same. Column-wise-concatenation of a matrix with a Scalar or a Vector can be defined
accordingly (see Figure 1•11). The column-wise-concatenation with a Scalar is defined as to have every row of

26 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

1 2 1 2 3 1 2 1 2 3 1 2 1 2 1 1 2 2
3 4 | 4 5 6 = 3 4 4 5 6 3 4 || 3 4 = 3 3 4 4
5 6 7 8 9 5 6 7 8 9 5 6 5 6 5 5 6 6

Figure 1•13 Column concatenation and one-by-one column concatenation of two Matrices.

1 2 1 2 1 1 2 1 1 2 1
3 4 | 1 = 3 4 1 3 4 || 1 = 3 1 4 1
5 6 5 6 1 5 6 5 1 6 1

1 2 1 1 2 1 1 2 1 1 1 2 1
3 4 | 2 = 3 4 2 3 4 || 2 = 3 2 4 2
5 6 3 5 6 3 5 6 3 5 3 6 3

Figure 1•14 Column concatenation and one-by-one column concatenation of two Matrices.
the Matrix concatenates column-wisely with the Scalar. The one-by-one column-wise-concatenation is defined
as for every element of the Matrix column-wise concatenates with the Scalar. The column-wise-concatenation
and one-by-one column-wise-concatenation with a Vector is also intuitive, but the “row-lengths” of the Vector
and the Matrix must be the same.
The positive unary operator for the Matrix can be used to perform the primary casting for a reference Matrix
to a Matrix that owns its memory space. The addition and subtraction operators, “Matrix::operator +(const
C0&)” and “Matrix::operator -(const C0&)”, need some explanations. If they take another Matrix as their argu-
ment, this Matrix must has the same row-length and column-length with the original Matrix. If the argument
taken is a Scalar (or equivalently a double), the addition or substraction is defined, with the distribution rule, to
have every element of the Matrix plus or minus the Scalar. If the argument is a Vector, the distribution rule in
such a case defines that every column of the Matrix is to plus or minus the Vector. In this case, the “row-length”
of the Matrix and the “length” of the Vector should be the same, otherwise, an exception will be thrown.
“Matrix::operator *(const C0&)” can take a Scalar, a Vector or another Matrix. For a Scalar (or a double)
argument the multiplication obeys the distribution rule. As defined earlier, this means the Scalar value will be
multiplied with every element of the Matrix. For a Vector argument, according to usual mathematical definition,
the Matrix and its argument Vector should have compatible lengths as

Workbook of Applications in VectorSpace C++ Library 27


Chapter 1 Computational Linear Algebra Using C0 Type Objects
M * v = w,

where dim M = m × n, dim v = n ( × 1), and dim w = m ( × 1) as required in matrix algebra. In Eq. 1•1 of
page 16, the scalar inner product of two vectors (v and w of the same length n) is written with VectorSpace C++
library as

(~v) * w = v * w

On the left-hand-side, the transpose of a Vector v is a Matrix of row-length = 1. Now, let’s see the dimension of
this expression is dim vT = {1 × n } and dim w = { n × 1 }. Therefore it is consistent with the definition of a
Matrix multiplies with a Vector. For “*” operator to take another Matrix object, the length compatible require-
ment is

M*N=L

where dim M = m × l, dim N = l × n, and therefore dim L = m × n. In Eq. 1•8 in page 21, the dyad obtained
from the tensor product of two Vectors is written as

v * (~w) = v % w

the transpose of the Vector w has dim (~w) = 1 × m. The Vector v has dim v = n × 1. The tensor product defini-
tion therefore is consistent with multiplication in matrix algebra where the dyad has dim (v*(~w)) = n × m.
The division operator “/” takes either a Scalar or a Matrix. For the case of a Matrix it is defined similar to the
process of finding solution of simultaneous equations with multiple right-hand-side vectors. Therefore the row-
length and column-length of the both Matrices must be the same. The replacement division, “/=”, and replace-
ment multiplication, “*=”, again only take a Scalar object in order to be consistent with the semantics of the
replacement operators.
Logic operators “==”, is defined to have same “row-length”, “column-length” and values for every element.
Four norms of a Matrix are defined in VectorSpace C++ library. The maximum column sum matrix norm is
denoted with subscript “1” as1

m–1

A 1 = max
0≤j<n ∑ a ij
Eq. 1•9
i=0

The “maximum row sum” matrix norm is denoted with subscript “∞ ” as

n–1

A ∞ = max
0≤i<m
∑ a ij Eq. 1•10
j=0

1. B.N. Datta, 1995, “Numerical Linear Algebra, and applications” Brooks/Cole Publishing Company, p. 26-27.

28 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
The spectral norm is denoted with subscript “2” as

A 2 = maximum eigenvalue of A T A Eq. 1•11

The Frobenius norm is the one that is most consistent with the 2-norm (Euclidean norm) of a Vector. It is denoted
with a subscript “F” as

n – 1m – 1
A F = ∑ ∑ a ij 2 Eq. 1•12
i= 0j =0

The maximum column norm subscript “1” and spectral norm subscript “2” for a Matrix “m” are called by func-
tions “norm(m, 1)” and “norm(m, 2)”, respectively. The maximum row sum norm subscript “ ∞ ” and Frobenius
norm subscript “F” are called by functions “norm(m, “infinity”)” (= “norm(m, “Infinity”)”), and “norm(m,
“frobenius”)” (= “norm(m, “Frobenius”)”), respectively.
The transcendental functions for the Matrix obey the distribution rule similar to those for the Vector. For
example, for a Matrix “m” of row-length “m” and column-length “n”,

sqrt ( m 00 ) sqrt ( m 01 ) … … sqrt ( m 0n )


sqrt ( m 10 ) sqrt ( m 20 ) … … …
sqrt ( m ) = … … …… … Eq. 1•13
… … …… …
sqrt ( m m0 ) … … … sqrt ( m mn )

The function, “sqrt()”, has been applied to every element of the Matrix “m”.

1.1.4 Matrix Algebra


Many matrix decomposition methods for (1) solution of simultaneous equations, and (2) eigenvalue problems
for symmetrical matrix are introduced in this section.

LU Decomposition
For the solutions of simultaneous equations A x = b, a square matrix, A, can be decomposed first into an
upper triangular matrix (U) and a lower triangular matrix (L), by the LU decomposition, as

A x = (L U) x = b Eq. 1•14

We define

y=Ux Eq. 1•15

and substitute Eq. 1•15 into Eq. 1•14, and we obtain

Workbook of Applications in VectorSpace C++ Library 29


Chapter 1 Computational Linear Algebra Using C0 Type Objects
Ly=b Eq. 1•16

The solutions of triangular matrices in the form of Eq. 1•15 and Eq. 1•16 are known to be particularly easy; the
solution steps begin either from the first or the last equation that has only one unknown. Then an equation next
to the one just solved will have one more new unknown to be solved for. The last step is repeated until all the
unknowns are solved. Therefore the solution of the original system can be performed in three steps: (1) perform
LU decomposition, (2) solve triangular system for y in Eq. 1•16. This step is known as forward elimination (or
forward substitution), (3) substitute y into Eq. 1•15 and solve for x from the triangular system. This last step is
known as back substitution.
In VectorSpace C++ Library, the LU decomposition can be called by using matrix decomposition operator
“Matrix::operator !()”. And, the forward elimination and back substitution are performed by multiplying the
decomposed matrix with the right-hand-side vector with the “LU::operator *(const C0&)”. For example,

double d[3][3] = { { 0.0, 1.0, 1.0 }, // (project: “matrix_algebra”)


{ 1.0, 2.0, 3.0 },
{ 1.0, 1.0, 1.0} },
v[3] = { 2.0, 6.0, 3.0 };
C0 A(3, 3, d[0]), b(3, v); // Matrix “A” and Vector “b”
C0 x = (!A)*b; // decomposed then forward/back substitutions
cout << x << endl; // { 1.0, 1.0, 1.0 }T

You can explicitly form an LU decomposed matrix object by calling the LU constructor as

LU a(A); // LU decomposition
C0 x = a * b; // “*” performs forward and back substitutions

or in short just

C0 x = LU(A)*b;

The LU decomposition is the default matrix solver in VectorSpace C++ Library. We can achieve a neater expres-
sion by considering the mathematical expressions as in

Ax=b ⇒ x = A-1 b = b / A

Therefore, the C++ codes can be written as

C0 x = b / A;

The “Vector::operator /(const C0&)”, taking a Matrix object of C0 type as its argument, is invoked with the def-
inition as in Eq. 1•3 of page 17. Or equivalently,

C0 x = A.inverse() * b; // x = A-1 b
or even

30 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
C0 I = A.indentity();
C0 x = (I / A) * b; // x = (I/A) b

The determinant is computed by the member function call “det()”, the rank of the matrix by “rank()”, and the
condition number by “cond()”.
For the LU-decomposition without pivoting, the decomposition algorithm is unstable. Some elements in the
reduced matrices can grow arbitrarily large that the information in the original data can be corrupted. The default
pivoting method for the LU-decomposition is the partial-pivoting (or row-pivoting). The algorithm can be even
more stabilized if the complete-pivoting is used. The default behavior can be over-written as

Matrix::Pivoting_Method = Matrix::Complete_Pivoting;
C0 x = b / A;
Matrix::Pivoting_Method = Matrix::Partial_Pivoting; // reset to default pivoting

The change made to the pivoting method in the above will affect not only the division operator “/”, but also all
invoking methods of the matrix solver used in the above examples.

Cholesky Decomposition
The idiosyncrasy of matrix computation is that there are different methods for specific kinds of matrices. For
a (square) symmetric matrix, the Cholesky decomposition is often used. The Cholesky decomposition can be
written as

A = L D LT Eq. 1•17

where D is the diagonal matrix. The Cholesky decomposition is two times faster than the LU decomposition.
However, the plain Cholesky decomposition works only for a positive definite symmetric matrix ( ∀ λ i > 0 ,
where λi are eigenvalues). For a positive semi-definite symmetric matrix ( ∀λ i ≥ 0 ), the Cholesky decomposition
with diagonal-pivoting is necessary to keep the algorithm stable. For an indefinite symmetric matrix, one can just
ignore its being symmetric, therefore, the LU-decomposition with complete-pivoting is a must. In the context of
the modified Newton method in optimization problem (see page 129 in Chapter 2), the negative curvatures of an
indefinite symmetric matrix (Hessian matrix; the second partial derivatives of the objective function) can be
modified to have positive curvatures by using the modified Cholesky decomposition.
The examples of using the Cholesky decomposition in VectorSpace C++ Library are1

double m[3][3] = { { 16.0,4.0, 8.0}, // (project: “matrix_algebra”)


{ 4.0, 5.0, -4.0}, // data for a symmetric matrix
{ 8.0, -4.0, 22.0} },
v[3] = {5.0, 1.0, 3.0 };// data for the right-hand-side vector
C0 A(3, 3, m[0]), b(3, v);

1. example data from A. Jennings and J.J. McKeown, 1992, “Matrix computation”, 2nd ed., John Wiley & Sons, New York,
p.100-101.

Workbook of Applications in VectorSpace C++ Library 31


Chapter 1 Computational Linear Algebra Using C0 Type Objects
Cholesky a(A); // Cholesky decomposition
C0 x = a*b; // forward/back substitutions
cout << x << endl;// {0.315972, -0.0416667, 0.0138889}T

“Cholesky a(A);” calls the Cholesky constructor explicitly. Operators “!” or “/” or function “inverse()” can be
used by setting the matrix solver from default LU decomposition. For example,

Matrix::Decomposition_Method = Matrix::Cholesky_Decomposition;
C0 x = b/A;
Matrix::Decomposition_Method = Matrix::LU_Decomposition; // reset back to default

If you are dealing with a positive semi-definite symmetric matrix the diagonal pivoting can be invoked by set-
ting

Matrix::Pivoting_Method = Matrix::Diagonal_Pivoting;

The modified Cholesky decomposition, for the symmetric indefinite Hessian in optimization, can be invoked
with a second argument indicating a small tolerance value, δ. A critical example is shown in the following1

1 double m[3][3] = { { 1.0, 1.0, 2.0}, // (project: “matrix_algebra”)


2 { 1.0, 1.0+1.e-20, 3.0}, // data for a symmetric matrix
3 { 2.0, 3.0, 1.0} },
4 v[3] = {4.0, 5.0+1.e-20, 6.0 }; // x ≡ {1.0, 1.0, 1.0}T for A x = b
5 C0 A(3, 3, m[0]), b(3, v);
6 Cholesky a(A); // Cholesky decomposition
7 C0 x = a*b; // produce garbage when ill-conditioned
8 cout << x << cout; // {-1.44e82, 1.44e82, -2.4e+61}T
9 ⇒ Cholesky a_bar(A, 1.e-20); // A-1 : modified Cholesky decomposition
10 C0 x_bar = a_bar*b; // x = A-1 b forward/back substitutions
11 cout << x_bar << endl; // x = {0.0668383, -0.152525, 1.95023}T
12 for(int i = 0; i < 3; i++) // add diagonal increase on the left-hand-side
13 b[i] +=a_bar.diagonal_increase(i); // to the right-hand-side(x ≡ {1.0, 1.0, 1.0}T)
14 x = a_bar * b; // x = A-1 b, check consistency
15 cout << x << endl; // x = {1.0, 1.0, 1.0}T

In line 9, the second parameter δ = 1.e-20 in the constructor “Cholesky a_bar(A, 1.e-20);” specifies the lower
bound (from zero) for the modified eigenvalues. The original matrix has the eigenvalues of 5.1131, -2.2019, and
0.0888, which is clearly indefinite. The Cholesky decomposition of the matrix gives

1. data from P.E. Gill, W. Murray, and M.H. Wright, 1981, “Practical optimization”, Academic Press Limited, San Diego,
pp. 109-111.

32 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

1.0 0.0 0.0 1.0 0.0 0.0


L = 1.0 1.0 0.0 and, D = 0.0 10– 20 0.0 Eq. 1•18
2.0 10 20 1.0 20
0.0 0.0 – ( 3.0 + 10 )

The modified Cholesky decomposition gives

1.0 0.0 0.0 3.771 0.0 0.0


L = 0.2652 1.0 0.0 and, D = 0.0 5.750 0.0 Eq. 1•19
0.5303 0.4295 1.0 0.0 0.0 1.121

Now all the diagonal elements have been modified to positive numbers. The difference of the original and the
modified matrix has the Frobenius norm of only 6.154. The amount being increased on the diagonals by the mod-
ified Cholesky decomposition can be obtained by calling “Cholesky::diagonal_increase(int i)”, the argument
specifies the off-set from the first element of the diagonal. In this case, the increased amount of the three diago-
nals are {2.771, 5.016, 2.243}.

QR Decomposition
Any matrix A can be written as

A= QR Eq. 1•20

where R is a upper triangular matrix and Q is an orthogonal matrix. An orthogonal tensor Q satisfies the neces-
sary and sufficient conditions of QTQ = I, and det Q = 1. Eq. 1•20 is called the QR decomposition.
For a square matrix A, the simultaneous equations A x = b can be solved by the QR decomposition as

A x = (QR) x = b

And, set

y = QT b Eq. 1•21

Then, solve the triangular system of equations

Rx = y Eq. 1•22

The QR decomposition for a square matrix, if carried out by Householder transformation, is two times more
expensive than the LU decomposition. The QR decomposition is always stable. Recall that the LU decomposi-
tion is stable only with complete pivoting.
Using QR decomposition with VectorSpace C++ Library is simple. For example,

Workbook of Applications in VectorSpace C++ Library 33


Chapter 1 Computational Linear Algebra Using C0 Type Objects
double d[3][3] = { { 0.0, 1.0, 1.0 }, // (project: “matrix_algebra”)
{ 1.0, 2.0, 3.0 },
{ 1.0, 1.0, 1.0} },
v[3] = { 2.0, 6.0, 3.0 };
C0 A(3, 3, d[0]), b(3, v); // Matrix “A” and Vector “b”
QR a(A); // QR decomposition
C0 x = a*b; // “*” is back substitution of Eq. 1•22
cout << x << endl; // { 1.0, 1.0, 1.0 }T

As in the case of the Cholesky decomposition, instead of explicitly calling the QR constructor, you can use oper-
ators “!” and “/” or function “inverse()” by setting the default matrix solver to the QR decomposition as

Matrix::Decomposition_Method = Matrix::QR_Decomposition;
C0 x = b / A; // implicitly call QR decomposition

The member functions of the QR class “QR::Q()” and “QR::R()” give clearly what they say they are.
For a rectangular matrix A of size m × n ( m ≥ n ) with full rank, the QR decomposition produces

R1
Q = Q1 Q 2 , and R = Eq. 1•23
0

Q is an m × m matrix and R is a m × n matrix, where Q1 with n vectors form the orthonormal basis of the range
space of A, and Q2 with (m-n) vectors form the orthonormal basis of null space of AT. R1 is a n × n matrix and the
lower part of the R matrix is a null matrix of size (m-n) × n.
In the overdetermined full rank least squares problem, the residual of a rectangular matrix A with right-hand-
side vector, b, and the solution, x, is written as

r=Ax-b Eq. 1•24

The square of residual norm is

n
m  m 
 A x –b  A x  –b
r 2
2 = T
r r = (Ax – b ) (Ax – b ) =
T
∑  ∑ ij j i  ∑ ik k i
i  j   k 
m m n m n n

= ∑ ∑ xj xk ∑ Aij Aik – 2 ∑ xj ∑ Aij b i + ∑ b i bi Eq. 1•25


j k i j i i

The least squares means to “minimize the sum of squares (the residual norm)”. Taking derivatives with respect
to x, for the three terms in the last line, gives
m m n
 m m n m n
∂ 
first term: -------- ∑ ∑ x j x k ∑ A ij A ik = ∑ ∑ ( δjq xk + δkq xj ) ∑ Aij Aik = 2 ∑ x k ∑ A iq A ik
∂x q  
 j k i  j k i k i

34 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

m n
 m n n
∂  
second term: – 2  ∑ x j ∑ A ij b i = –2 ∑ δ jq ∑ A ij b i = – 2 ∑ A iq b i
--------
∂x q
 j i  j i i

 n 

third term: --------  ∑ b i b i = 0
∂x q  
 i 
Add three terms together and set the derivatives to zero for the purpose of minimization, we get

m n n
∂ r 22
------------- = 2 ∑ x k ∑ A iq A ik – 2 ∑ A iq b i = 0 Eq. 1•26
∂x
k i i

Eq. 1•26 can be expressed in matrix form as

AT A x - AT b = 0 Eq. 1•27

This equation is known as the normal equations. The solution can be obtained from Eq. 1•27 as

x = [AT A]-1AT b = A-g b Eq. 1•28

where A-g = [AT A]-1AT is called the generalized inverse. On the other hand the projection of vector b (of size m)
into a lower dimensional range space of A (of size n, with m ≥ n ) gives the minimum length of the Euclidean
norm of r (= A x - b, see Figure 1•15)

b
r m = 3, n = 2

Ax
Range(A)

Figure 1•15 Projection into range space of A gives the minimum length of r.

Since r and the range of A are perpendicular to each other, every column of A is orthogonal to r. Therefore,

AT r = 0 (orthogonal property)

Substituting Eq. 1•24 for r ( = A x - b), we get

AT (A x - b) = AT A x - AT b = 0

Workbook of Applications in VectorSpace C++ Library 35


Chapter 1 Computational Linear Algebra Using C0 Type Objects
This is the geometrical interpretation of the normal equations as obtained from range space projection1.
One way to tackle the least squares problem is to obtain first “ATA ” and “AT b” then solve the system of
equations. Since “ATA ” is symmetrical, we can use Cholesky decomposition for efficiency. However, the pro-
cess to get “ATA ” is sometimes problematic. Round-off errors accumulated in the multiplication of the two
matrices, “ATA ”, may corrupt the information in the original A matrix.”
We can remedy this by using the QR decomposition for the least squares solution of A. Consider the squares
of residual norm as

r 2 = Ax – b 2 Eq. 1•29
2 2

An orthogonal transformation of Eq. 1•29 with QT should not change the length of the residual as

r 2 = Ax – b 2 = QTAx – QTb 2 Eq. 1•30


2 2 2

where

R1 b’1
QTA = R = and Q T b = b’ = Eq. 1•31
0 b’2

The submatrix R1 and subvector b’1 have sizes of n × n and n, respectively, and the null matrix and the subvector
b’2 have sizes of (m-n) × n and (m-n), respectively. Therefore Eq. 1•30 becomes

r 2 = QTA x – QTb 2 = R 1 x – b’1 2 + b’2 2 Eq. 1•32


2 2 2 2

In Eq. 1•32, the squares of residual norm is minimized with respect to x if we set

R 1 x – b’1 = 0 Eq. 1•33

Therefore, after we have done the QR decomposition—A = Q R, the least squares solution can be found by first
obtaining b’1 = QT b, then, solving Eq. 1•33 for x. For example,2

1. D.G. Luenberger, 1969, “Optimization by Vector Space Methods”, John Wiley & Sons, Inc., p. 55.
2. B.N. Datta, 1995, “Numerical Linear Algebra, and applications” Brooks/Cole Publishing Company, pp. 333-4, and
pp.337-8.

36 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
double d[3][2] = { {1.0, 1.0}, // (project: “matrix_algebra”)
{1.e-4, 0.0},
{0.0, 1.e-4} },
v[3] = {2.0, 1.e-4, 1.e-4};
C0 A(3, 2, d[0]), b(3, v);
QR a(A); // QR decomposition
C0 x = a * b; // “*” is back substitution by Eq. 1•33
cout .precision(12);
cout << x << endl; // {1.0, 1.0}T

The solution can be checked by computing in your mind, since the right-hand-side vector, b, is just summation of
each row of the left-hand-side matrix, A. The QR decomposition therefore yields an exact solution in this numer-
ical case. On the other hand, the normal equation method for this ill-conditioned matrix shows some discrepan-
cies in the following codes (see also project: “matrix_algebra”)

C0 gram_matrix = (~A)*(A); // AT A
Cholesky c(gram_matrix); // Cholesky decomposition
C0 x = c * ((~A)*b); // “*” is forward/back substitutions
cout .precision(12);
cout << x << endl; // {0.99999999875, 1.00000000125}T

For this example the solution has only been mildly corrupted. You may argue that the Cholesky decomposition
for normal equation is actually acceptable in practice, if not theoretically sound. However, the flop-count of
(ATA) followed by Cholesky decomposition is about two times more expensive than that of QR decomposition
alone.
QR decomposition can be also used for rank deficient problem to reveal its column rank provided that col-
umn-pivoting is used. In VectorSpace C++ Library we reset default of no pivoting for QR decomposition by

“Matrix::Pivoting_Method = Matrix::Column_Pivoting;”

before the QR decomposition is called. Then, member function “QR::rank()” can be called to reveal its column
rank. However, the rank revealing QR decomposition is not as reliable as the singular value decomposition
(SVD), although the SVD is about one order of magnitude more expensive than the QR decomposition.

Eigenvalue Problem
Before we get to the singular value decomposition, let’s first look at the symmetric eigenvalue problem. A
symmetric (square) matrix A of size n × n have eigenvalue λ and corresponding eigenvector x if

Ax=λx Eq. 1•34

The computation of eigenvalues and eigenvectors of a symmetric matrix can be written in VectorSpace C++
Library as (in project: “matrix_algebra”)

Workbook of Applications in VectorSpace C++ Library 37


Chapter 1 Computational Linear Algebra Using C0 Type Objects
double d[4][4] = {{ 1.0, -3.0, -2.0, 1.0},
{-3.0, 10.0, -3.0, 6.0},
{-2.0, -3.0, 3.0, -2.0},
{ 1.0, 6.0, -2.0, 1.0}};
C0 A(4, 4, d[0]);
Eigen a(A);
C0 lambda = a.Eigenvalues(),
x = a.Eigenvectors(); // column vectors of x are eigenvectors
cout << lambda << endl; // eigenvalues: {14.3295, 4.45696, -0.371375, -3.41509}T
cout << x(0) << endl; // { 0.119346, -0.855989, 0.279432, -0.418279 }T
cout << x(1) << endl; // { 0.655651, -0.229133, -0.693100, 0.192958}T
cout << x(2) << endl; // { 0.505683, -0.00299097, 0.640311, 0.578167}T
cout << x(3) << endl; // {-0.547871, -0.463435, -0.177574, 0.673448 }T

Singular Value Decomposition


For a rectangular matrix A of size m × n, m ≥ n , the singular value decomposition gives

A = U Σ VT

The singular values of matrix A are the diagonals in the diagonal matrix Σ. Assuming “r” (r ≤ n) is the rank of
the matrix, the first “r” singular values are non-zero in the diagonal submatrix Σ1. U = [U1, U2], and V = [V1, V2]
are subdivided so that the submatrices U1 and V1 to have first “r” column vectors of U and V, respectively..
mxm mxn nxn
mxn
Σ1 V1T
rxn
A = U1 U2
rxr

V2 T
mxr mx(m-r) 0
(n-r)xn
The singular values, σi, are the non-negative square root of the eigenvalues, λi, of the ATA. The column vectors
of U and V are the orthonormal bases that span the range and null spaces of A and AT (see TABLE 1•3.)

Space Orthonormal Bases


Range(A) column vectors of U1
Null(A) column vectors of V2
Range(AT) column vectors of V2
T
Null(A ) column vectors of U1
TABLE 1•3. Orthogonal projection using singular value decomposition.

38 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
The singular values in Σ are arranged in non-increasing order. If A is non-singular, the first one σ1 is the maxi-
mum and the last one σn is the minimum. The spectral norm is

A 2 = σ1 ,

and the Frobenius norm can be computed by

A F = ( σ 12 + σ 12 + … + σ n2 )

The condition number of a non-singular matrix is

cond = σ1 / σn

The singular value decomposition in VectorSpace C++ library can be called by writing

double d[3][2] = { { 1.0, 2.0 }, // (project: “matrix_algebra”)


{ 2.0, 3.0 },
{ 3.0, 4.0 } };
C0 A(3, 2, d[0]);
SVD a(A);
C0 sigma = a.Singularvalues(),
U = a.U(),
V = a.V();
cout << sigma << endl; // {6.54676, 0.374153}T
cout << U(0) << endl; // {0.338098, 0.550649, 0.7632}T
cout << U(1) << endl; // {0.847952, 0.173547, -0.500858}T
cout << U(2) << endl; // {0.408248, -0.814697, 0.408248}T
cout << V(0) << endl; // {0.569595, 0.821926}T
cout << V(1) << endl; // {-0.821926, 0.569595}T
cout << a.cond() << endl; // 17.4975
cout << a.rank() << endl; // 2

The least square problem can be solved by singular value decomposition. Sometimes the problem can be overde-
termined that you make more measurements than the unkown, but it can be still rank deficient because some
measurements basically repeat the information of the others, while, at the same time, some vital information has
never been obtained. The use of singular value decomposition for the solution of least squares or simultaneous
equations is parallel to the use of LU, Cholesky or QR decomposition. For example, we have

SVD a(A);
C0 x = a * b;
or set default matrix solver by

Matrix::Decomposition_Method = Matrix::Singular_Value_Decomposition;
C0 x = b / A;

Workbook of Applications in VectorSpace C++ Library 39


Chapter 1 Computational Linear Algebra Using C0 Type Objects
We can also use the decompose operator “!” or the member function “inverse()”. In fact, the function “inverse()”
gives the so-called pseudoinverse (also known as Moore-Penrose generalized inverse).
The condition of m ≥ n on matrix A is not considered restrictive at all. If we have m < n, we can work on the
SVD of AT = U Σ VT and the SVD of A = (U Σ VT)T = V Σ U T.

In summary, we have introduced all the primary objects, Scalar, Vector, and Matrix objects of C0 type, in
Sections 1.1.1 to 1.1.4. For a complicated object like Matrix, data abstraction begins to play a more important
role compared to simple objects like Scalar or Vector. Data abstraction helps us encapsulate the complexity of
memory management of the data array for the Matrix class. The details of how actually these data are stored in
the Matrix object is hidden from users. The various constructors and the destructor of the Matrix object help
users, behind the scene, handle the resources of the Matrix class. On top of this, because the Matrix class needs
an extensive “bag of tricks” on the subject of matrix algebra, the data abstraction help to organize different kinds
of decomposition methods-LU, Cholesky, QR, SVD, and eigen-system solver with the associated matrix data
into a coherent module. This integrated module, under the concept of data abstraction, acts like an intelligent
entity that has the knowledge to deal with problems of its own.
In the section on Scalar, we introduced the object-oriented programming that achieves the flexibility by the use
of virtual constructors. In the definition for two assignment operators “=” and “&=”, we introduced the C0 type
object actually acting more like a label that can be peeled off or attached to a concrete object . This symbolic fla-
vor is also a feature enabled by the object-oriented programming method implemented in VectorSpace C++
library. The C0 class is the base class for the derived concrete classes, Scalar, Vector, Matrix, ..., etc. The base
class C0 also serves as a flat-interface1 for all of its derived concrete classes. The C0 class contains all member
operators and functions of all its derived concrete classes. In the user’s program, C0 is the only generic type
used. C0 serves as the delegate for all of its constituents. This VectorSpace C++ library generic type feature
actually pushes the programming environment of C++ more towards the side of a full-fledged object-oriented
language for programming versatility, which complements the strong type-compiled language that C++ is origi-
nally designed for safety. We use one simple example to illustrate the advantage of this dynamic, late-binding
feature supported by VectorSpace C++ Library.
For example, root-finding for a function f(x) is stated as the following

Problem: Solve scalar x, for a scalar function f(x) = 0

Approximation of function f(x) by the Taylor expansion to the first order gives

f(x0+dx) ≈ f(x0) + f’ (x0) dx = 0

Therefore, the increment of solution dx can be found, from the above equation, by using dx = -f(x0) / f’ (x0)
(known as Newton’s formula), and the solution is updated with increment dx by xi+1 = xi + dx, where “i” is the
iterative index. A converged solution is obtained when dx becomes negligible. We can easily extend this prob-

1. Bjarne Stroustrup, 1991, “The C++ programming language”, 2nd ed., Addison-Wesley Publishing Company, Massachu-
setts, p. 452.

40 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
lem to a multi-dimensional case. For a vector function f(x) of size n in n-dimensional space x, the same root-find-
ing problem can be solved by the same Newton’s method. However, in the n-dimensional case, both f and x are
vectors of size “n”, and the first derivatives df is a Matrix (of size n × n, = grad f ). In VectorSpace C++ Library,
we write

C0 newton_formula(const C0& f, const C0& df) { return -f / df; }

At the time of writing this subroutine, we do not need to distinguish whether it is for a one-dimensional problem
or a multi-dimensional problem. When this subroutine is called by the user, if the problem is one-dimensional,
the arguments f, df and the return value are all Scalar objects of C0 type. If the problem is multi-dimensional, the
user passes a Vector object of C0 type through argument f, and a square Matrix object of C0 type through argu-
ment df, and the return value will be a Vector object—the increment dx from Newton’s formula. The division
operator “/”, in the multi-dimensional case, implicitly calls the default matrix solver—the LU decomposition to
solve the problem.
We discuss one more example of object-oriented function dispatching mechanism. Assuming in the subrou-
tine “newton_formula()”, we want to display the value of “f” column-wise and add a line as

for(int i = 0; i < f.length(); i++) cout << f[i] << endl;

The member function “C0::length()” makes sense if the variable “ f ” is a Vector. We mentioned on page 17 the
concept of backward compatibility of a member function. For a Scalar object, a call to “length()” exists and will
always return the default value of “ 1 ”. This compatibility of member functions is the result of using the flat-
interface provided by the C0 for all its derived concrete classes. Furthermore, whatever “ f ” is, a scalar or a vec-
tor, we can always write

cout << f << endl;

cout in VectorSpace C++ Library knows how to handle the output whatever “ f ” is! We see that as the result of
using C0 instead of concrete types a general form of syntax can be defined.
We note by passing that for Newton’s method we better use C1 type instead, which will be introduced in the
next chapter. With the C1 type, a differentiable object can be easily defined. The above hypothetical example is
only used to illustrate, from programming method perspective, the versatility of using the more dynamic, late-
binding technique. It is not for the purpose of explaining the implementation of a real world numerical problem.

1.1.5 Subvector and Submatrix


Fortran and C++ languages support multi-dimensional array for representing higher order tensors. It is tempt-
ing for us to implement objects with dimension greater than Matrix (dim. > 2). In fact, higher order tensors are
needed in practice. For example, the elasticity tensor in solid mechanics is a fourth order tensor. In engineering
literatures, however, the fourth order tensor is rearranged in engineering convention and is represented by a 2-
dimensional matrix. There is an obvious reason for doing that. Lower dimensions are always easier to be written
down in paper. Learning from engineering experience, we realize that arrays of too high dimensions should be
avoided if possible, because they are always very hard to be understood and handled. Therefore, the implementa-
tion of VectorSpace C++ Library put emphasis on the use of Subvector and Submatrix objects instead of objects

Workbook of Applications in VectorSpace C++ Library 41


Chapter 1 Computational Linear Algebra Using C0 Type Objects

9 = 3x3 0.0 4 x 9 = {2x2} x {3x3}


1.0
2.0 0. 1. 2. 3. 4. 5. 6. 7. 8.
3.0 10. 11. 12. 13. 14. 15. 16. 17. 18.
4.0 20. 21. 22. 23. 24. 25. 26. 27. 28.
5.0 30. 31. 32. 33. 34. 35. 36. 37. 38.
6.0
7.0
8.0

Figure 1•16 Referenced source Vector and referenced source Matrix are equal-
partitioned to Subvectors and Submatrices.
of arbitrarily higher dimensional array. In many engineering applications, for example in the finite element
method, subvectors and submatrices are used as a convention to represent objects that would otherwise be writ-
ten in a higher dimensional array.
The mathematical “subvector” and “submatrix”, of course, should include the “reference Vector” and “refer-
ence Matrix” defined on page 10 and page 21, respectively. The reference Vector or reference Matrix can have
arbitrary sizes and starting/ending indices as long as they are within the bounds of the Vector or the Matrix they
are referring to. In VectorSpace C++ Library the terms Subvector and Submatrix are reserved for special kind of
subvector and submatrix. This special kind of subvector and submatrix should be able to have their referenced
source vector and referenced source matrix partitioned into equal-sized blocks. For example,, in Figure 1•16 the
referenced source Vector has “length” = 9. The Vector can be subdivided into 3 equal sized sub-blocks of
“length” = 3. Similarly, the reference source Matrix has “row-length” = 4 and “column-length” = 9. This Matrix
can be subdivided into 6 equal sized blocks. Each has “row-length” = 2 and “column-length” = 3. We will show
you later that the Subvector and Submatrix not only serve the purpose of representing higher dimensional ten-
sors with a lower dimensional ones, but also with the use of the VectorSpace Subvector/Submatrix index
scheme, the need for the “for” control statement in C++ being substantially reduced, leads to a much uncluttered
C++ codes that become close to the mathematical expressions.

Constructors and Selectors


To construct a Subvector or Submatrix object of the C0 type, the constructor and selector need to act in con-
cert to make the Subvector and Submatrix objects (see examples in project: “subvector_submatrix_examples”).
The dedicated constructors for Subvector and Submatrix can be written in VectorSpace C++ Library as (see
Figure 1•16)

double v[9] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0},
d[4][9] = {{0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0},

42 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
{10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0},
{20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0},
{30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0}};
C0 a(v, 9, 3), b(d[0], 4, 9, 2, 3); // Nominal_Subvector and Nominal_Submatrix

The double array “v” and “d” are the physical memory space to be referenced. The declaration by the dedicated
constructors of the last line actually gives a Nominal_Subvector and a Nominal_Submatrix. By the word “Nomi-
nal”, we mean that it is the name for a Subvector or a Submatrix. Therefore, “a” and “b” are the symbols that will
be used to generate Subvector and Submatrix per se. The declaration above by the dedicated constructors of the
C0 type only provides information on how to partition a continuous memory space into equal sized sub-blocks.
For example, a(v, 9, 3) means that the double array “v” has length = 9, and it is partitioned into three equal sized
sub-blocks, each of which has “length” = 3, and b(d[0], 4, 9, 2, 3) means that the double array “d” has “row-
length” = 4, “column-length” = 9, and it is partitioned into six equal sized sub-blocks, each of which has “row-
length” = 2, and “column-length” = 3. The Subvector and Submatrix are generated by using selectors. For exam-
ple, (in project: “subvector_submatrix_examples”, and see also Figure 1•16)

// continuous block selector; operator “( . )” or “( . , . )”


// subvectors
cout << a(0) << endl; // {0.0, 1.0, 2.0}T
cout << a(1) << endl; // {3.0, 4.0, 5.0}T
cout << a(2) << endl; // {6.0, 7.0, 8.0}T
// submatrices
cout << b(0, 0) << endl; // {{ 0.0, 1.0, 2.0},
// {10.0, 11.0, 12.0}}
cout << b(0, 1) << endl; // {{ 3.0, 4.0, 5.0},
// {13.0, 14.0, 15.0}}
cout << b(0, 2) << endl; // {{ 6.0, 7.0, 8.0},
// {16.0, 17.0, 18.0}}
cout << b(1, 0) << endl; // {{20.0, 21.0, 22.0},
// {30.0, 31.0, 32.0}}
cout << b(1, 1) << endl; // {{23.0, 24.0, 25.0},
// {33.0, 34.0, 35.0}}
cout << b(1, 2) << endl; // {{26.0, 27.0, 28.0},
// {36.0, 37.0, 38.0}}

The operator “( )” of the Nominal_Subvector and Nominal_Submatrix selects a continuous block of memory
space to form a Subvector or Subvectors (see Figure 1•16). For example, the index of “a(1)” is “1”, which means
the off-set from the first continuous block subvector. The contents of the Subvector “a(1)” is, therefore, {3.0, 4.0,
5.0}T referring to the second continuous block in the equal-partitioned double array v. The indices of b(1, 2)
means the continuous block Submatrix has “1” row-offset and “2” column-offset from the first continuous block.
The contents of “b(1, 2)” is then {{26.0, 27.0, 28.0}, {36.0, 37.0, 38.0}}.

The operator “[ ]” of the Nominal_Subvector and Nominal_Submatrix selects a regular increment memory spots
to form a Subvector or a Submatrix . For example, in VectorSpace C++ Library, the Subvectors and Submatrices
are generated as (in project: “subvector_submatrix_examples”)

Workbook of Applications in VectorSpace C++ Library 43


Chapter 1 Computational Linear Algebra Using C0 Type Objects

0.0
1.0 0. 1. 2. 3. 4. 5. 6. 7. 8.
2.0 10. 11. 12. 13. 14. 15. 16. 17. 18.

3.0 20. 21. 22. 23. 24. 25. 26. 27. 28.
a(1) 3.0 4.0 30. 31. 32. 33. 34. 35. 36. 37. 38.

4.0 5.0
5.0 6.0 b(1, 2) 26. 27. 28.
7.0 36. 37. 38.
8.0

Figure 1•17 Subvectors and Submatrices are generated by calling a continuous block
selector with the selector “( )”.

// subvectors
cout << a[0] << endl; // {0.0, 3.0, 6.0}T
cout << a[1] << endl; // {1.0, 4.0, 7.0}T
cout << a[2] << endl; // {2.0, 5.0, 8.0}T
// submatrices
// operator “[ ]” applied twice
cout << b[0][0] << endl; // {{ 0.0, 3.0, 6.0},
// {20.0, 23.0, 216.0}}
cout << b[0][1] << endl; // {{ 1.0, 4.0, 7.0},
// {21.0, 24.0, 27.0}}
cout << b[0][2] << endl; // {{ 2.0, 5.0, 8.0},
// {22.0, 25.0, 28.0}}
cout << b[1][0] << endl; // {{10.0, 13.0, 16.0},
// {30.0, 33.0, 36.0}}
cout << b[1][1] << endl; // {{11.0, 14.0, 17.0},
// {31.0, 34.0, 37.0}}
cout << b[1][2] << endl; // {{12.0, 15.0, 18.0},
// {32.0, 35.0, 38.0}}

For example, see Figure 1•16, “a[1]” means every element with offset of “1” from every equal-partitioned sub-
block is selected to form a Subvector. The contents of the Subvector “a[1]” is therefore {1.0, 4.0, 7.0}T, which is
referring to every second element of the sub-blocks in the double array “v”. The indices of b[0][1] means every
element with “0” row offset and “1” column offset from the first elements of the equal-partitioned sub-blocks is
selected to form a Submatrix. The contents of the Submatrix “b[0][1]” is therefore {{1.0, 4.0, 7.0}, {21.0, 24.0,
27.0}}, which is referring to every first row, second column element of each sub-block in the double array “d”.

44 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

0.0
1.0
2.0 0. 1. 2. 3. 4. 5. 6. 7. 8.
b[0][1]
3.0 10. 11. 12. 13. 14. 15. 16. 17. 18.
1. 4. 7.
a[1] 1.0 4.0 20. 21. 22. 23. 24. 25. 26. 27. 28.
21. 24. 27.
4.0 5.0 30. 31. 32. 33. 34. 35. 36. 37. 38.
7.0 6.0
7.0
8.0

Figure 1•18 Subvectors and Submatrices are generated by calling a regular increment
selectors “[ ]”. “[ ]” needs to be applied twice for the regular increment Submatrix.

The access to a part or an element of a Subvector or a Submatrix is exactly like the access to that of the Vector
and Matrix. We can use “operator[ ](int)” to select an element of a Subvector. For the Submatrix, “operator [
](int)” is the row-selector and “operator( )(int)” is the column selector, they both return a Vector object, and
“operator( )(int, int)” is the element selector. For example, (project: “subvector_submatrix_examples”)

cout << a[1][1] << endl; // 4.0


cout << a(1)[0] << endl; // 3.0
cout << b(1, 2)[0] << endl; // {26.0, 27.0, 28.0}T
cout << b(1, 2)[0][1] << endl; // 27.0
cout << b[0][1](2) << endl; // {7, 27}T
cout << b[0][1](2, 0) << endl; // 7
In the above example, the Subvectors and Submatrices are always referring to memory space directly. In Vec-
torSpace C++ Library, one more layer of indirection is possible. You can define Subvector of Subvector, or Sub-
matrix of Submatrix. For example (see Figure 1•19, and project: “subvector_submatrix_examples”),

double v[12] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0},
d[8][12] = { { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0},
{10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0},
{20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0},
{30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0},
{40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0},
{50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0},
{60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0},
{70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0} };
C0 a(v, 12, 6), b(d[0], 8, 12, 2, 2),
c(a(0), 3), // “c” is a Nominal_Subvector of a Subvector

Workbook of Applications in VectorSpace C++ Library 45


Chapter 1 Computational Linear Algebra Using C0 Type Objects
e(b[0][1], 2, 3); // “e” is a Nominal_Submatrix of a Submatrix
cout << a(0) << endl; // {0.0, 1.0, 2.0, 3.0, 4.0, 5.0}T
cout << c[0] << endl; // {0.0, 3.0}T; Subvector of Subvector
cout << b[0][1] << endl; // { { 1.0, 3.0, 5.0, 7.0, 9.0, 11.0},
// {21.0, 23.0, 25.0, 27.0, 29.0, 31.0},
// {41.0, 43.0, 45.0, 47.0, 49.0, 51.0},
// {61.0, 63.0, 65.0, 67.0, 69.0, 71.0} }
cout << e(1, 0) << endl; // { {41.0, 43.0, 45.0}, Submatrix of Submatrix
// {61.0, 63.0, 65.0} }
In this example, “a” and “b” are Nominal_Subvector and Nominal_Submatrix, respectively, which refer to
double array “v” and “d”. “c” and “e” are Nominal_Subvector and Nominal_Submatrix that refer to Subvector
“a[0]” and Submatrix “b[0][1]”. Upon being applied selectors, “c[0]” and “e(1, 0)” are a Subvector of Subvector
and a Submatrix of Submatrix, respectively. The virtual constructor (use macro definitions “SUBVECTOR” and
“SUBMATRIX”) and autonomous virtual constructor serve exactly the same purpose as those of the primary
objects of C0 type. The strings that are used for the virtual constructor and autonomous virtual constructor are
listed in the following boxes.

d
0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10. 11.
v
10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.
0.0 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31.
1.0 a(0)
30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41.
2.0 0.0 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51.
3.0 1.0 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61.
c[0]
4.0 2.0 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71.
5.0 0.0
3.0 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81.
6.0 4.0 3.0
7.0 5.0 b[0][1] 1.0 3.0 5.0 7.0 9.0 11.
8.0
21. 23. 25. 27. 29. 31.
9.0
41. 43. 45. 47. 49. 51.
10.
61. 63. 65. 67. 69. 71.
11.

41. 43. 45.


e(1, 0)
61. 63. 65.

Figure 1•19 Subvector of Subvector(left-hand-side) and Submatrix of Submatrix (right-hand-side).

46 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

virtual constructor string VectorSpace C++ library definition priority


nominal Subvector
“int, int, double* ” double*, source-length, block-length —
“int, C0&” Vector or Subvector of C0 type, block-length 21
nominal Submatrix
“int, int, int, int, double* ” source row-length, source column-length, block —
row-length, block column-length, double*
“int, int, C0&” block row-length, block column-length, 22
Matrix or Submatrix of C0 type
Strings in C0 virtual constructor for generating nominal Subvector and nominal Submatrix.

We emphasize that the C0 constructors play the role of constructing Nominal_Subvectors and
Nominal_Submatrices, while the selectors of the Nominal_Subvector and Norminal_Submatrix play the role of
“constructors” to generate Subvectors and Submatrices.

Nominal_Subvector Subvector
C0 constructor & selectors &
Nominal_Submatrix Submatrix
(of Nominal_Subvector
and Nominal_Submatrix)

Other Operators and Functions


Arithmetic operators for Nominal_Subvector and Nominal_Submatrix only make sense when we introduce
basis expression in the next section. We restrict our discussion here on Subvector and Submatrix. On page 11, we
introduce the primary casting for reference Vector by unary operator “+”. “operator +()” works for Subvector
and Submatrix to convert them into Vector and Matrix, respectively. Actually not just the unary “+” operator, all
arithmetic operators of Subvector and Submatrix are parallel to those of Vector and Matrix. When an arithmetic
operator of a Subvector or a Submatrix is called, it converts (primary cast) the operand(s), and the operator is dis-
patched to that of the Vector or Matrix accordingly. The transcendental functions and matrix algebra functions of
Subvector and Submatrix also “primary cast” them into Vector and Matrix. Continuing from the previous exam-
ple, we explore the addition operator, (project: “subvector_submatrix_examples”)

cout << a(0) << endl; // {0.0, 1.0, 2.0, 3.0, 4.0, 5.0}T
cout << a(1) << endl; // {6.0, 7.0, 8.0, 9.0, 10.0, 11.0}T
cout << (a(0)+a(1)) << endl; // {6.0, 8.0, 10.0, 12.0, 14.0, 16.0}T

In the last line, when binary “C0::operator +(const C0&)” is called, both Subvectors “a(0)” and “a(1)” are turned
into Vectors, and then the two Vectors are added together. In fact, in the first two lines, upon “<<” being called,
the Subvectors are also converted into Vectors for output.

Workbook of Applications in VectorSpace C++ Library 47


Chapter 1 Computational Linear Algebra Using C0 Type Objects
Subvector and Submatrix may not have been used extensively in physics and mathematics. However, its use
in engineering literatures is very extensive. They are used to explicitly lay out formula for engineers to follow
easily. Many of them have become quite conventional, for example, the “B-matrix” in the finite element method.
We will demonstrate the power of Subvector and Submatrix when we get to the finite element method in Chapter
4 and Chapter 5. On the contrary, the mathematicians and scientists favor the linear algebraic expression using
basis. VectorSpace C++ Library support the best of the both worlds.

1.1.6 Basis
The usage of Basis is a lot like that of Subvector and Submatrix. The constructor and selector are orches-
trated with each other to create Basis objects of C0 type. To understand the semantics of constructing the Sub-
vector and Submatrix, we need to understand how the temporary objects like Nominal_Subvector and
Nominal_Submatrix are generated in the intermediate step. The semantics of generating Basis object is just the
same. The names of those temporary objects are getting quite wordy because of the combinatorial explosion of
objects. We can learn how to generate those complicated basis expressions by examples and illustrations in fig-
ures, without regard to those meticulous temporary object names. Only in one occasion or two, when we need to
get into the semantics of their usage, we need to spell their names all out. In VectorSpace C++ Library, a Basis is
created by calling the C0 dedicated constructor and then applying the selector as (in project: “basis_examples”)

C0 r(5.0), // Scalar 5.0


e(5); // Nominal_Basis of space dimension = 5
C0 v = r * e[3]; // Vector; “r” porjected into fourth position of “e”
cout << v << endl; // {0.0, 0.0, 0.0, 5.0, 0.0}T
C0 w = e[1] + r * e[3] + 6.0 * e[4];
cout << w << endl; // {0.0, 1.0, 0.0, 5.0, 6.0}T

The C++ code “r * e[3]” is completely parallel to the mathematical expression,

v = r e 3, Eq. 1•35

where “r * e[3]” means that a Scalar “r” is projected, by operator “*” to the basis e[3]. The result of such projec-
tion gives a Vector with the fourth component, with index “3” as its offset number, having the value of “r”. If one
of the terms in the expression does not have a Scalar, like “r” or “6” in the following, projected to the basis, it
assumes the scalar constant is “1”. For example,

w=e1+re3+6e4 Eq. 1•36

where w = {0.0, 1.0, 0.0, 5.0, 6.0}T. The second component of “w” is “1.0”, which has assumed a constant “1.0”
projected at “e 1”. The projection by “*” can be further visualized as in Figure 1•20, in which “r” is projected by
using Basis e[3] as its projection map (or filter). In the C++ code, “w” is a Vector that has the value of “5.0” from
projecting the Scalar “r” into its fourth component. Similarly, the fifth component of “w” has double “6.0”.
What if we have mathematical expressions, using bases, that generate second order tensor, such as

48 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

Basis: e[3] Vector: w


0
1
2
“operator *(const C0&)”
Scalar: r = 5.0 3 5.0
4

Figure 1•20 Projection using “*”. Basis “e[3]” serves as the projection map.

T = t 00 e 0 ⊗ e 0 + t01 e 0 ⊗ e 1 + t 02 e 0 ⊗ e 2
+ t 10 e 1 ⊗ e 0 + t11 e 1 ⊗ e 1 + t 12 e 1 ⊗ e 2
+ t 20 e 2 ⊗ e 0 + t21 e 2 ⊗ e 1 + t 22 e 2 ⊗ e 2 Eq. 1•37

In C++ code, it can be written as (in project: “basis_examples”)

double t[3][3] = { { 0.0, 1.0, 2.0},


{10.0, 11.0, 12.0},
{20.0, 21.0, 22.0} };
C0 e(3),
T = t[0][0] * (e[0]%e[0]) + t[0][1] * (e[0]%e[1]) + t[0][2] * (e[0]%e[2])
+ t[1][0] * (e[1]%e[0]) + t[1][1] * (e[1]%e[1]) + t[1][2] * (e[1]%e[2])
+ t[2][0] * (e[2]%e[0]) + t[2][1] * (e[2]%e[1]) + t[2][2] * (e[2]%e[2]);
cout << T << endl; // { { 0.0, 1.0, 2.0},
// {10.0, 11.0, 12.0},
// {20.0, 21.0, 22.0} };
Again the tensor product “ ⊗ ” is represented by “%” to produce a “Basis_Matrix”. The operator “*” still acts as
“projection operator”.

Matrix: T

Basis_Matrix: e[2]%e[1]
21.

double t[2][1] = 21.0


Figure 1•21 Projection using “*”. Basis_Matrix “e[2]%e[1]” is used as a projection map.

Workbook of Applications in VectorSpace C++ Library 49


Chapter 1 Computational Linear Algebra Using C0 Type Objects
In Eq. 1•37 and the above code segment, every term has been written out explicitly, although missing a few
terms or omitting some constant values of “tij” before a second order basis “ei ⊗ ej” still works.
We call Eq. 1•35 to Eq. 1•37 as primary basis expressions. When we introduce the use of Subvector and Sub-
matrix in the previous section, we argued the disadvantage of higher dimensional objects from the experience of
the development of the engineering convention (page 41). We do not promote using tensor of order higher than
2, because they are much harder to comprehend and are almost impossible to be written out neatly. In comple-
ment, we provide extended basis expressions, when some higher dimensional tensors are needed in an applica-
tion. The extended basis expression gives Nominal_Subvector or Nominal_Submatrix as its return value. For
example, (in project: “basis_examples”)

double v[6] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0},


w[6] = {6.0, 7.0, 8.0, 9.0, 10.0, 11.0};
C0 V(6, v), W(6, w), // two Vectors
e(6), E(2); // two Basis
C0 A = V * (e*E[0]) + W * (e*E[1]); // use Basis_Subvector: e*E[0] and e*E[1]
cout << (+A) << endl; // {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0}T
cout << A(0) << endl; // {0.0, 1.0, 2.0, 3.0, 4.0, 5.0}T
cout << A(1) << endl; // {6.0, 7.0, 8.0, 9.0, 10.0, 11.0}T
cout << A[3] << endl; // {3.0, 9.0}T
C0 U = e * E; // Nominal_Basis_Subvector: U
C0 A1 = V * U(0) + W * U(1); // A1 == A

where “A” is a Nominal_Subvector generated by projecting two Vectors “V” and “W” into two
Basis_Subvectors e*E[0] and e*E[1]. Let’s first look at the Basis_Subvectors e*E[0] (see Figure 1•22). The
Basis “e” is projected into the a Basis “E[0]” to form a Basis_Subvector “e*E[0]”. Then, a Vector “V” is pro-
jected using “e*E[0]” as its projection map to form a Nominal_Subvector “V*(e*E[0])”. In the above code seg-
ment, a similar step is done by projecting a Vector “W” into the lower part of a Nomial_Subvector
“W*(e*E[1])”. Then, these two Nominal_Subvectors are added together and assigned to a C0 variable “A”,
which serves as a label for it. “A” can be used just as we used a Nominal_Subvector to generate all kinds of Sub-
vectors that we want in the previous section. For the output of A, (+A) is the primary casting using unary posi-
tive operator “+” to transform the Nominal_Subvector into a Vector. A different path to generate the same thing
can be done (see Figure 1•23). The Nominal_Basis “e” which has length = 6 is projected into another
Nominal_Basis “E” which has length = 2. The result is a Nominal_Basis_Subvector “e*E”. We then assign the
C0 variable “U” to this Nominal_Basis_Subvector. A Vector “V” is projected to a Basis_Subvector “U(0)” to
produce a Nominal_Subvector “V*U(0)”.
In the above example the Nominal_Basis is projected to a Basis. We now discuss a case with a reverse order
of the object occurrences; i.e., with a Basis (“E[0]”) projecting to a Nominal_Basis (“e”), such as “E[0]*e” (in
project: “basis_examples”).

C0 B = V * (E[0]*e) + W * (E[1]*e); // use Basis_Subvector: E[0]*e and E[1]*e


cout << (+B) << endl; // {0.0, 6.0, 1.0, 7.0, 2.0, 8.0, 3.0, 9.0, 4.0, 10.0, 5.0, 11.0}T
cout << B(0) << endl; // {0.0, 6.0}T
cout << B[1] << endl; // {6.0, 7.0, 8.0, 9.0, 10.0, 11.0}T

50 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

e * E[0] V e * E[0] = V*(e*E[0])


“*”
=
e “*” E[0] 0.0 0.0

0 1.0 1.0

1 2.0 2.0
3.0 3.0
4.0 4.0
5.0 5.0
0.0
0.0
0.0
0.0
0.0
0.0

Figure 1•22 Projection of a Nominal_Basis “e” on a Basis “E[0]” to form a Basis_Subvector


e*E[0] and projection of Vector “V” into the Basis_Subvector “e*E[0]”.
e*E=U V U(0) = V*U(0)
= “*”
e “*” E
0.0 0.0
1.0 1.0
2.0 2.0
3.0 3.0
4.0 4.0
5.0 5.0
0.0
0.0
0.0
0.0
0.0
0.0
Figure 1•23 Projection of Nominal_Basis “e” on Nominal_Basis “E” to form a
Nominal_Basis_Subvector “U = e*E”, and projection of Vector “V” on a Nominal_Basis_Subvector
“U(0)” to form a Nominal_Subvector “V*U(0)”.

Workbook of Applications in VectorSpace C++ Library 51


Chapter 1 Computational Linear Algebra Using C0 Type Objects
C0 Z = E*e; // Nominal_Basis_Subvector: Z
C0 B1 = V * Z[0] + W * Z[1]; // B1 == B

In the above code (see also Figure 1•22), a Basis “E[0]” is projected to a Nominal_Basis “e” to form a
Basis_Subvector “E[0]*e”. A Vector “V” is then projected to “E[0]*e” to generate a Nominal_Basis_Subvector
“V*(e*E[0])”.
E[0]*e E[0]*e = V*(E[0]*e)
“*”
=
“*” 0.0
0.0

e V 1.0

0.0 0.0
E[0] 2.0
1.0
0 0.0
2.0
1 3.0
3.0
4.0 0.0

5.0 4.0
0.0
5.0
0.0

Figure 1•24 Projection of a Basis “E[0] to a nominal Basis “e” to form a Basis Subvector “E[0]*e”.
A Vector “V” is projected to “E[0]*e” to generate a nominal Basis Subvector “V*(E[0]*e)”.
There are cases that the Nominal_Basis_Subvector can be used. (see Figure 1•22). The Nominal_Basis “E”
which has the length = 2 is projected into another Nominal_Basis “e” which has the length = 6. The result is a
Nominal_Basis_Subvector “E*e”, then a C0 variable “Z” is assigned (by copy constructor which is always by
reference in VectorSpace C++ Library) to this Nominal_Basis_Subvector. A Vector “V” is projected to a
Basis_Subvector “Z[0]” to produce a Nominal_Subvector “V*Z[0]”.
If you have mastered the Subvector, Submatrix and the Basis_Subvector in the above, the Basis_Submatrix
should become natural according to the rules that you have just learned. The only thing is you will notice a two
dimensional Basis_Submatrix is certainly more complicated than the one dimensional Basis_Subvector. We
have warned you that the names of the temporary objects generated along the way could get quite wordy. Those
names are important for understanding the semantics and therefore the internal working of VectorSpace C++
Library. However, in most cases you can get a practical understanding by just reading the example codes and the
Figures that are associated with the codes. For example, (in project: “basis_examples”)

double d1[4][6] = { { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0},


{10.0, 11.0, 12.0, 13.0, 14.0, 15.0},
{20.0, 21.0, 22.0, 23.0, 24.0, 25.0},

52 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
E*e=Z Z[0] = V*Z[0]
= “*”
“*” 0.0
e 0.0

V 1.0
E
0.0 0.0
2.0
1.0
0.0
2.0
3.0
3.0
4.0 0.0

5.0 4.0
0.0
5.0
0.0

Figure 1•25 Projection of nominal Basis “E” on nominal Basis “e” to form a nominal Basis
Subvector “Z = E*e”, and projection of Vector “V” on a nominal Basis Subvector “Z[0]” to form a
nominal Subvector “V*Z[0]”.
{30.0, 31.0, 32.0, 33.0, 34.0, 35.0} },
d2[4][6] = { { 6.0, 7.0, 8.0, 9.0, 10.0, 11.0},
{16.0, 17.0, 18.0, 19.0, 20.0, 21.0},
{26.0, 27.0, 28.0, 29.0, 30.0, 31.0},
{36.0, 37.0, 38.0, 39.0, 40.0, 41.0} },
d3[4][6] = { {40.0, 41.0, 42.0, 43.0, 44.0, 45.0},
{50.0, 51.0, 52.0, 53.0, 54.0, 55.0},
{60.0, 61.0, 62.0, 63.0, 64.0, 65.0},
{70.0, 71.0, 72.0, 73.0, 74.0, 75.0} },
d4[4][6] = { {46.0, 47.0, 48.0, 49.0, 50.0, 51.0},
{56.0, 57.0, 58.0, 59.0, 60.0, 61.0},
{66.0, 67.0, 68.0, 69.0, 70.0, 71.0},
{76.0, 77.0, 78.0, 79.0, 80.0, 81.0} };
C0 a1(4, 6, d1[0]), a2(4, 6, d2[0]), // four matrices
a3(4, 6, d3[0]), a4(4, 6, d4[0]),
e1(4), e2(6), E(2); // three Bases
C0 A = a1 * ((e1%e2)*(E[0]%E[0])) + a2 * ((e1%e2)*(E[0]%E[1]))
+ a3 * ((e1%e2)*(E[1]%E[0])) + a4 * ((e1%e2)*(E[1]%E[1]));
cout << A(0, 0) << endl; // { { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0},
// {10.0, 11.0, 12.0, 13.0, 14.0, 15.0},
// {20.0, 21.0, 22.0, 23.0, 24.0, 25.0},
// {30.0, 31.0, 32.0, 33.0, 34.0, 35.0} }

Workbook of Applications in VectorSpace C++ Library 53


Chapter 1 Computational Linear Algebra Using C0 Type Objects
C0 X = (e1%e2)*(E%E); // Nominal_Basis Submatrix
C0 A1 = a1 * X(0, 0) + a2 * X(0, 1) // A1 == A
+ a3 * X(1, 0) + a4 * X(1, 1);

In the above code segment (see also Figure 1•26), a Matrix “a1” of “row-length” = 4 and “column-length” = 6 is
projected into a Basis_Submatrix “((e1%e2)*(E[0]%E[0]))”. A Nominal_Basis_Matrix “e1%e2” is projected
into a Basis_Matrix “E[0]%E[0]” to form a Basis_Submatrix “((e1%e2)*(E[0]%E[0]))”. This Basis_Submatrix
is then used to project a Matrix “a1” to form a Nominal_Submatrix “a1*((e1%e2)*(E[0]%E[0]))”.
The equivalent expression by defining X = (e1%e2)*(E%E) is (see also Figure 1•26) that a
Nominal_Basis_Submatrix “X” can be defined as projecting a Nominal_Basis_Matrix “e1%e2” into another
Nominal_Basis_Matrix “E%E”. Then, a Matrix “a1” is projected to a Basis_Submatrix “X(0)” to form a
Nominal_Submatrix “a1*X(0)”..
Just as in the case of Basis_Subvector, we can switch from projecting Nominal_Basis_Matrix into
Basis_Matrix to projecting Basis_Matrix into Nominal_Basis_Submatrix. For example, (in project:
“basis_examples”)

C0 B = a1 * ((E[0]%E[0])*(e1%e2)) + a2 * ((E[0]%E[1])*(e1%e2))
+ a3 * ((E[1]%E[0])*(e1%e2)) + a4 * ((E[1]%E[1])*(e1%e2));
cout << B[0][0]<< endl; // { { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0},
// {10.0, 11.0, 12.0, 13.0, 14.0, 15.0},
// {20.0, 21.0, 22.0, 23.0, 24.0, 25.0},
// {30.0, 31.0, 32.0, 33.0, 34.0, 35.0} }
C0 Y = (E%E)*(e1%e2); // nominal Basis Submatrix
C0 B1 = a1 * Y[0][0] + a2 * Y[0][1] // B1 == B
+ a3 * Y[1][0] + a4 * Y[1][1];

In the above (see also Figure 1•26), a Basis_Matrix “E[0]%E[0]” is projected into a Nominal_Basis_Submatrix
“e1%e2” to form a Basis_Submatrix “(E[0]%E[0])*(e1%e2)”. A Matrix “a1” is then projected by using the
Basis Submatrix as a map to form a Nominal_Submatrix “a1*((E[0]%E[0])*(e1%e2))”..
The Nominal_Basis_Submatrix can be used in this case to generate an equivalent expression (see. Figure
1•26). A Nominal_Basis_Matrix “E%E” is projected into another Nominal_Basis_Matrix “e1%e2” to form a
Nominal_Basis_Submatrix “(E%E)*(e1%e2)”. The Nominal_Basis_Submatrix is assigned to C0 variable Y. a
Matrix “a1” is projected into a Basis_Submatrix “Y[0][0]” to form a Nominal_Submatrix “a1*Y[0][0]”.

54 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

(e1%e2)*(E[0]%E[0])

E[0]%E[0]
“*”
e1%e2

a1*(e1%e2)*(E[0]%E[0])
0.0 1.0 2.0 3.0 4.0 5.0 0.0 0.0 0.0 0.0 0.0 0.0
10. 11. 12. 13. 14. 15. 0.0 0.0 0.0 0.0 0.0 0.0
20. 21. 22. 23. 24. 25. 0.0 0.0 0.0 0.0 0.0 0.0
30. 31. 32. 33. 34. 35. 0.0 0.0 0.0 0.0 0.0 0.0

(e1%e2)*(E[0]%E[0]) 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0

“*”

a1
0.0 1.0 2.0 3.0 4.0 5.0
10. 11. 12. 13. 14. 15.
20. 21. 22. 23. 24. 25.
30. 31. 32. 33. 34. 35.

Figure 1•26 Projection of a Nominal_Basis_Matrix “e1%e2” into a Basis_Matrix E[0]%E[0] to


form a Basis_Submatrix “(e1%e2)*(E[0]%E[0])”. The Basis_Submatrix is used to project a
Matrix “a1” to form a Nominal_Submatrix “a1*(e1%e2)*(E[0]%E[0])”.

Workbook of Applications in VectorSpace C++ Library 55


Chapter 1 Computational Linear Algebra Using C0 Type Objects

X = (e1%e2)*(E%E)

E%E
“*”
e1%e2

a1*X(0)
0.0 1.0 2.0 3.0 4.0 5.0 0.0 0.0 0.0 0.0 0.0 0.0
10. 11. 12. 13. 14. 15. 0.0 0.0 0.0 0.0 0.0 0.0
20. 21. 22. 23. 24. 25. 0.0 0.0 0.0 0.0 0.0 0.0
30. 31. 32. 33. 34. 35. 0.0 0.0 0.0 0.0 0.0 0.0

0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
X(0) 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0

“*”

a1
0.0 1.0 2.0 3.0 4.0 5.0
10. 11. 12. 13. 14. 15.
20. 21. 22. 23. 24. 25.
30. 31. 32. 33. 34. 35.

Figure 1•27 Projection of a Nominal_Basis_Matrix “e1%e2” into another Nominal_Basis_Matrix


“E%E” to form a Nominal_Basis_Submatrix “(e1%e2)*(E%E) = X”. A Basis_Submatrix “X(0)”
is used to project a Matrix “a1” to form a Nominal_Submatrix “a1*X(0)”.

56 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects

(E[0]%E[0])*(e1%e2)

e1%e2

“*”

E[0]%E[0]
a1*(E[0]%E[0])*(e1%e2)
0.0 0.0 1.0 0.0 2.0 0.0 3.0 0.0 4.0 0.0 5.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
10. 0.0 11. 0.0 12. 0.0 13. 0.0 14. 0.0 15. 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

(E[0]%E[0])*(e1%e2) 20. 0.0 21. 0.0 22. 0.0 23. 0.0 24. 0.0 25. 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 33. 0.0 34. 0.0 35. 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0

“*”

a1
0.0 1.0 2.0 3.0 4.0 5.0
10. 11. 12. 13. 14. 15.
20. 21. 22. 23. 24. 25.
30. 31. 32. 33. 34. 35.

Figure 1•28 Projection of a Basis_Matrix “E[0]%E[0]” into a Nominal_Basis_Matrix “e1%e2” to


form a Basis_Submatrix “(E[0]%E[0])*(e1%e2)”. The Basis_Submatrix is used to project a
Matrix “a1” to form a Nominal_Submatrix “a1*((E[0]%E[0])*(e1%e2))”.

Workbook of Applications in VectorSpace C++ Library 57


Chapter 1 Computational Linear Algebra Using C0 Type Objects

(E%E)*(e1%e2) = Y

e1%e2

“*”

E%E
a1*Y[0][0]
0.0 0.0 1.0 0.0 2.0 0.0 3.0 0.0 4.0 0.0 5.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
10. 0.0 11. 0.0 12. 0.0 13. 0.0 14. 0.0 15. 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

Y[0][0] 20. 0.0 21. 0.0 22. 0.0 23. 0.0 24. 0.0 25. 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 33. 0.0 34. 0.0 35. 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0

“*”

a1
0.0 1.0 2.0 3.0 4.0 5.0
10. 11. 12. 13. 14. 15.
20. 21. 22. 23. 24. 25.
30. 31. 32. 33. 34. 35.

Figure 1•29 Projection of a Nominal_Basis_Matrix “E%E” into another Nominal_Basis_Matrix


“e1%e2” to form a Nominal_Basis_Submatrix “(E%E)*(e1%e2) = Y”. The Basis_Submatrix
Y[0][0] is used to project a Matrix “a1” to form a Nominal_Submatrix “a1*Y[0][0]”.

58 Workbook of Applications in VectorSpace C++ Library


C0 Type Objects
In the case of the Subvector/Submatrix and the extended Basis the objects and their names become quite
complicate. However, the design of these complicated objects are driven entirely by practical need in real-world
applications. Chapter 4 and Chapter 5 on the finite element method make extensive use of them.
We have introduced the primary objects including Scalar, Vector, and Matrix. These primary objects together
with the utility objects make up the Basis expression up to the second order such as “e” and “e ⊗ e”. They are
very commonly used in mathematics and physics. Subvector and Submatrix are very popular in engineering liter-
atures. The extended basis expression we just introduced is implemented in accordance with the spirit of the use
of Subvector and Submatrix that higher order tensors are tacitly avoided, and the resultant objects are the Sub-
vector and Submatrix.

Workbook of Applications in VectorSpace C++ Library 59


Chapter 1 Computational Linear Algebra Using C0 Type Objects
1.2 Applications of Computational Matrix Algebra

1.2.1 Solution of Simultaneous Linear Equations: LU Decomposition


The LU decomposition is the most basic matrix solver that has its root in Gauss elimination method. Two
examples are shown in this section. The first example is the solution of electric current of an electric circuit. A
Wheatstone bridge that can not be solved without resorting to solution of simultaneous equations is used. The
second example is a two dimensional heat conduction problem using finite difference method. Compared to the
electric circuit problem this example is not that trivial. The mathematical equations and the geometry of the
problem have a much more complicated relation. This more complicated relation gives us an opportunity to
underline the importance of the object-oriented programming. In many numerical problems, such as finite differ-
ence method, we are not just dealing with mathematics. There are a lot of non-numerical algorithms to be dealt
with. As the current programming paradigm, object-oriented programming is strongly supported by C++ lan-
guage.

Electric Circuit—Wheatstone Bridge


First let’s look at total resistance of a parallel circuit as shown in Figure 1•301. From Ohm’s law

I1 = V / R1, I2 = V / R2, I3 = V / R3.

- I1 I2 I3

V R1 R2 R3

+
I1+ I2 + I3

Figure 1•30 Parallel circuit with three resistances and a voltage source.

The total resistance of this parallel circuit R|| is

R|| = V / (I1 + I2 + I3) = V / (V / R1 + V / R2 + V / R3)

Therefore,

1 / R|| = 1 / R1 + 1 / R2 + 1 / R3 Eq. 1•38

1. Examples taken from H.V. Malmstadt, C.G. Enke, and E.C. Toren, Jr., 1963, “ Eletronics for Scientists, Principles and
Experiments for Those who Use instruments.”, W.A. Benjamin, Inc., New York, p. 536-540.

60 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra

Step 2:
2.5
5

90 90 x 100
40 5
40 5 90 + 100
10 60 20 = 47.4
20 30

Step 3:

Step 1: 40 2.5 + 20 + 47.4


5 x 5 = 2.5 = 69.9
5+5

Step 4:
40 5
100
90 25.4
20

Figure 1•31 Series and parallel resistances circuit reduction steps.

This last formula is applicable for parallel resistances of two or more. A reduction procedure can be applied to a
more complicated network of circuit (see Figure 1•31). In four steps, we can reduce the resistances of the circuit
to a total resistance. The total current can be calculated. Then, we can work backwards to resolve the currents in
each part of the circuit. However we may not be always very fortunate like in the above case. Some circuits can
not be reduced merely by applying series resistances (simple addition) and parallel resistances formula (Eq.
1•38). For example, a Wheatstone bridge in Figure 1•32 provides a good example that can only be solved by a
linear algebraic approach.
The most important law with regarding to the calculation of the electric current in this circuit is the Kirch-
hoff’s law. This law says (1) The sum of all the current flowing toward and out of a node is zero. (2) The sum of
all the voltages from the source equals the sum of all the voltages drop in any loop. For solving this problem the
direction of current I4 is temporary assumed to go from node 2 to node 3. Applying the first part of the Kirch-
hoff’s law to node 1, node 2, and node 3, respectively

I1 = I2 + I3
I2 = I4 + I5
I6 = I3 + I4 Eq. 1•39

From the second part of the Kirchhoff’s law, note that V = IR, loops L1, L2, and L3, have the following relations.

Workbook of Applications in VectorSpace C++ Library 61


Chapter 1 Computational Linear Algebra Using C0 Type Objects
3 = 30 I3 + 100 I6
0 = -30 I3 +10 I4 +50 I2
0 = -200 I5 + 10 I4 + 100 I6 Eq. 1•40

Loop L1 has a source voltage of 3 volts. L2 and L3 are both “0”. Eq. 1•39 and Eq. 1•40 is implemented in C++ as

double v[6] = { 0.0, 0.0, 0.0, 3.0, 0.0, 0.0}; // (project: “electric_circuit”)
C0 M(6, 6, (double*)0), V(6, v);
Eqn0 = M[0], Eqn1 = M[1], Eqn2 = M[2], // aliases; copy constructor is always
Eqn3 = M[3], Eqn4 = M[4], Eqn5 = M[5]; // by reference in VectorSpace
Eqn0[0] = 1.0; Eqn0[1] = -1.0; Eqn0[2] = -1.0; // I1 = I2 + I3
Eqn1[1] = 1.0; Eqn1[3] = -1.0; Eqn1[4] = -1.0; // I2 = I4 + I5
Eqn2[5] = 1.0; Eqn2[2] = -1.0; Eqn2[3] = -1.0; // I6 = I3 + I4
Eqn3[2] = 30.0; Eqn3[5] = 100.0; // 3 = 30 I3 + 100 I6
Eqn4[2] = -30.0; Eqn4[3] = 10.0; Eqn4[1] = 50.0; // 0 = -30 I3 +10 I4 +50 I2
Eqn5[4] = -200.0; Eqn5[3] = 10.0; Eqn5[5] = 100.0; // 0 = -200 I5 + 10 I4 + 100 I6
C0 I = V / M; // default solver is the LU decomposition
cout << I << endl; // {0.0351158, 0.0130105, 0.0221053, 1.26316e-2
// 0.0117474, 2.33684e-2}T

Notice that Eqn“n” are aliases to the rows of Matrix “M”. This is in accordance with the implementation of Vec-
torSpace C++ Library where the copy constructor, for example, invoked by the statement “C0 Eqn0 = M[0];”, is
always performed as copy by reference. In other words, if the copy constructor is performed as by value, Eqn“n”
will not be the aliases to the rows of Matrix “M”. However, assigning the coefficients of the matrix in the above
is not only mathematically unsound, but also aesthetically un-pleasant. This can be improved by using the basis
expression as (project: “electric_circuit”)

C0 e(6); // Basis of dimension = 6


Eqn0 = 1.0*e[0] - 1.0*e[1] - 1.0*e[2]; // I1 = I2 + I3
Eqn1 = 1.0*e[1] -1.0* e[3] - 1.0*e[4]; // I2 = I4 + I5
Eqn2 = 1.0*e[5] - 1.0*e[2] - 1.0*e[3]; // I6 = I3 + I4
Eqn3 = 30.0* e[2] + 100.0* e[5]; // 3 = 30 I3 + 100 I6
4

100Ω 200Ω
- I6 L3 I5
10Ω
L1 2
3 Volts 3 I4
L2 50Ω
+ 30Ω
I1 I3
I2
1
Figure 1•32 Calculation of electric current in Wheatstone bridge.

62 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra
Eqn4 = -30.0* e[2] + 10.0* e[3] + 50.0* e[1] // 0 = -30 I3 +10 I4 +50 I2
Eqn5 = -200.0* e[4] + 10.0* e[3] + 100.0*e[5]; // 0 = -200 I5 + 10 I4 + 100 I6

These C++ statements are more readily to be compared with Eq. 1•39 and Eq. 1•40. Therefore, the programming
task should be much transparent and the finishing code is much more readable and maintainable. The linear alge-
braic approach to the Wheatstone bridge certainly is also applicable to the previous less complicated circuit net-
work. It is a more systematic approach to the general circuit network problem. We note by passing that the same
idea on the partial elimination of a part of a circuit network can be studied systematically with graph theory. The
graph theory are also used in developing efficient algorithms for the solution of sparse matrix1.

Finite Difference for 2-D Heat Conduction


For a two dimensional heat conduction problem2, the governing equation is

∂T 2 ∂T 2 q
--------- + λ --------- = – ---- Eq. 1•41
∂x 2 ∂y 2 K

where λ is the ratio for anisotropic heat conduction in two coordinate directions, q is the intensity of internal heat
source, and K is the thermal diffusivity. The boundary conditions of this problem are shown in Figure 1•33. The
geometry of the domain is a square area of 2 x 2 square units, with material properties of λ = 3, q / K = 16. The
temperature condition on both the right-edge and the left-edge is fixed at zero. This kind of boundary condition is
the so-called essential (or Dirichlet) boundary condition, also known as boundary condition of the first kind. In
particular, the essential condition that vanishes (with zero value) is also known as the homogeneous boundary
condition. The upper and lower-edges have boundary conditions of ∂T / ∂y = -T. Due to the symmetry of the
problem only a quarter of the area needs to be modeled. It is shown in the right-hand-side of the Figure 1•33 that
X-axis and the Y-axis are also the lines of symmetry. Therefore the lower-edge and the left-edge of this reduced
model have the boundary conditions ∂T / ∂y = 0 and ∂T / ∂x = 0, respectively. This kind of boundary condition is
the so-called natural (or Neumann) boundary condition, also known as boundary condition of the second kind. In
the present case, the zero-flux on these boundaries physically means to impose insulation walls. The boundary
condition on the upper-edge is ∂T / ∂y = -T, which mixes the independent variable, “T”, and the derivative of the
independent variable, “ ∂T / ∂y ”, in linear combination. This kind of boundary condition is known as the mixed
boundary condition or boundary condition of the third kind.
The grid size for the above problem is h = 1/4. The finite difference approximation, with the central differ-
ence scheme, to the Eq. 1•41, is

1 λ q
----- ( T i + 1, j – 2T i, j + T i – 1, j ) + ----- ( T i, j + 1 – 2T i, j + T i, j – 1 ) = – ---- Eq. 1•42
h2 h2 K

1. For example, Chapter 4 and Chapter 5 in S. Pissanetsky, 1984, “Sparse Matrix Technology”, Academic Press, New York.
2. Example from G.D. Smith, 1985, “Numerical Solution of Partial Differential Equations: Finite Difference Methods”, p.
242-245.

Workbook of Applications in VectorSpace C++ Library 63


Chapter 1 Computational Linear Algebra Using C0 Type Objects

B.C. of 3rd kind


∂T
-4 -5 ------ = -T
∂y ∂T
------ = -T
∂y
-3 -2 -1 0 1 2 3
0 1 2 3
-5 4 5 6 7

4 5 6 7 B.C. of
-9 8 9 10 11 ∂T 1st kind
------ = 0
T=0 T=0 ∂x 8 9 10 11 T=0
-13 12 13 14 15
12 13 14 15
-17 16 17 18 19
16 17 18 19
-13 -12 -13 -14 -15
∂T
------ = 0
∂y

B.C. of 2nd kind

∂T
------ = -T
∂y

Figure 1•33 Boundary value problem of heat conduction with all three kinds of boundary conditions.

where “i” and “j” are row and column indices to the nodal positions. With the grid size (h = 1/4) and material
properties (λ = 3, and q / K = 16) give in the above, we obtain

Ti+1,j + 3 Ti,j+1 + Ti-1,j +3 Ti,j-1 -8 Tij = -1 Eq. 1•43

Eq. 1•43 leads to a finite difference stencil

0 3 0 0 3T i – 1, j 0
ˆT = – 1 ⇒ T Eq. 1•44
1 –8 1 i, j – 1 – 8T i, j T i, j + 1 = – 1
0 3 0 0 3T i + 1, j 0

For any node with Tij , the finite difference stencil in the left-hand-side maps the corresponding coefficients to
the node around it. Although the finite difference stencil is extremely simple, which does not involve any high-
flown mathematics, treatment of the boundary conditions in finite difference method is notoriously non-system-
atic comparing to that in finite element method. The specification of boundary conditions in finite difference
method is full of trivial details pertaining to each problem. We simply can’t completely encapsulate these com-
plexities from users.

64 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra
Essential Boundary Conditions : We have homogeneous boundary conditions on the right-edge of the reduced
model. Nothing needs to be done for them. If instead of the homogeneous boundary conditions we have essential
boundary condition T = T. As a more general essential boundary condition, the left-hand-side vector “V” needs
to be modified. For example, for node number “3” next to the right-edge (corresponding to the fourth row in the
left-hand-side matrix, and the fourth element of the right-hand-side vector) we need to modify

V[3] -= T

where the essential boundary condition is to the right of the “center-node”(3). The “right-node” has the coeffi-
cient “1” in the finite difference stencil. Therefore, the coefficient “1” multiplies the essential boundary condition
T is T. Then, shift to the right-hand-side of the equation number “3” by changing sign. We use replacement sub-
traction, “-=”, to subtract this amount out of the corresponding element in the right-hand-side vector, “V[3]”.

Natural Boundary Conditions : The left-edge and the lower-edge of the reduced model has zero natural boundary
conditions that can be considered as having the same nodes on the opposite side of the boundaries, considering
the boundaries as lines of symmetry. For example, for node number “8”, we can just specify both its “left-node”
and “right-node” as node number “9”. Therefore, the corresponding coefficients in the finite difference stencil
will be all added to the left-hand-side matrix corresponding to the node number “9”. In a more general case, for
non-zero flux, the natural boundary condition will be - ∂T / ∂x = q, where “q” is the outward heat flux. In this
more general case

(T-9 - T9) / 2 h = 2 (T-9 - T9) = -q; i.e., T-9 = T9 -q / 2

This shows “T-9” can be represented by “T9 -q / 2”. For the first term, “T9”, we can specify the “left-node” (-9) as
the node number “9”. For the second term, “-q / 2”, the right-hand-side vector corresponding to the “center-
node” (8) needs to be modified as V[8] += q / 2. The quantity “-q / 2” has been shifted to the right-hand-side vec-
tor “V”, therefore, a change of sign is necessary.

Mixed Boundary Conditions : The boundary condition of third kind on top gives, for example, the current node
number “0”,

(T-4 - T4) / 2h = 2 (T-4 - T4) = -T0

Therefore,

T-4 = T4 - 0.5 T0 Eq. 1•45

with “upper-node” specified as node number “4”. The left-hand-side matrix corresponding to the “center-node”
(0) needs to be modified with M[0][0] -= 3.0*0.5. Notice that the coefficient in the finite difference stencil corre-
sponding to the “upper-node” (-4) is “3.0”.
Program Listing 1•8 is the implementation of the above finite difference problem. The global left-hand-side
matrix “M” and the global right-hand-side vector “V” are declared static that only one copy of them is allowed in
the program for memory space consideration. At the heart of this code, we use object-oriented model to map the
finite difference stencil in Eq. 1•44 to the global matrix and global vector. The class name is “FD”. Two private

Workbook of Applications in VectorSpace C++ Library 65


Chapter 1 Computational Linear Algebra Using C0 Type Objects

#include “c0_init.h”
#define ndf 20 global left-hand-side matrix and
static C0 M(ndf, ndf, (double*)0); global right-hand-side vector
static C0 V(ndf, (double*)0);
class FD { // finite difference stencil finite difference stencil mapping class
C0 *the_M_ptr, *the_V_ptr; reference to M and V pointers
public:
FD(C0* M_ptr, C0* V_ptr) : the_M_ptr(M_ptr), the_V_ptr(V_ptr) {} constructor
void add(int, int, int, int, int);
mapping finite difference stencil
};
void FD::add(int i0, int, i1, int i2, int i3, int i4) {
center node
(*the_M_ptr)[i0][i0] -= 8.0; (*the_V_ptr)[i0][i0] = -1;
if(i1>=0) (*the_M_ptr)[i0][i1] += 1.0; if(i2>=0) (*the_M_ptr)[i0][i2] += 3.0;
left and bottom nodes
if(i3>=0) (*the_M_ptr)[i0][i3] += 1.0; if(i4>=0) (*the_M_ptr)[i0][i4] += 3.0; right node and top nodes
}
int main() {
FD fd(&M, &V); // I. Problem Definition Phase
fd.add(0, 1, 4, 1, 4); fd.add(1, 0, 5, 2, 5); fd.add(2, 1, 6, 3, 6); fd.add(3, 2, 7, -1, 7); first row
fd.add(4, 5, 8, 5, 0); fd.add(5, 4, 9, 6, 1); fd.add(6, 5, 10, 7, 2); fd.add(7, 6, 11, -1, 3); second row
fd.add(8, 9, 12, 9, 4); fd.add(9, 8, 13, 10, 5); d.add(10, 9, 14, 11, 6); fd.add(11, 10, 15, -1, 7); third row
fd.add(12,13,16,13,8); fd.add(13,12,17,14,9); fd.add(14,13,18,15,10); fd.add(15,14,19,-1,11);
fourth row
fd.add(16,17,12,17,12);fd.add(17,16,13,18,13);fd.add(18,17,14,19,14);fd.add(19,18,15,-1,15);
fifth row
for(int i = 0; i < 4; i++) M[i][i] -= 1.5;
B.C. of the third kind modification
C0 T = V / M; // II. Solution Phase
for(int i = 0; i < 5; i++) { // III. Output Phase
matrix solver
for(int j = 0; j < 4; j++) output: (node#, T)
cout << “(“ << (i*4+j) << “, “ << T[i*4+j] << “) “; (0, 3.06), (1, 2.91), (2, 2.42), (3, 1.50),
cout << endl; (4, 3.72), (5, 3.53), (6, 2.92), (7, 1.80),
} (8, 4.17), (9, 3.95), (10, 3.26), (11, 2.0),
return 0; (12,4.43),(13,4.20),(14,3.46),(15, 2.11),
} (16,4.52),(17,4.28),(18,3.52),(19,2.15)

Listing 1•8 Finite difference method for 2-d heat conduction problem (project: “finite_difference”).
member data are C0 pointers to the global matrix and the global vector. The constructor of “FD” will establish
the link between the class and the global objects by “FD fd(&M, &V);”. The only public member function in
“FD” is FD::add(int, int, int, int, int). The “FD::add(int, int, int, int, int)” will assemble and add the correspond-
ing coefficient to the global matrix and the global vector according to the finite difference stencil. The five inte-
ger arguments on the function interface represent the node numbers of the center, left, lower, right, and upper
nodes. For a typical interior node “5”, as shown in Figure 1•33, it can be written as

fd.add(5, 4, 9, 6, 1);

The node numbers “4”, “9”, “6”, and “1” are the node numbers relative in their position to the node “5”. For a
homogeneous boundary condition node such as node “7”

66 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra
fd.add(7, 6, 11, -1, 3);

We note that “-1” is in place of the right-node relative to node “7”. Any negative integer value can be used to
suppress the corresponding coefficient in finite difference stencil to be added to the left-hand-side matrix. For the
natural boundary conditions on the left-edge and lower-edge, considering the symmetry of node “8” for example,
we can specify

fd.add(8, 9, 12, 9, 4)

The “right-node” (9) on the right-hand-side of the “center-node” (8) is also used as the “left- node”. In more gen-
eral case, the natural boundary condition is - ∂T / ∂x = q, where “ q” is the outward heat flux and (T-9 - T9) / 2 h =
2 (T-9 - T9) = -q; i.e., T-9 = T9 -q / 2. We may write in C++ together with “FD::add()” as

fd.add(8, 9, 12, 9 4); V[8] += q /2;

Similarly, for the boundary condition of the third kind, using node “1” as an example is

fd.add(1, 0, 5, 2, 5); M[1][1] -= 1.5; Eq. 1•46

Eq. 1•45 for current node “1” instead of “0” is

T-5 = T5 - 0.5 T1 Eq. 1•47

To represent the top node, node “5” is used for the “upper-node” that accounts for the first term in Eq. 1•47. In
fact, the finite difference stencil coefficient of “3” on the top node position will be added to the left-hand-side
matrix at the corresponding elements. For the second term, we need to modify with the finite difference stencil
coefficient “3”, then, multiply by the second term in Eq. 1•47 as “3 × (- 0.5 T1) = -1.5 T1” that has not been
accounted for by the class “FD”. In the second part of the Eq. 1•46 “M[1][1] -= 1.5;” performs this modification.
The result of this problem is shown in Figure 1•34. The finite difference grid is drawn on the top of the box.
The height of the shaded surface represents the value of the temperature. The basic feature of the solution is a
warp-up surface along the center due to internal heat generation of “q” in Eq. 1•42. The homogenous boundary
condition on the two sides of the problem pin down the surface to zero. The surface has a concave shape along
the Y-axis, which means that the heat is lost both from the top and bottom that have the boundary condition of the
third kind.
From this simple example of implementing finite difference method, we can recognize that VectorSpace C++
Library made programming easy only on some mathematical related aspects. However, the finite difference
method is NOT intensively mathematical. The most advantage comes from the data abstraction offered by C++
that we modeled the mapping of finite difference stencil to the global matrix and vector as a class—“FD”. The
experience learned from this example tells us to stay with the current industrial flag-ship general purpose lan-
guage. We don’t want to invest our effort on a specialized environment or, even worse, a mixed environment with
more than one language to worry about. The applicability of a special purpose language on wide-ranging applica-
tion areas is most likely to break down somewhere. The advantage of object-oriented programming is where
most of the strength of applications using VectorSpace C++ Library comes from. In Chapter 3, examples on con-

Workbook of Applications in VectorSpace C++ Library 67


Chapter 1 Computational Linear Algebra Using C0 Type Objects

Y
1.0-1 X
0.0
Y
0.0 1.0

4 T
-1
1

T
2 X
1

Figure 1•34 Solution of finite difference heat conduction problem, with internal heating,
homogeneous boundary conditions on the two sides and boundary conditions of the third kind on
the top and bottom.
strained optimization and Chapter 4 and 5 on finite element method, we will show you that when an application
problem is not trivial, the power from object-oriented programming becomes increasingly critical!
Some remarks need to be made for this finite difference problem. Assuming we even use a laptop computer
for this problem, we will get the result instantly. We will not want to do anything more. However, what if you
want 1000 × 1000 grid or even greater to increase resolution? Computing time and memory space need to be
considered. Firstly, we can use a sparse matrix instead of a full matrix to represent the global matrix “M” to save
memory space. When the size (n × n) of the global matrix grows, the required memory space will grow in the
order of O(n2), and computing time of the matrix solver, in direct method, will grow in the order of O(n3). Sec-
ondly, the global matrix in this case is not only sparse but also diagonally dominant. An iterative method such as
sucessive over relaxation (SOR) can be used to search for a feasible solution.1
How to tackle large-size problem in terms of programming method? The idea is simple. Get rid of everything
offered by VectorSpace C++ Library! Simply go “back to the basics” by using plain C exclusively for ultimate
speed. However, we can start from using VectorSpace C++ Library as the first step with a smaller number of
variables like the above example. Then, in the second step, we can reverse engineer the program segment by
segment for efficiency. This two-steps programming method is known as rapid proto-typing. In the first step,
programming in VectorSpace C++ Library is much more mathematically friendly comparing to that in Fortran or

1. Introduction on the technical details of sparse matrix representation and sucessive over-relaxation (SOR) can
be found in W.H. Press, S.A. Teukolsky, W.T. Vetterling, and B.P. Flannery, 1992, “Numerical Recipes in C, the Art of Sci-
entific Computing”, 2nd ed., Cambridge University Press, UK.

68 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra
C. The proto-type program written with VectorSpace C++ Library is fully functional. The reverse engineering
step is to reduce memory space usage and computing time. During the second step, the output from the proto-
type program can be used to debug the optimized production codes.

1.2.2 Linear Least Squares: Cholesky and QR Decompositions

Car Sale Market Prediction


A regional sports car dealer would like to analyze their sale for market prediction.1 They first survey the
number of sports cars sold and the population in each area of the local dealers (see TABLE 1•4.).

Location No. of Cars—y Population—x


A 252 34,549
B 261 53,943
C 251 42,983
D 430 102,832
E 258 12,034
F 334 9,023
G 321 20,934
H 409 73023
I 243 23,294
J 322 83,543
TABLE 1•4. Car sale and population.

As a first cut, more people would buy more cars seems to be a good assumption, if no better a priori information
is available. We model the relationship of the number of cars sold and the population as a straight line

y=a+bx Eq. 1•48

where “y” is the number of cars sold, “x” is the population, and “a” and “b” are two coefficients (intercept and
slope) for a straight line. This problem is clearly over-determined. Ten data are available for just resolving two
model parameters m = {a, b}T : the intercept “a" and the slope “b”. The full rank over-determined least squares
problem is explained as projection method in Figure 1•15. The normal equation of Eq. 1•27 on page 35 can be
used to solve this problem. Program Listing 1•9 uses normal equations method to solve the least squares prob-
lem using Cholesky decomposition. This problem is not too ill-conditioned. In fact, using QR decomposition as
discussed on page 36 produces the same result, with intercept “a” = 268.92 and slope “b” = 0.0078214. The fitted
line is shown in Figure 1•35. The simple-minded assumption that the area with more population demand more

1. The abstract form of the problem in terms of “fitting a straight line” is presented in William Menke, 1984, “Geophysical
Data Analysis: Discrete Inverse Theory”, Academic Press, Inc., p.10-11.

Workbook of Applications in VectorSpace C++ Library 69


Chapter 1 Computational Linear Algebra Using C0 Type Objects

#include “include/vs.h”
int main() {
double y[10] = {421, 569, 514, 1139, 287, 543, 615, 934, 327, 918}, y obsv.
a[10][2] = { {1.0, 34549.0}, {1.0, 53943.0}, {1.0, 42983.0}, {1.0, 102832.0},
{1.0, 12034.0}, {1.0, 9023.0}, {1.0, 20934.0}, {1.0, 73023.0}, 10 rows of {1, x}
{1.0, 23294.0}, {1.0, 83543.0}};
C0 A(10, 2, a[0]), Y(10, y);
Matrix::Decomposition_Method = Matrix::Cholesky_Decomposition; Cholesky decomposition
C0 AtA = ((~A)*A),
normal equations
x = ((~A)*Y) / AtA;
mest = [ATA]-1 AT yobsv.
cout << x << endl;
= {268.92, 0.0078214}T
C0 cov_m = AtA.inverse(),
N = A * cov_m *(~A);
covu m = ATA = unit covaraince matrix
cout << cov_m << endl; N = data resolution matrix
cout << N << endl;
return 0;
}

Listing 1•9 Least squares solution using normal equations method for fitting a straight line (project:
“least_squares_line_fitting”).

sports cars in general seems to be a good prediction model. However, there are two outliers with small popula-
tion (Locations “F” and “G”) having stronger demand relative to the other areas. Some factors other than the
population may have been overlooked. We will come back to this later.

y Location D
1200
Location F&G
1000
Number
of 800
Cars
600

400

200

0 x
20000 40000 60000 80000 100000

Population
Figure 1•35 Least squares solution of fitting a straight line

The purpose of the present analysis is for market prediction. We may ask how well are the observational data
y obsv. contribute to the model we built. Since we have built a model to predict the sale of cars y pred. according to

70 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra
y pred. = A m est = A {[ATA]-1AT y obsv.} = N y obsv.

N = A [ATA]-1AT is called the data resolution matrix. “N” maps the observations, y obsv., which we collected for
the analysis to the market prediction, y pred., from the model we built. From Figure 1•36, a plot of the data resolu-
tion matrix, the diagonal elements corresponding to the collected data from each area of the local dealers, which
show the importance to their own predictions. Each row of the data resolution matrix is a linear combination with
other data to make the prediction. Therefore, if the diagonal element is the most dominant (closer to 1), it means
that the prediction around this particular data can be more independently resolved. The data resolution matrix for
the above problem shows that the most important data is at the location “D”. This is a location with the largest
population and the greatest sports car demand. This particular information is not only consistent with the model
assumption in the first place, but also helps spread out the data to cover a wider range. The location “D” is also
expected to be resolved relatively more independently, because along the row and column number 4, this loca-
tion is much greater than the rest in the row. However, along row number 4, location “H” and “J” are also relative
large. From Figure 1•35, Locations “H” & “J” are those having large population and relative large car sale. These
information are sort of duplicating what Location “D” stands for. We note that for an overdetermined problem,
we have more data than the model parameters. Therefore the model parameters can always be perfectly resolved,
while the data resolution matrix shows how well each datum is resolved
J
H

D
Location D
H
J

0.4
4

0.2
2 J10
I
0 8
H
G
A 6
F
2B C E
D
4 4 Row number
D
E
F
6 C
G
Column number 8H B
2
I
JA
10
Figure 1•36 Data resolution matrix.

Recall probability and distribution from statistics. The peak width of a distribution can be measured by using
a quadratic form of (y - y Exp)2, where superscript “Exp” denotes expectation. The variance σ2 of a distribution is
defined as

Workbook of Applications in VectorSpace C++ Library 71


Chapter 1 Computational Linear Algebra Using C0 Type Objects

σ2 = ∫–∞ ( y – yExp )2 P ( y ) dy Eq. 1•49

where P(y) is the probability function of the distribution. For the correlation of any two data the covariance is
defined similarly as

∞ ∞
cov y = cov ( yi, y j ) = ∫–∞ … ∫–∞ [ yi – y iExp ] [ y j – yjExp ]P ( y )d y1 …d y N
Eq. 1•50

Assuming all data are not correlated (off-diagonals are all zeros) and have the same variance σ2, the unit covari-
ance matrix of model parameters [covu m] is defined as mapping the uncorrelated data covariance [cov y ] to
model covariance [cov m ] with a normalized factor considered

[covu m ] ≡ σ-2 A-g [cov y ] [A-g]T = [ATA]-1 Eq. 1•51

Recall from Eq. 1•28 that A-g = [ATA]-1AT is the generalized inverse for the over-determined problem, where A-
g
is used to map “y - y Exp” in Eq. 1•50 to m. Then, [cov y ] is divided by the variance such that σ-2[cov y ] = I.
The unit covarinace matrix [covu m] indicates how much error in the data, “y - y Exp”, will be amplified into the
model parameters, m. The diagonals of the unit covariance matrix for this problem is diag [covu m ] =
{0.325285, 1.08269e-10}T. Assuming that all car sales have money back guaranteed if there is a lemon product,
and at the time of survey some sales are pending with the contingency of loan to be approved by banks. That is a
lot of uncertainty may be contained in the collected data. We test with an extremely large variance of data σd =
± 10 (σd2 = 100) for this problem, and also assume that it is the same for every location for simplicity. After the
mapping by Eq. 1•51, the diag [covu m ] remains small with intercept “a” = 268.92 ± 5.7, slope “b” = 78.214e-
4 ± 1.04e-4.
It is not uncommon to think the two seaters probably are going to be most popular among wealthy people.
The regional car dealer collected the average incomes of all locations as shown in TABLE 1•5. We assume that
the car sale is also proportional to the average income, and build a modified model as

y = a + b x1 + c x2 Eq. 1•52

No. of Sale Population Average Income


Location y x1 x2
A 421 34,549 39,028
B 569 53,943 40,239
C 514 42,983 32,984
D 1139 102,832 45,032
E 287 12,034 56,029
F 543 9,023 92,034
TABLE 1•5. Car Sale, population and average income.

72 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra

No. of Sale Population Average Income


Location y x1 x2
G 615 20,934 89,034
H 934 73,023 59,032
I 327 23,294 40,932
J 918 83,543 38,094
TABLE 1•5. Car Sale, population and average income.
Program Listing 1•10 (project: “least_squares_plane_fitting”) implemented the fitting of a plane in Eq. 1•52
with the normal equations method. The Matrix “A” now contains rows of coefficients of {1, x1, x2} instead of {1,
x}. There is almost no difference for the rest of code comparing to Program Listing 1•9.
#include “include/vs.h”
int main() {
double y[10] = {421, 569, 514, 1139, 287, 543, 615, 934, 327, 918}, y obsv.
a[10][3] = { {1.0, 34549.0, 39028.0}, {1.0, 53943.0, 40239.0},
{1.0, 42983.0, 32984.0}, {1.0, 102832.0, 45032.0}, 10 rows of {1, x1, x2}
{1.0, 12034.0, 56029.0}, {1.0, 9023.0, 92034.0},
{1.0, 20934.0, 89034.0}, {1.0, 73023.0, 59032.0},
{1.0, 23294.0, 40932.0}, {1.0, 83543.0, 38094.0}}; Cholesky decomposition
C0 A(10, 3, a[0]), Y(10, y);
normal equations
Matrix::Decomposition_Method = Matrix::Cholesky_Decomposition;
m est ={-183.61, 9.9271e-3, 6.695e-3}T
C0 AtA = ((~A)*A),
covu m = unit covaraince matrix
x = ((~A)*V) / AtA;
cout << x << endl;
N = data resolution matrix
C0 cov_m = AtA.inverse(),
N = A * cov_m *(~A);
cout << cov_m << endl;
cout << N << endl;
return 0;
}

Listing 1•10 Least squares solution using normal equations method for fitting a plane.

The solution of the problem is m est ={-183.61, 9.9271e-3, 6.695e-3}T. The fitted plane is shown in Figure
1•37. Compared to Figure 1•35 Location “G”&”F” now do not look that much of outliers. With added parameter
of “average income”, every data seems to be fitting into a plane much nicely. We did distill an important factor to
this problem. On the other hand, it has been said that “Give me five parameters, I can fit an elephant!”. The more
parameters we use to build a model the more nicely our data will fit the model. The nature of limited information
is inherent from the data we collected. If we try to make to many inferences from the data, we may simply have
the data beaten to death. The more statements we make the weaker they are.
The data resolution matrix of the plane-fitting problem is shown in Figure 1•38. Now besides Location “D”
the importance of Location “F” & “G” increase. They both explore the range of data to cover locations that have
small population but strong demand for the sports cars. However “F” and “G” are also plagued by the same prob-
lem that “D” has and the problem getting even worse. That is when we examine row 6 and row 7, “F” and “G”

Workbook of Applications in VectorSpace C++ Library 73


Chapter 1 Computational Linear Algebra Using C0 Type Objects

100000
80000
Income 60000
40000
20000
0

1000
00

750
50
Number
of 500
Cars
250

0
0
25000
50000
75000
Population 100000

Figure 1•37 Least squares solution of fitting a plane.

always have about the same height. Both the off-diagonals and the diagonals in data resolution matrix N are
strongly coupled. This means that “F” and “G” can not be resolved independently. This result actually is not sur-
prising. After all these two locations are small towns with predominantly high income folks living there. The
information from these two locations duplicates each other.
The diagonal vector of the unit covarinace matrix for this case is {1.77863, 1.39737e-10, 3.18123e-10}T.
Assuming the same large variance for every data collected as in the straight-line fitting problem (σd = ± 10; i.e.,
σd2 = 100), the mapping of the variance from data to model parameter gives “a” = -183.606 ± 13.33, “b” =
0.00992712 ± 0.0001182, and “c” = 0.0066951 ± 0.0001784. It seems the model we built is pretty robust even
when it is subject to large error or uncertainty in the collected data.

Computer Tomography
Computer Tomography (CT) is commonly used in medical practice today. In this section we study a much
simplified hypothetical acoustic tomographic1 problem to show the essence of mathematical treatment that CT
uses. Sound devices are used instead of X-ray, and the source and receiver arrangement is very primitive com-
pared to that of medical CT.
Define the slowness “s” as the inverse of the velocity

s=1/v Eq. 1•53

1. problem after William Menke, 1984, “Geophysical Data Analysis: Discrete Inverse Theory”, Academic Press, Inc., p.11-
14 and p. 182-186.

74 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra

Location “F” & “G”

0.4
4

0.2
.2 10

0 8

6
2 Row number
4 4
Column number 6
8 2
10
Figure 1•38 Data resolution matrix for fitting a plane.

The problem illustrated in Figure 1•39 has 10x10 blocks of size h (=1.0). 91 unshaded blocks have low velocity
(v = 10), and 3 × 3 shaded blocks have high velocity (v = 12). The measurements are carried out either row-wise
or column-wise. The travel time for the i-th measurement is

ti = h si0 + h si1 + h si2 + ... + h si9 Eq. 1•54

Indices 0 to 9 denote the consecutive blocks along a row or a column, and “h” is the size of the blocks. Therefore,
we will solve the slowness “s” of each block given travel time “t” from measurements.
There are 100 slowness variables associated with each block and we only have twenty measurements (10
row-wise + 10 column-wise measurements). The problem is underdetermined. The 10x10 blocks is completely
surrounded and dominated by low velocity blocks (v = 10). We work on a solution that is a perturbation to an
entirely low velocity solution

s est = s a priori + A-g [t - A s a priori] = s a priori + A-g ∆t Eq. 1•55

where ∆t = t - A s a priori. We define ∆s = s est - s a priori, where s a priori is a vector of length = 100 with the slow-
ness of “0.1=1/10” (low velocity is a priori information for all blocks). t is a vector of length = 20 for the travel
time measurements. The A-g in Eq. 1•55 is defined as

A-g = AT[AAT]-1 Eq. 1•56

That is Eq. 1•56 is the generalized inverse for the underdetermined problem. The derivation of Eq. 1•56 is dis-
cussed in the following. Since the problem is underdetermined, a common a priori assumption is that the solu-
tion is “simple” that its Euclidean norm, ||∆s||2 = ∆sT ∆s = Σ ∆si, is minimized. The problem solved is a standard
constrained optimization problem (detailed in Chapter 2):

Minimized ||∆s||2 subject to the constraint c = ∆t - A ∆s = 0.

Workbook of Applications in VectorSpace C++ Library 75


Chapter 1 Computational Linear Algebra Using C0 Type Objects

0 1 2 3 4 5 6 7 8 9

10 11 12 13 14 15 16 17 18 19

20 21 22 23 24 25 26 27 28 29
Source
30 31 32 33 34 35 36 37 38 39

43
Receiver
40 41 42 44 45 46 47 48 49

50 51 52 53 54 55 56 57 58 59

60 61 62 63 64 65 66 67 68 69

70 71 72 73 74 75 76 77 78 79

80 81 82 83 84 85 86 87 88 89

90 91 92 93 94 95 96 97 98 99

Figure 1•39 10x10 blocks with 91 unshaded blocks as low velocity (10) and 3x3
crossed hatched blocks as high velocity (12). Twenty travel time data are collected
from either row-wise or column-wise arrangement of devices of source and receiver.

An objective functional can be constructed using Lagrange multiplier method (with “m” as number of solution
varialbes and “n” number of observations)

m n m
f(∆s) = ∆s 2 +λ c = T
∑ ∆s i2 + ∑ λi ∆t i – ∑ Aij ∆sj Eq. 1•57
i=1 i=1 j=1

where λ is the Lagrange multiplier. Take the derivative of the objective function “f” with respect to ∆s and set
this derivative to zero for minimization
m n m n
∂f ∂∆s i ∂∆s j
------------ =
∂∆s k ∑ 2 ------------ ∆s i –
∂∆s k ∑ λi ∑ A ij ------------ = 2∆s k –
∂∆s k ∑ λi Aik = 0 Eq. 1•58
i=1 i=1 j=1 i=1

Written in matrix form Eq. 1•58 is

2 ∆s = ATλ Eq. 1•59

Therefore, we are facing solving two equations simultaneously

76 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra
2 ∆s - ATλ = 0
A∆s = ∆t

As in a constrained optimization problem the Lagrange multiplier λ can be eliminated from the equations. Sub-
stituting ∆s in the first equation into the second equation yields
A ATλ / 2 = ∆t

Hence, λ = 2[A AT]-1∆t, and λ is eliminated by substituting this back to the first equation

∆s = AT[AAT]-1 ∆t = A-g ∆t

This is the proof for Eq. 1•56 that defines the generalized inverse of an underdetermined problem as A-g =
AT[AAT]-1.
Program Listing 1•11 implements the tomographic problem. The kernel of this program is the class “Ray”.
This class maps the blocks, that the sound passes, to a global matrix according to Eq. 1•54. The first ten argu-
ments are the block numbers. The last argument is the size of the block. The computation of the solution in terms
of slowness is straight forward according to Eq. 1•55.
// minimum Euclidean norm solution minimum Euclidean norm solution
C0 ds = (~A)*(dT /(A*(~A)); slowness
cout.precision(6); output slowness solution
for(int i = 0; i < 10; i++)
for(int j = 0; j < 10; j++) cout << (0.1+ds[i*10+j]) << “, “;
cout << endl;
}
// model resolution matrix
model resolution matrix
C0 R = (~A)*(A*(~A)).inverse()*A;
// output row # 33 of the model resolution matrix in matrix form output row # 33 of model resolution
cout << “{ “ << endl; matrix and rearranged the values in the
for(int i 0; i < 10; i++) row into a matrix form (10x10).
cout << “{“;
for(int j = 0; j < 10; j++) {
if(j != 9) cout << (R[33][i*10+j]) << endl << “, “;
else cout << (R[33][i*10+j]);
}
if(i != 9) cout << “}, “ << endl;
else cout << “}” << endl;
}
cout << “}” << endl;
return 0;
}

Listing 1•11 Acoustic tomography with row and column measurements (project: “acoustic_tomography_1”).

Workbook of Applications in VectorSpace C++ Library 77


Chapter 1 Computational Linear Algebra Using C0 Type Objects
For an underdetermined problem, there are more model parameters than data. Therefore, data ∆t can always
be fitted to the model perfectly. We may want to know, contrary to the over-determined problem, how well are
the model parameters ∆sest been resolved.

∆sest = A-g ∆t =( AT[AAT]-1) A∆strue = R ∆strue

where

R= AT[AAT]-1 A Eq. 1•60

is the model resolution matrix that indicates the closeness of our solution ∆sest to the true solution ∆strue. As for
the data resolution matrix, the diagonals of the model resolution matrix show the importance of a particular
model parameter. If the model resolution matrix is close to I, it means the model parameters are nearly perfectly
resolved. In Program Listing 1•11, we chose to print out information corresponding to the center block of the
high velocity (block number 33 in Figure 1•39), the row number 33 of the model resolution matrix.

#include “include/vs.h”
#define ndf 100 100 blocks
#define neqn 20 20 measurements
static C0 A(neqn, ndf, (double*)0); global matrix
static int eqn = 0; global matrix row index
class Ray { // Map blocks that pass by sound to global matrix class “Ray” to map blocks in the sound
C0 *the_A;
path to the global matrix.
public:
Ray(C0* A) : the_A(A) {}
void add(int, int, int, int, int, int, int, int, int, int, double = 1.0);
10 block numbers, and grid size
};
void Ray::add(int i0, inti1, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, double h) { add() used to map to global matrix
(*the_A)[eqn][i0] = (*the_A)[eqn][i1] = (*the_A)[eqn][i2] = (*the_A)[eqn][i3] =
(*the_A)[eqn][i4] = (*the_A)[eqn][i5] = (*the_A)[eqn][i6] = (*the_A)[eqn][i7] =
(*the_A)[eqn][i8] = (*the_A)[eqn][i9] = h;
eqn++; increase global matrix row index
};
int main() { measurements: ∆t
C0 dT(neqn, (double*)0);
Ray ray(&A);
// add row path
// add rows
for(int i = 0; i < 10; i++)
ray.add(i*10, i*10+1, i*10+2, i*10+3, i*10+4,
i*10+5, i*10+6, i*10+7, i*10+8, i*10+9);
// add columns // add column path
for(int i = 0; i < 10; i++)
ray.add(i, i+10, i+20, i+30, i+40,
i+50, i+60, i+70, i+80, i+90); assign measurement values
for(int i = 0; i < neqn; i++) dT[i] = 0.0;
dT[2] = dT[3] = dT[4] = dT[12] = dT[13] = dT[14] = -0.05;

78 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra
The result is shown in Figure 1•40. The upper-left part shows the “true” model of slowness that we used to
cook up the row-wise and column-wise travel time data for the Vector “dT”. The lower-left is the solution using
the minimum Euclidean norm solution method from Eq. 1•55. The right-hand-side shows the resolution of the
center block of the 3 × 3 high velocity blocks. The “importance” of number 33 model parameter only has a value
of 0.19. Two big side-lobes of the left-hand side graphs are parallel to the row-wise and column-wise measure-
ments that pass through block number 33. From Eq. 1•54, it is mostly natural that the solution of block number
33 is a linear combination of the row-wise and column-wise blocks. Hence, block number 33 can not be resolved
independently from these blocks. The solution of slowness compared to the “true” model of slowness in Figure
1•40 is not very good, and we simply need more data to resolve the model better.
One way to get a better resolution is to include measurements on 45o and 135o angles. 38 more measurements
can be added on top of the row-wise and column-wise measurements, and we should have the resolution of the
model improve to some extent. Program Listing 1•12 is the implementation of the tomographic problem using 20
row-wise and column-wise measurements plus 38 measurements at 45o and 135o angles.
The added member function of class “Ray” is “Ray::add()”, which allows variable length of arguments. Con-
sidering the additional measurements taking on NE-SW direction, this may involve minimum 1 to maximum 10
blocks in program specification. The double arguments immediately follow the int arguments of block numbers
are the distance of ray that passes through the corresponding blocks. For example at 45o , the ray passes through
a single block, e.g., number “0”, can be simply written as

(*ray).add(0, sqrt2);

where sqrt2 has been defined by the macro definition to have the value of 2 . The ray that passes through the
NE-SW diagonal has 10 blocks on the way. It can be written as

(*ray).add(90, sqrt2, 81, sqrt2, ... , 9, sqrt2);

with ten int values and all given the length of 2 . This function is very versatile. In fact, if you begin to consider
ray path at different angles other than those we have just taken (0o, 45o, 90o, and 135o), the distances of a ray
which passes through each block can be different and the different distances need to be specified with different
blocks.
The solution is shown in the left-hand-side of Figure 1•41. Compared to the solution in Figure 1•40 the model
has been improved at least incrementally (since you can obtain even better resolution by adding more data to the
solution procedure). The model resolution matrix shown in the right-hand-side of Figure 1•41 shows that the
over-all importance of the data has been significantly improved. The value of the importance increases from
“0.19” to “0.44” a more than two times growth. However, the model resolution matrix also shows the number of
dependent blocks increases. The new pattern looks like the British national flag with 45o and 135o stripes that
corresponding to the newly slanted measurement paths superposed on the previous vertical/horizontal cross pat-
tern

Workbook of Applications in VectorSpace C++ Library 79


Chapter 1 Computational Linear Algebra Using C0 Type Objects
1.2.3 Eigenvalue Problems

Buckling of a Rod
Consider a beam1 distorted from its natural state with the application of a load value “P” as in Figure 1•42.
True model of
2
2 slowness
4
4 6
8 row #33 from Model Resolution Matrix
6 10
0.11
0
8 0.1
0
0.09
0.
10
0 0.08
0.

0.2
2
0.1
1
2
0
-0.1
.1 4
2
6
4
6 8
8
2
2 Solution of
10 10
4 slowness
4 6
8
6 10
0.11
0
8 0.1
0
0.09
0.
10
0 0.08
0.

Figure 1•40 Solution of acoustic tomographic problem using minimum solution Euclidean norm.
The high velocity area is smeared out to have two pairs of big side-lobes. The left-hand side is
from the 33-rd row of model resolution matrix.

1. For an introduction on Bernoulli-Euler beam theory see J.M. Gere and S.P. Timoshenko, 1984, “Mechanics of Materials”,
2nd ed., Wadsworth, Inc., California, p. 553-8, and p. 680 for a brief historical account.

80 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra

#include “include/vs.h”
#define ndf 100 100 blocks
#define neqn 58
58 measurements
static C0 A(neqn, ndf, (double*)0);
global matrix
static int eqn = 0;
class Ray { // Map block that pass by sound to global matrix
global matrix row index
C0 *the_A;
class “Ray” to map blocks in the sound
public: path to the global matrix.
Ray(C0* A) : the_A(A) {}
void add(int, int, int, int, int, int, int, int, int, int, double = 1.0); 10 block numbers, and grid size
void add(int, double, int = -1, double = 0.0, int = -1, double = 0.0, variable length of block numbers and
int = -1, double = 0.0, int = -1, double = 0.0, int = -1, double = 0.0, grid size
int = -1, double = 0.0, int = -1, double = 0.0, int = -1, double = 0.0,
int = -1, double = 0.0);
};
void Ray::add(int i0, inti1, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, double h) {
add() used to map to global matrix
(*the_A)[eqn][i0] = (*the_A)[eqn][i1] = (*the_A)[eqn][i2] = (*the_A)[eqn][i3] =
(*the_A)[eqn][i4] = (*the_A)[eqn][i5] = (*the_A)[eqn][i6] = (*the_A)[eqn][i7] =
(*the_A)[eqn][i8] = (*the_A)[eqn][i9] = h;
eqn++; increase global matrix row index
};
void Ray::add(int i0, double h0, int i1, double h1, int i2, double h2, int i3, double h3, variable length of arguments version
int i4, double h4, int i5, double h5, int i6, double h6, int i7, double h7,
int i8, double h8, int i9, double h9) { // variable length arguments version
(*the_A)[eqn][i0] = h0;
if(i1 >= 0) (*the_A)[eqn][i1] = h1; if(i2 >= 0) (*the_A)[eqn][i2] = h2;
if(i3 >= 0) (*the_A)[eqn][i3] = h3; if(i4 >= 0) (*the_A)[eqn][i4] = h4;
if(i5 >= 0) (*the_A)[eqn][i5] = h5; if(i6 >= 0) (*the_A)[eqn][i6] = h6;
if(i7 >= 0) (*the_A)[eqn][i7] = h7; if(i8 >= 0) (*the_A)[eqn][i8] = h8;
if(i9 >= 0) (*the_A)[eqn][i9] = h9;
eqn++;
}
void add_rowcol(Ray *ray, C0 *dT) { // add blocks along rows and columns; 20 measurements measurements: ∆t
// add rows
for(int i = 0; i < 10; i++) add row path
(*ray).add(i*10, i*10+1, i*10+2, i*10+3, i*10+4,
i*10+5, i*10+6, i*10+7, i*10+8, i*10+9);
// add columns
for(int i = 0; i < 10; i++)
add column path
(*ray).add(i, i+10, i+20, i+30, i+40,
i+50, i+60, i+70, i+80, i+90);
for(int i = 0; i < neqn; i++) (*dT)[i] = 0.0;
(*dT)[2] = (*dT)[3] = (*dT)[4] = (*dT)[12] = (*dT)[13] = (*dT)[14] = -0.05;
assign measurement values
}
#define sqr2 1.414213562
void add_diagonals(Ray *ray, C0 *dT) { // add 45o blocks; 38 measurements
// add NE-SW ray paths 45o measurements
(*dT)[eqn] = 0.0; (*ray).add(0, sqrt2);

Workbook of Applications in VectorSpace C++ Library 81


Chapter 1 Computational Linear Algebra Using C0 Type Objects
(*dT)[eqn] = 0.0; (*ray).add(10, sqrt2, 1, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(20, sqrt2, 11, sqrt2, 2, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(30, sqrt2, 21, sqrt2, 12, sqrt2, 3, sqrt2);
(*dT)[eqn] = sqrt2*(1.0/12.0-1.0/10.0);
(*ray).add(40, sqrt2, 31, sqrt2, 22, sqrt2, 13, sqrt2, 4, sqrt2);
(*dT)[eqn] = 2.0*sqrt2(1.0/12.0-1.0/10.0);
(*ray).add(50, sqrt2, 41, sqrt2, 32, sqrt2, 23, sqrt2, 14, sqrt2, 5, sqrt2);
(*dT)[eqn] = 3.0*sqrt2(1.0/12.0-1.0/10.0);
(*ray).add(60, sqrt2, 51, sqrt2, 42, sqrt2, 33, sqrt2, 24, sqrt2, 13, sqrt2, 6, sqrt2);
(*dT)[eqn] = 2.0*sqrt2(1.0/12.0-1.0/10.0);
(*ray).add(70, sqrt2, 61, sqrt2, 52, sqrt2, 43, sqrt2, 34, sqrt2, 25, sqrt2, 16, sqrt2,
7, sqrt2);
(*dT)[eqn] = sqrt2*(1.0/12.0-1.0/10.0);
(*ray).add(80, sqrt2, 71, sqrt2, 62, sqrt2, 53, sqrt2, 44, sqrt2, 35, sqrt2, 26, sqrt2,
17, sqrt2, 8, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(90, sqrt2, 81, sqrt2, 72, sqrt2, 63, sqrt2, 54, sqrt2, 45, sqrt2,
36, sqrt2, 27, sqrt2, 18, sqrt2, 9, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(91, sqrt2, 82, sqrt2, 73, sqrt2, 64, sqrt2, 55, sqrt2, 46, sqrt2,
37, sqrt2, 28, sqrt2, 19, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(92, sqrt2, 83, sqrt2, 74, sqrt2, 65, sqrt2, 56, sqrt2, 47, sqrt2,
38, sqrt2, 29, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(93, sqrt2, 84, sqrt2, 75, sqrt2, 66, sqrt2, 57, sqrt2, 48, sqrt2,
39, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(94, sqrt2, 85, sqrt2, 76, sqrt2, 67, sqrt2, 58, sqrt2, 49, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(95, sqrt2, 86, sqrt2, 77, sqrt2, 68, sqrt2, 59, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(96, sqrt2, 87, sqrt2, 78, sqrt2, 69, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(97, sqrt2, 88, sqrt2, 79, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(98, sqrt2, 89, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(99, sqrt2);
// add NW-SE ray paths
135o measurements
(*dT)[eqn] = 0.0; (*ray).add(9, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(8, sqrt2, 19, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(7, sqrt2, 18, sqrt2, 29, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(6, sqrt2, 17, sqrt2, 28, sqrt2, 39, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(5, sqrt2, 16, sqrt2, 27, sqrt2, 38, sqrt2, 49, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(4, sqrt2, 15, sqrt2, 26, sqrt2, 37, sqrt2, 48, sqrt2, 59, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(3, sqrt2, 14, sqrt2, 25, sqrt2, 36, sqrt2, 47, sqrt2, 58, sqrt2, 69, sqrt2);
(*dT)[eqn] = sqrt2(1.0/12.0-1.0/10.0);
(*ray).add(2, sqrt2, 13, sqrt2, 24, sqrt2, 35, sqrt2, 46, sqrt2, 57, sqrt2, 68, sqrt2,
79, sqrt2);
(*dT)[eqn] = 2.0*sqrt2*(1.0/12.0-1.0/10.0);
(*ray).add(1, sqrt2, 12, sqrt2, 23, sqrt2, 34, sqrt2, 45, sqrt2, 56, sqrt2, 67, sqrt2,
78, sqrt2, 89, sqrt2);
(*dT)[eqn] = 3.0*sqrt2*(1.0/12.0-1.0/10.0);
(*ray).add(0, sqrt2, 11, sqrt2, 22, sqrt2, 33, sqrt2, 44, sqrt2, 55, sqrt2,
66, sqrt2, 77, sqrt2, 88, sqrt2, 99, sqrt2);
(*dT)[eqn] = 2.0*sqrt2*(1.0/12.0-1.0/10.0);
(*ray).add(10, sqrt2, 21, sqrt2, 32, sqrt2, 43, sqrt2, 54, sqrt2, 65, sqrt2, 76, sqrt2,
87, sqrt2, 98, sqrt2);

82 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra
(*dT)[eqn] = sqrt2(1.0/12.0-1.0/10.0);
(*ray).add(20, sqrt2, 31, sqrt2, 42, sqrt2, 53, sqrt2, 64, sqrt2, 75, sqrt2, 86, sqrt2,
97, sqrt2);
(*dT)[eqn] = 0.0;
(*ray).add(30, sqrt2, 41, sqrt2, 52, sqrt2, 63, sqrt2, 74, sqrt2, 85, sqrt2, 96, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(40, sqrt2, 51, sqrt2, 62, sqrt2, 73, sqrt2, 84, sqrt2, 95, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(50, sqrt2, 61, sqrt2, 72, sqrt2, 83, sqrt2, 94, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(60, sqrt2, 71, sqrt2, 82, sqrt2, 93, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(70, sqrt2, 81, sqrt2, 92, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(80, sqrt2, 91, sqrt2);
(*dT)[eqn] = 0.0; (*ray).add(90, sqrt2);
eqn++;
}
int main() {
C0 dT(neqn, (double*)0);
Ray ray(&A);
add_rowcol(&ray, &dT); // add row-wise and column-wise blocks
add_diagonals(&ray, &dT); // add 45o angle measurements
// minimum Euclidean norm solution
minimum Euclidean norm solution
C0 ds = (~A)*(dT /(A*(~A));
slowness
cout.precision(6);
for(int i = 0; i < 10; i++)
output slowness solution
for(int j = 0; j < 10; j++) cout << (0.1+ds[i*10+j]) << “, “;
cout << endl;
}
// model resolution matrix model resolution matrix
C0 R = (~A)*(A*(~A)).inverse()*A;
// output row # 33 of the model resolution matrix in matrix form output row # 33 of model resolution
cout << “{ “ << endl; matrix and rearranged the values in the
for(int i 0; i < 10; i++) row into a matrix form (10x10).
cout << “{“;
for(int j = 0; j < 10; j++) {
if(j != 9) cout << (R[33][i*10+j]) << endl << “, “;
else cout << (R[33][i*10+j]);
}
if(i != 9) cout << “}, “ << endl;
else cout << “}” << endl;
}
cout << “}” << endl;
return 0;
}

Listing 1•12 Acoustic tomography with row-wise and column-wise plus 45o and 135o measurements (project:
“acoustic_tomography_2”).

Workbook of Applications in VectorSpace C++ Library 83


Chapter 1 Computational Linear Algebra Using C0 Type Objects

row #33 from Model Resolution Matrix


Solution of 2
2
slowness 4
4 6
8
6 10
0.11
0
8 0.1
0
0
0.09 0.2
2
10
0 0.08
0. 0.1
1
2
0
-0.1
.1 4
2
6
4
6 8
8
10 10
Figure 1•41 Acoustic tomographic problem with row-wise, column-wise, 45o, and 135o
measurements.
For a small “P” the rod deforms as uniform compression. When “P” is increased to a critical load value “PEuler”,
known as the “Euler load”, named after the “greatest mathematician” of all times, the rod will buckle upward or
downward if the rod is fixed in plane.
M
P P
v
P < PEuler P > PEuler
φ
Figure 1•42 Buckling of a rod. The rod buckled up when the load is increased
beyond the “Euler load”.

Let φ be the angle between tangent of the rod and the horizontal axis, and v the lateral deflection. The curva-
ture of the rod is related to the bending moment “M” and the flexure rigidity of the beam “EI” as

d2 v M
-------- = – ------
dx 2 EI
Eq. 1•61

where “E” is the Young’s modulus and “I” is the moment of inertia. For static equilibrium, M = Pv. We have

EIv’’ = – Pv Eq. 1•62

The length of the rod is L, and the boundary conditions at the ends are v(0) = v(L) = 0. We use finite difference
to approximate numerically the second order derivative in the left-hand-side of Eq. 1•62. The rod is subdivided
into “n” segments, and the size “h” of each segment is “h = L / n”. The central difference stencil for the second
order derivative of v evaluated at node xi is

84 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra

d2 v v i + 1 – 2v i + v i – 1
-------- ( x i ) = ------------------------------------------
- Eq. 1•63
dx 2 h2 x = xi

Substituting Eq. 1•63 into Eq. 1•62, we have the standard matrix form of an eigenvalue problem

Av=λv

This equation has the same form as Eq. 1•34 on page 37.
Program Listing 1•13 is the implementation of this problem with VectorSpace C++ Library. Class “FD” is the
finite difference stencil for Eq. 1•63, which is very similar to class “FD” in Program Listing 1•8. The computa-
tion of eigenvalues and eigenvectors is straight forward as has been discussed on page 37. Only the first three
eigenvalues and eigenvectors are reported to a file “rod.out”. The results of the eigen computation are shown in
Figure 1•43. These are actually eigenvalues ({λ} = {i2π2}, i = 1, 2, ...) and eigenfunctions ({sin iπx}, i = 1, 2, ...)
of the operator “-d2/dx2”. In the present case, the smallest eigenvalue and its eigenvector is the most stable. The
second and third modes on the right-hand-side of Figure 1•43 are only attainable if the inflection points are sup-
ported at the beginning of an incremental loading procedure. The branching diagram in the upper left-hand-side
of Figure 1•43 is actually quite un-realistic that the eigenvalues exist only in some discrete values and the value
of v’ can not be determined. This is caused by using Eq. 1•62, which is a linearization of a fully non-linear prob-
lem of 1

φ'' + sin φ = 0 Eq. 1•64

The fully non-linear version of the branching diagram shows pitch fork bifurcation2. The eigenvalues of the lin-
earized problem (Eq. 1•62)3 are only the branching points of the fully non-linear problem (Eq. 1•64).
If the resultant matrix is symmetrical, the so-called generalized eigenvalue problem and quadratic eigenvalue
problem can be solved by the symmetrical eigenvalue solver provided by VectorSpace C++ Library. In many
practical engineering applications, the symmetrical solver is sufficient. An example of how a generalized eigen-
value problem can be solved using the symmetrical eigenvalue solver is shown on page 234.

1. I. Stakgold, 1979, “Green’s Functions and Boundary Value Problems”, John Wiley & Sons, New York, p. 572-576.
2. J.E. Marsden and T. J.R. Hughes, 1983, “Mathematical Foundations of Elasticity”, Prentice-Hall, Inc., Englewood Cliffs,
N.J., p.427-429.
3. I. Stakgold, 1979, “Green’s Functions and Boundary Value Problems”, John Wiley & Sons, New York, p. 584.

Workbook of Applications in VectorSpace C++ Library 85


Chapter 1 Computational Linear Algebra Using C0 Type Objects

#include "include/vs.h"
#include "assert.h"
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include <iomanip.h>
static ofstream ofs("rod.out", ios::out | ios::trunc);
#define n 40 // number of segments
#define P 1.0 // loading number of segments
#define L 1.0 // length of the rod loading
#define E 1.0 // Young’s modulus length of rod
#define I 1.0 // moment of inertia Young’s modulus
static C0 M(n-2, n-2, (double*)0); moment of inertia
static const double h = L / n; global matrix
static const double k = - E * I / (P*h*h); finite difference grid size
class FD { // finite difference stencil
C0 *the_M;
class of finite difference stencil
public:
FD(C0 *M) : the_M(M) {}
void add(int, int, int);
};
void FD::add(int i_center, int i_left, int i_right) {
// finite difference stencil
if(i_center >= 0) (*the_M)[i_center][i_center] += 2.0*k; (-1, 2, -1) finite difference stencil
if(i_left >= 0) (*the_M)[i_center][i_left] += -1.0*k;
if(i_right >= 0) (*the_M)[i_center][i_right] += -1.0*k;
}
int main() {
FD fd(&M);
fd.add(0, -1, 1); // first node
for(int i = 1; i < n-3; i++) fd.add(i, i-1, i+1);
first node
fd.add(n-3, n-4, -1); // last node
middle nodes
Eigen a(M); last node
C0 lambda = a.Eigenvalues(),
x = a.Eigenvectors(); eigenvalues
ofs << “lambda:” << lambda[0] << “, “ << lambda[1] << “, “ labmda[2] << endl; eigenvectors
for(int i = 0; i < 3; i++) ofs << x(i) << endl; output first three eigenvalues and
ofs.close(); eigenvectors
return 0;
}

Listing 1•13 Finite difference implementation for buckling of rod eigenvalue analysis (project: “rod_buckling”)

86 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra
Eigenvalues Eigenvectors

v’ branching diagram of a linearized problem 0.2

0.15

0.1

|λ| 0.05

λ1 λ2 λ3
5 10 15 20 25 30 35

0.2

0.1

5 10 15 20 25 30 35

fully non-linear branching diagram -0.1

v’ -0.2

inflection points

branching points 0.2

|λ| 0.1

λ1 λ2 λ3
5 10 15 20 25 30 35

-0.1

-0.2

Figure 1•43 First three eigenvalues and eigenvectors of the rod buckling problem using
finite difference method.

Singular Value Decomposition and the Principal Components Analysis


Three simple examples of principal components analysis using singular value decomposition, namely (1)
scholastic achievement analysis, (2) stock market analysis, and (3) geomorphology analysis, are implemented
with VectorSpace C++ library in this section.

Scholastic Achievements Analysis:1 of 20 students over 6 subjects are given in TABLE 1•6. The covariance
matrix defined in Eq. 1•50 on page 72 is
∞ ∞
cov y = cov ( yi, y j ) = ∫–∞ … ∫–∞ [ yi – y iExp ] [ y j – yjExp ]P ( y )d y1 …d y N

1. A. Jennings and J.J. McKeown, 1992, “Matrix Computation”, 2nd ed., John Wiley & Sons, Inc., New York, p.181-184.

Workbook of Applications in VectorSpace C++ Library 87


Chapter 1 Computational Linear Algebra Using C0 Type Objects
In this case, the indices “i” and “j” span over six subjects that the twenty students have taken, and N = 20 is the
number of the students. We first subtract each column with its average value (in place of yExp in Eq. 1•50). The
results from a matrix X of size 20 × 6. The covarinace matrix, consistent with Eq. 1•50, is “XTX/(N-1)” of size
6× 6 .

Student Math. English Physics Geography French Art


1 50 45 41 45 46 30
2 17 29 40 43 40 22
3 60 51 49 69 58 51
4 49 70 46 63 71 44
5 64 63 52 53 68 61
6 35 55 49 44 66 47
7 51 67 48 64 64 57
8 57 69 60 52 73 60
9 99 77 58 86 81 67
10 36 60 81 74 68 52
11 43 33 40 52 45 43
12 36 39 34 55 49 44
13 37 30 47 40 52 35
14 70 48 58 55 72 60
15 78 48 60 70 61 41
16 66 72 66 73 83 66
17 49 63 55 69 55 57
18 68 50 68 62 55 47
19 43 71 68 71 71 59
20 44 34 33 51 42 53
Average 52.6 53.7 52.65 59.55 61.0 49.8
TABLE 1•6. Scores and average of 6 subjects from 20 students.
The implementation of this problem is shown in Program Listing 1•14. The result of the covariance matrix is

336.2 139.8 83.1 137.8 134.5 127.4


139.8 233.4 111.5 130.5 168.4 135.1
83.1 111.5 154.7 93.9 104.8 71.1
C = ( XTX ) ⁄ ( N – 1) = Eq. 1•65
137.8 130.5 93.9 154.1 95.0 91.4
134.5 168.4 104.8 95.0 161.6 114.3
127.4 135.1 71.1 91.4 114.3 141.4

88 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra
It is argued that the largest diagonal element is in the first row and column which means the mathematics has the
largest variance, and all of the off-diagonal elements, Eq. 1•65, are positive which means that any student who is
good at one subject has the tendency to do well in other subjects. The greatest off-diagonal is 168.4 which means
that the strongest correlation between any two subjects are French and English. The result of the singular value

#include "include/vs.h"
#define N 20
#define sub_no 6 number of students
static double s[N][sub_no] = number of subjects
{ {50.0, 45.0, 41.0, 45.0, 46.0, 30.0}, {17.0, 29.0, 40.0, 43.0, 40.0, 22.0},
{60.0, 51.0, 49.0, 69.0, 58.0, 51.0}, {49.0, 70.0, 46.0, 63.0, 71.0, 44.0},
{64.0, 63.0, 52.0, 53.0, 68.0, 61.0}, {35.0, 55.0, 49.0, 44.0, 66.0, 47.0},
{51.0, 67.0, 48.0, 64.0, 64.0, 57.0}, {57.0, 69.0, 60.0, 52.0, 73.0, 60.0},
{99.0, 77.0, 58.0, 86.0, 81.0, 67.0}, {36.0, 60.0, 81.0, 74.0, 68.0, 52.0},
{43.0, 33.0, 40.0, 52.0, 45.0, 43.0}, {36.0, 39.0, 34.0, 55.0, 49.0, 44.0},
{37.0, 30.0, 47.0, 40.0, 52.0, 35.0}, {70.0, 48.0, 58.0, 55.0, 72.0, 60.0},
{78.0, 48.0, 60.0, 70.0, 61.0, 41.0}, {66.0, 72.0, 66.0, 73.0, 83.0, 66.0},
{49.0, 63.0, 55.0, 69.0, 55.0, 57.0}, {68.0, 50.0, 68.0, 62.0, 55.0, 47.0},
{43.0, 71.0, 68.0, 71.0, 71.0, 59.0}, {44.0, 34.0, 33.0, 51.0, 42.0, 53.0}};
int main() { matrix X, and average a
C0 X(N, sub_no, s[0]), a(sub_no, (double*)0);
for(int i = 0; i < sub_no; i++) { compute average
for(int j = 0; j < N; j++) a[i] += X[j][i];
a[i] /= (double)N; Xj = Xj - a
for(int j = 0; j < N; j++) X[j][i] -= a[i]; C = ( XTX) / (N-1)
}
C0 C = (~X)*X / (N-1.0);
singular value decomposition
cout << C << endl;
SVD svd(C);
C0 sigma = svd.Singularvalues(),
x = svd.U(); {799.2,178.4,87.4, 58.9,41.6,16.0}T
cout << sigma << endl; {.518, .474, .305, .360, .401, .351}T
cout << x(0) << endl; {-.815, .389, .350, .027, .235, .077}T
cout << x(1) << endl;
return 0;
}

Listing 1•14 Principal components analysis of scholastic achievement of 20 students on 6 subjects (project:
“scholastic_achievements”).

Workbook of Applications in VectorSpace C++ Library 89


Chapter 1 Computational Linear Algebra Using C0 Type Objects
decomposition is shown in right-hand-side column of Program Listing 1•14. The first and second eigenvalues
account for

799.2 / 1181.3 = 67.7 %, and 178.4 / 1181.3 = 15.1 %

of the total variance, respectively. The all positive values of the first eigenvector reconfirm the tendency that a
student who does well in one subject will do very well in the other. The second eigenvector indicates, again, that
mathematics is the subject that has the largest variation of student performance.

Stock market1 weekly return rates from five companies give a covariance matrix as

Allied Chemical DuPont Union Carbide Exxon Texaco


Allied Chemical 1.000 0.577 0.509 0.387 0.462
DuPont 0.577 1.000 0.599 0.398 0.322
Union Carbide 0.509 0.599 1.000 0.436 0.426
Exxon 0.387 0.398 0.436 1.000 0.523
0.462 0.322 0.426 0.523 1.000
Texaco

Program Listing 1•15 is the implementation of the singular decomposition of this problem.The proportion of
total variance in the first two eigenvalues are 2.857 / 5.084 = 56 %, and 0.809 / 5.084 = 16 %. The rest of the
eigenvalues sum to only 18% of the total variance. The first two eigenvectors are

x1 = {0.464, 0.457, 0.470, 0.421, 0.421}T, and x2 = {-0.240, -0.509, -0.260, 0.526, 0.582}T.

The first eigenvector is called the market component which is an equal weighted vector of the five stocks. This
component reflects the general condition of the market. The second eigenvector is called the industry component
which reflects the contrast between the chemical and the oil industries. Although this problem can be analyzed
using eigenvalue solution as well, in general the singular value decomposition is recommended for the principal
components analysis in statistics.

Mountain Morphology :2 can be analyzed using singular value decomposition. An array of 14 mountain profiles
are digitized with 11 points on each profile and the profiles are shown in Figure 1•44. A matrix S contains this
digitized data is of size 14 × 11. S can be decomposed using singular value decomposition as

S= (U Λ) VT = CF Eq. 1•66

1. example taken from B.N. Datta, 1995, “Numerical Linear Algebra and Applications”, Brooks/Cole Publishing Company,
p.399-400.
2. example data digitized from William Menke, 1984, “ Geophysical Data Analysis: Discrete Inverse Theory”, Academic
Press Inc., Orlando, p.167-170.

90 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra

#include "include/vs.h"
int main() {
double cv[5][5] = {{1.000, 0.577, 0.509, 0.387, 0.462}, covaraince matrix
{0.577, 1.000, 0.599, 0.389, 0.322},
{0.509, 0.599, 1.000, 0.436, 0.426},
{0.387, 0.389, 0.436, 1.000, 0.523},
{0.462, 0.322, 0.426, 0.523, 1.000}};
C0 cm(5, 5, cv[0]); singular value decomposition
SVD a(cm);
C0 sigma = a.Singularvalues(),
U = a.U();
{2.857, 0.809, 0.540, 0.452, 0.343}T
cout << sigma << endl;
cout << U(0) << endl;
{0.464, 0.457, 0.470, 0.421, 0.421}T
cout << U(1) << endl; {-0.240,-0.509,-0.260,0.526,0.582}T
return 0;
}

Listing 1•15 Stock-market analysis by singular value decomposition (project: “stock_market”).

1 2 3 4 5 6

7 8 9 10 11 12

13 14
Figure 1•44 14 mountain profiles.

where C = U Λ is called factor loadings, and rows of F are the factors. Program Listing 1•14 implements
this problem using VectorSpace C++ Library.
The first three singular values in the percentage of the total are

8.56 / 16.4 = 52.2%, 2.41 / 16.4 = 14.7%, and 1.91 / 16.4 = 11.6%

These three components together account for 78.5 % of the total. The three dominant “factors” are shown in Fig-
ure 1•45. The first factor (52% strong) is called the “average mountain”, while the second and third factors are
called “skewness” and “sharpness”, respectively, for obvious reasons.
The first three components of “factor loadings” C = U Λ for each row represent the proportion of the first
three factors in each mountain profile. These three numbers of each mountain profile are plotted in a 3-D graph
as shown in Figure 1•46. This figure shows a quantitative method to analyze geometrical objects. For example,

Workbook of Applications in VectorSpace C++ Library 91


Chapter 1 Computational Linear Algebra Using C0 Type Objects
#include "include/vs.h"
static double v[14][9] =
{{0.29, 0.54, 0.83, 1.07, 1.42, 1.11, 0.82, 0.55, 0.25},
{0.56, 0.82, 0.93, 1.05, 1.23, 1.06, 0.93, 0.82, 0.55},
{0.17, 0.30, 0.87, 1.12, 1.37, 1.08, 0.86, 0.34, 0.14},
{0.23, 0.79, 1.34, 1.35, 1.26, 1.08, 0.93, 0.82, 0.55},
{0.58, 0.83, 0.96, 1.08, 1.22, 1.37, 1.39, 0.86, 0.34},
{1.35, 1.34, 1.35, 1.17, 1.15, 1.19, 0.82, 0.58, 0.25},
{0.23, 0.55, 0.84, 1.27, 1.25, 1.24, 1.39, 1.40, 1.10},
{0.02, 0.04, 0.13, 0.58, 1.36, 0.55, 0.12, 0.03, 0.02},
{0.12, 0.30, 0.58, 0.84, 1.38, 0.83, 0.57, 0.28, 0.13},
{0.09, 0.23, 0.82, 1.37, 0.81, 0.28, 0.11, 0.12, 0.02},
{0.02, 0.16, 0.16, 0.27, 0.85, 1.42, 0.85, 0.28, 0.12},
{0.01, 0.03, 0.02, 0.11, 0.12, 0.28, 0.58, 1.39, 0.58},
{0.59, 1.37, 0.58, 0.28, 0.15, 0.14, 0.03, 0.02, 0.01},
{0.03, 0.03, 0.17, 0.58, 1.38, 1.27, 1.24, 0.96, 0.68}};
int main() {
C0 S(14, 9, v[0]);
SVD a(S);
singular value decomposition
C0 sigma = a.Singularvalues(), U = a.U(), V = a.V(), C(14, 9, (double*)0);
for(int i = 0; i < 14; i++)
for(int j = 0; j < 9; j++) C[i][j] = U[i][j] * sigma[j]; C; factor loadings
C0 Sp(14, 9, (double*)0); spectral representation by the first three
for(int i = 0; i < 14; i++) principal components
for(int j = 0; j < 3; j++) Sp[i] += C[i][j] * V(j);
cout << "sigma: " << endl << sigma << endl; singular values; σ = {8.56, 2.41, 1.91,
cout << V << endl << C << endl << Sp << endl; 1.33, 0.74, 0.55, 0.37, 0.29, 0.26}T
return 0;
}
Listing 1•16 Principal components (Factor) analysis of mountain geomorphology (project:
“mountain_geomorphology”).

F1: “Average Mountain” F2: “Skewness” F3: “Sharpness”

Figure 1•45 Three dominant principal components from the 14 mountain profiles.

92 Workbook of Applications in VectorSpace C++ Library


Applications of Computational Matrix Algebra

F3(sharpness) 12

7
13
2 5 14
6
F2(skewness) 11 4 F1(average mountain)
1
9 3
10 8

Figure 1•46 The “factor loadings” of first three factors (F1, F2, and F3) of the 14 mountain profiles.

you can use this analysis to discriminate different mountain shapes caused by different erosional agents (ice,
water, or wind). Alternatively, you may want to differentiate shapes that are caused by the same agent but at dif-
ferent stages (from juvenile to mature stages) of geomorphological evolution.
On the other hand, since the first three factors account for most (78.5%) of the information of the 14 moun-
tain profiles. We can use the first three columns of the factor loadings and the first three factors to reproduce the
14 mountain profiles as

Si3 = Ci 1 F1 + Ci 2 F2 + Ci 1 F3, (i = 1, ..., N)

where superscript “3” denotes that the profile is only composed of only three most significant factors. The recon-
structed mountain profiles from the three dominant factors are plotted against the digitized profiles in Figure
1•47. A close fit is more likely to happen if the original mountain is close to the “average mountain”.

Workbook of Applications in VectorSpace C++ Library 93


Chapter 1 Computational Linear Algebra Using C0 Type Objects

Figure 1•47 Mountain profiles shown in gray lines are reconstructed from three
dominant factors. They are plotted against the solid lines that represents the original
mountain profiles.

94 Workbook of Applications in VectorSpace C++ Library


CHAPTER

Two Numerical Optimization


Using C1 and C2 Type
Objects

C1 and C2 spaces are continuous vector spaces which are differentiable up to order 1 and order 2, respec-
tively. Classes in C1 and C2 types enable the users of VectorSpace C++ Library to deal with numerically differ-
entiable objects. The applications in the subject of numerical optimization, constrained or unconstrained, can be
easily expressed with C1 and C2 types. C++ programs using VectorSpace C++ Library in this chapter are
projects in project workspace file “Cn.dsw” under directory “vs\ex\Cn”.

2.1 C1 Type Objects


VectorSpace C++ Library supports two classes, modeled after concept in differential geometry1,
Tangent_Bundle and Vector_of_Tangent_Bundle, in C1 type. A tangent bundle has a tangent vector “w” attached
to a base point “p” on a body; i.e., p ∈ , where a body ⊂ m, and w ∈ n, with m ≤ n. We denote the tangent
bundle as a pair of a base point and a tangent vector “(p, w)” defined in T × n. We represent the tangent bun-
dle graphically in Figure 2.1. In VectorSpace C++ Library, the class name “Tangent_Bundle” is reserved for a
“scalar” tangent bundle where the base point has dimension of 1 (p ∈ ), and w ∈ n. A more general case of
tangent bundle C++ class with ⊂ m, p ∈ , and w ∈ n; i.e., the base point “p” has the dimension of “m”
(m ≤ n) is called “Vector_of_Tangent_Bundle” (notice that may have lower dimension than its containing
space = n, for example, a rod (1-D; m =1), a plate (2-D; m = 2), or a shell (2-D; m = 2) in a three dimensional
physical space 3; n = 3.) Note that for more generalized “spatial and body dimension” (in case for including

1. e.g., p.90 in W.L. Burke, 1985, “Applied differential geometry”, Cambridge University Press, Cambridge, U.K.

Workbook of Applications in VectorSpace C++ Library 95


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
dimensions greater than 3), a generalized containing space is said to contain a manifold— , in place of a
body— in physical space 3.

w
p

Figure 2.1 Tangent bundle of a body in its containing space .

In the following we define the algebra of the C1 type. For “x”, “y”, and “z”, Tangent_Bundle objects of C1
type, with an abstract binary operator “ ◊ ”, such as in z = x ◊ y , TABLE 2•1 summaries the algebra of four con-
crete basic operators (in place of the abstract operator ◊ ).

Operator base point tangent vector


+ z=x+y dz = dx + dy
- z=x-y dz = dx - dy
* z=xy dz = y dx + x dy (Lebniz rule)
/ z=x/y dz = (y dx - x dy) / y2
TABLE 2•1 Four basic binary operators for the algebra of C1 type objects.

The derivatives for the multiplication operator in the third row simply follows the Lebniz rule in calculus—d z =
d(x y) = y dx + x dy. Lebniz rule can also be applied to the division operator—d(x / y) = d(x * (1/y)) = (y dx - x
dy) / y2. Other operators and transcendental functions can be defined accordingly as in calculus.

It is obvious how C1 type object works as a differentiable object. Since we keep track of numerical values of
base point, p, and tangent vector, w, of a C1 type object through out all kinds of operations, the resultant numer-
ical values of base point and tangent vector of intermediate (temporary) objects will always be available. Inquir-
ing on the numerical values of tangent vector, w, of the C1 type object gives the derivative information we need.
From reverse engineering point of view, when we want to do away with C1 type object, this model has the
advantage that the computing algorithm is quite compatible with traditional FORTRAN or C programming. On
the other hand, the symbolic languages process the intermediate analytical expression by looking up its “dictio-
nary”, while defer the evaluation of actually numerical values until it is explicitly requested by users. Therefore
the computing algorithm is completely different from that of FORTRAN or C. In retrospect, we note that the
defer evaluation approach is known to have its advantage of fast response time in an interactive environment,
since un-necessary evaluations are avoided sometimes.

2.1.1 Tangent Bundle


The data abstraction for a C1 type Tangent_Bundle object is modeled mathematically by (1) a C0* type Sca-
lar “u” for the base point—p (with the dimension of the base point being always equals 1), and (2) a C0* type

96 Workbook of Applications in VectorSpace C++ Library


C1 Type Objects
Scalar/Vector “du” for the tangent vector—w (a Scalar when that spatial dimension =1, and a Vector when the
spatial dimension > 1). The two private data members C0* type Scalar, “u”, and Scalar/Vector, “du”, in turn refer
to double* type “v” and “dv” in physical memory space, respectively. The constructor and the destructor of the
C1 type Tangent_Bundle object encapsulate the details of low-level memory management on the double* type
“v” and “dv”. In other words, both the base point and the tangent vector are represented in two levels. That is in
higher level as C0* type Scalar(u) and Scalar/Vector(du), and in lower level as double* type “v” and “dv”. This
dual abstraction in Tangent_Bundle class is to facilitate (1) swift memory management in lower level, and (2)
mathematical abstraction in higher level for the C1 type Tangent_Bundle class as shown in TABLE 2•2.

Tangent Bundle Abstraction Mathematics Physical Memory


base point—p C0* of Scalar—u double*—v
tangent vector—w C0* of Scalar/Vector—du double*—dv
TABLE 2•2. Dual abstraction of a C1 type Tangent_Bundle class.

Constructors
A dedicated constructor for a C1 type Tangent_Bundle object can be written as (project: “c1_examples”)

1 C1 x(0.0); // (*u) = 0.0; (*du) = 1.0 (default), spatial dimension = 1 (default)


2 cout << ((C0) x) << endl; // 0.0
3 cout << d(x) << endl; // 1.0
4 ((C0) x) = 3.0; // as “l-value”
5 cout << ((C0) x) << endl; // 3.0

A double constant “0.0” in line 1 is the argument passed to the dedicated constructor to be assigned as the value
of the base point. The Tangent_Bundle so constructed has default spatial dimension of 1, and its default deriva-
tive (tangent vector) value du = 1.0 as default. C0 converter “C1::operator C0()” in line 2 casting on “x” is used
to retrieve the value of the base point of “x”. The free function “d(const C1&)” in line 3 can be used to retrieve
the value of the derivative (tangent vector). Both the casting operator and the derivative function can be used as
“l-value”, to be put on the left-hand-side to assign value to it. The reason for the default value du = 1.0 is evident
when we consider using “x” as a variable. For example, use “x” as a variable to define a function “f” 1(project:
“c1_examples”)

1 C1 x(0.0), // (*u) = 0.0; (*du) = 1.0


2 f = 2.0 * x * (sin(x)+1.0); // f(x) = 2x[sin(x)+1], x ∈ C1 and f ∈ C1
3 cout << ((C0) f) << endl; // f(0.0) = 0.0 = 2.0*(0.0)*[sin(0.0)+1.0]
4 cout << d(f) << endl; // df(0.0) / dx = 2.0 = 2.0*(0.0)*cos(0.0) +
5 // 2.0*[sin(0.0)+1.0]

1. example taken from K.E. Gorlen, S.M.Orlow, and P.S. Plexico, 1991, “Data Abstraction and Object-Oriented Program-
ming in C++”, John Wiley & Sons Ltd, p.92-93.

Workbook of Applications in VectorSpace C++ Library 97


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
The function (or dependent variable) “f” is defined with the (independent) variable “x” as its parameter. A differ-
ent view on the default value of “du = 1.0”, is that if “x” is to serve as a variable, the derivative of “x”—dx
equals “1.0” by the way of differentiation in calculus. The kind of dedicated constructor “C1::C1(const dou-
ble&)” that can be used as a variable to define a more complicated function is called a variable (dedicated) con-
structor.
When the spatial dimension is not equal to 1, we can use the following constructor (not a “variable dedicated
constructor”)

C1 y(3.0, 3); // (project: “c1_examples”)


cout << ((C0) y) << endl; // 3.0
cout << d(y) << endl; // {0.0, 0.0, 0.0}T

The first argument of this dedicated constructor is a “const double&” which specifies the value of the base point,
and the second argument is a “int” which gives the number of the spatial dimension, the dimension of a tangent
vector, w. The default value of the tangent vector is to have all its components set to “0.0”.
The constant strings for Tangent_Bundle virtual constructor (use macro definition “TANGENT_BUNDLE”)
and autonomous virtual constructor are shown in the following box.

virtual constructor string VectorSpace C++ Library definition priority

by reference
“C1&” C1 type Tangent_Bundle 1
“C1*” pointer to C1 type Tangent_Bundle 2
“double*, double*” base point, tangent vector, (spatial dim. = 1) 3
“double*, double*, int” base point, tangnet vector, spatial dim. 4

by value
“int” spatial dim. 5
“const double&, const double&” base point, tangent vector, (spatial dim. = 1) 6
“const C0&, const C0&” base point, tangent vector, (spatial dim. = 1) 7
“const double*, const double*” base point, tangent vector, (spatial dim. = 1) 8
“const C0*, const C0*” base point, tangent vector, (spatial dim. = 1) 9
“const double*, const double*, int” base point, tangent vector, spatial dim. 10
“const C0*, const C0*, int” base point, tangent vector, spatial dim. 11
“const C1&” C1 type Tangent_Bundle 12
“const C1*” pointer to C1 type Tangent_Bundle 13

Strings in C1 virtual constructor for C1 type Tangent_Bundle class.

Operators and Functions


We have introduced the casting operator “C1::operator C0()” and the free function “d(const C1&)” along
with the Tangent_Bundle constructors. These are the two most important operator and function in C1 type. The
rest of the operators and functions are listed in the following box.

98 Workbook of Applications in VectorSpace C++ Library


C1 Type Objects

operator or function VectorSpace C++ library definition remark

symbolic operators
C0& operator &= ( ) assignment by reference
C0& operator = ( ) assignment by value
operator C0() casting operator; retrieve base point

arithmetic operators
C0 operator + ( ) const positive (primary casting) unary
C0 operator - ( ) const negative unary
C0 operator + (const C0&) const addition
C0 operator - (const C0&) const subtraction
C0 operator * (const C0&) const multiplication by a scalar; scalar product of two Vectors
C0 operator / (const C0&) const division (by a Scalar or a Matrix only) return a Vector
C0& operator += (const C0&) replacement addition
C0& operator -= (const C0&) replacement subtraction
C0& operator *= (const C0&) replacement multiplication (by a Scalar only)
C0& operator /= (const C0&) replacement division (by a Scalar only)

logic operators
int operator == (const C0&) const equal TRUE == 1
int operator != (const C0&) const not equal FALSE == 0
int operator >= (const C0&) const greater or equal
int operator <= (const C0&) const less or equal
int operator > (const C0&) const greater
int operator < (const C0&) const less

functions
int col_length() const spatial dimension
C0& d() the first derivative; retrieve tangent vector
C0 pow(int) const power (applied to each element of the Vector)
C0 exp(const C0&) const exponent (applied to each element of the Vector)
C0 log(const C0&) const log (applied to each element of the Vector)
C0 sin(const C0&) const sin (applied to each element of the Vector)
C0 cos(const C0&) const cos (applied to each element of the Vector)

Partial listing of C1 type Tangent_Bundle class arithmetic operators, logic operators and functions.

2.1.2 Vector of Tangent Bundle


Recalled that a Tangent_Bundle object of C1 type is reserved for tangent bundle with a scalar base point (m =
1; i.e., the manifold dimension equals 1). For the dimension “m” of a manifold to be greater than 1, we have C1
type Vector_of_Tangent_Bundle objects. The data abstraction for the C1 type Vector_of_Tangent_Bundle class

Workbook of Applications in VectorSpace C++ Library 99


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
is represented by a C0* type Vector object for its base point (“length” = “m”), and a C0* type Matrix object
(“row-length” = m, “column-length” = n, and m ≤ n) for its tangent vector. The Matrix object actually is the
“Jacobian matrix” of the form (for example, m = n = 3)

∂f 1 ⁄ ∂x 1 ∂f 1 ⁄ ∂x 2 ∂f1 ⁄ ∂x 3
df df i
------ = ------- = ∂f 2 ⁄ ∂x 1 ∂f 2 ⁄ ∂x 2 ∂f2 ⁄ ∂x 3 Eq. 2•1
dx dx j
∂f 3 ⁄ ∂x 1 ∂f 3 ⁄ ∂x 2 ∂f3 ⁄ ∂x 3

Constructors
The dedicated constructor for the C1 type Vector_of_Tangent_Bundle class can be written as (project:
“c1_examples”)

double v[3] = {1.0, 1.0, 1.0}; // base point values (coordinates)


C1 x(3, v); // m = n = 3; equal dimensions for body and physical space
cout << ((C0)x) << endl; // {1.0, 1.0, 1.0}T
cout << d(x) << endl; // { {1.0, 0.0, 0.0},
// {0.0, 1.0, 0.0},
// {0.0, 0.0, 1.0} }

“C1::C1(int, const double*)” is the variable dedicated constructor for the C1 type Vector_of_Tangent_Bundle
class. The example for using this variable dedicated constructor is a vector function f = {f1, f2, f3}T, which
depends on three independent variable x = {x1, x2, x3}T as1

f 1 = 16x 14 + 16x 24 + x 34 – 16

f 2 = x 12 + x 22 + x 32 – 3

f 3 = x 13 + x 2 Eq. 2•2

Root finding of “f(x) = 0” for this non-linear problem can be obtained by an iterative algorithm. Considering
approximation of the vector function f by Taylor expansion at the neighborhood of an initial value xi with its
increment dx as

f(xi+dx) = f(xi) + f,x(xi) dx + O(dx2) = 0 Eq. 2•3

where O(dx2) denotes the error in the second-order of dx or above. Neglecting higher-order errors in Eq. 2•3 for
small dx, we have

dx = - f(xi) / f,x(xi) Eq. 2•4

1. example taken from K.E. Gorlen, S.M. Orlow, and P.S. Plexico, 1991, “Data Abstraction and Object-Oriented Program-
ming in C++”, John Wiley & Sons Ltd, p.93-97.

100 Workbook of Applications in VectorSpace C++ Library


C1 Type Objects
This is the formula for root finding, and xi+1 = xi+dx is the update. The implementation of this iterative algorithm
(Newton-Raphson Method) shown in Program Listing 2•1 with VectorSpace C++ Library is very simple. The
C++ codes are actually as concise as the mathematical expressions. The selector “C1::operator [](int)” is used to
access the components of the C1 type Vector_of_Tangent_Bundle class. The return value of the selector is a
Tangent_Bundle (see Figure 2.2). The solution for “f(x) = 0” is x = {0.877966, 0.676757, 1.33086}T.

#include “include/vs.h”
#define EPSILON 1.e-12
#define MAX_ITER_NO 10
int main() {
double v[3] = {1.0, 1.0, 1.0}; initial values, x0 = {1.0, 1.0, 1.0}T
C1 x(3, v), f(3, (double*)0);
int count = 0;
do {
f1 = 16x 14 + 16x 24 + x 34 – 16
f[0]=16.0*x[0].pow(4)+16.0*x[1].pow(4)+x[2].pow(4)-16.0;
f[1]= x[0].pow(2)+ x[1].pow(2)+x[2].pow(2)-3.0; f2 = x 12 + x 22 + x 32 – 3
f[2] = x[0].pow(3)- x[2]; f 3 = x 13 + x 2
C0 dx = - ((C0)f) / d(f); Eq. 2•4, dx = - f(xi) / f,x(xi)
(C0) x += dx; update xi+1 = xi+dx
} while(++count < MAX_ITER_NO &&
(double)norm((C0)f) > EPSILON); check convergence condition
if(count == MAX_ITER_NO)
cout << “Warning: convergence failed, residual norm: ” if convergence failed output
<< ((double)norm((C0)f)) << endl; residual norm
else
cout << “solution (” << count << “): ” << ((C0)x) << endl; x = {0.877966, 0.676757, 1.33086}T
return 0;
}

Listing 2•1 Solving a nonlinear vector of function using C1 type Vector_of_Tangent_Bundle class
(project: “newton_method_nd_root_finding”).

0 00 01 02
x: 1 10 11 12
double v[3] = {1.0, 1.0, 1.0};
C1 x(3, v); 2 20 21 22

base tangent
point vector

x[0] : 0 00 01 02

Figure 2.2 Selector of the C1 type Vector_of_Tangent_Bundle class.

Workbook of Applications in VectorSpace C++ Library 101


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
The constant strings for C1 type Vector_of_Tangent_Bundle class virtual constructors (use macro definition
“VECTOR_OF_TANGENT_BUNDLE”) and autonomous virtual constructors are shown in the following box.

virtual constructor string VectorSpace C++ library definition priority

by reference
“C1&” C1 type Vector_of_Tangent_Bundle —
“C1*” pointer to C1 type Vector_of_Tangent_Bundle —
“int, int, double*, double*” manifold dim, spatial dim, base point, tangnet vector
14

by value
“int, int” manifold dim., spatial dim. 15
“int, int, const double*, const double*” base point, tangent vector, (spatial dim. = 1) 16
“const C0&, const C0&” base point (Vector), tangent vector (Matrix) —
“const C0*, const C0*” base point (Vector), tangent vector (Matrix) —
“const C1&” C1 type Vector_of_Tangent_Bundle —
“const C1*” pointer to C1 type Vector_of_Tangent_Bundle —
Strings in C1 virtual constructor for C1 type Vector_of_Tangent_Bundle class.

Operators and Functions


The most important new operator other than those which have already been introduced in the C1 type
Vector_of_Tangent_Bundle class is the selector. The rest of the operators and functions are listed in the follow-
ing box.

102 Workbook of Applications in VectorSpace C++ Library


C1 Type Objects

operator or function VectorSpace C++ library definition remark

symbolic operators
C0& operator &= ( ) assignment by reference
C0& operator = ( ) assignment by value
C0& operator [] (int) selector return Tangent_
Bundle
operator C0() casting operator; retrieve base point

arithmetic operators
C0 operator + ( ) const positive (primary casting) unary
C0 operator - ( ) const negative unary
C0 operator + (const C0&) const addition
C0 operator - (const C0&) const subtraction
C0 operator * (const C0&) const multiplication by a scalar or scalar product
C0 operator / (const C0&) const division (by a Scalar only)
C0& operator += (const C0&) replacement addition
C0& operator -= (const C0&) replacement subtraction
C0& operator *= (const C0&) replacement multiplication (by a Scalar only)
C0& operator /= (const C0&) replacement division (by a Scalar only)

logic operators
int operator == (const C0&) const equal TRUE == 1
int operator != (const C0&) const not equal FALSE == 0
int operator >= (const C0&) const greater or equal
int operator <= (const C0&) const less or equal
int operator > (const C0&) const greater
int operator < (const C0&) const less

functions
int row_length() const manifold dimension
int col_length() const spatial dimension
C0& d() the first derivative; retrieve tangent vector
C0 pow(int) const power (applied to each element of the Vector)
C0 exp(const C0&) const exponent (applied to each element of the Vector)
C0 log(const C0&) const log (applied to each element of the Vector)
C0 sin(const C0&) const sin (applied to each element of the Vector)
C0 cos(const C0&) const cos (applied to each element of the Vector)

Partial listing of Vector_of_Tangent_Bundle object arithmetic operators, logic operators and functions.

Workbook of Applications in VectorSpace C++ Library 103


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
2.2 C2 Type Objects
C2 type models objects in C2 spaces. It is a generalization of the tangent bundle to derivatives of second
order. For the tangent bundle in C1 space, the generalization to second order derivative gives “tangent of” tan-
gent bundle in C2 space. In the previous section we have a vector of tangent bundle in C1 space when the mani-
fold dimension is greater than 1. Therefore, we can also have “vector of” tangent of tangent bundle. In
VectorSpace C++ library we implement the generalization of classes from C1 type to C2 type as classes
“Tangent_of_Tangent_Bundle” and “Vector_of_Tangent_of_Tangent_Bundle”. The nomenclature is unfortu-
nately quite verbose. These cluttered class names are trade-off for showing generic clarity.
The algebra of the C2 type can be defined similar to that of the C1 type. For C2 type objects “x”, “y”, and
“z” with an abstract binary operator “ ◊ ” in the case of z = x ◊ y . TABLE 2•1 outlines the algebra of four con-
crete basic operators.

Operator base point tangent vector tangent of tangent vector


+ z=x+y dz = dx + dy d2z = d2x + d2y
- z=x-y dz = dx - dy d2z = d2x - d2y
2 2
* z=xy dz = y dx + x dy dx ⊗ dy + dy ⊗ dx + xd y + yd x
/ z=x/y dz = (y dx - x dy) / y2 2 2
d x ⁄ y– ( dx ⊗ dy + dy ⊗ dx + xd y ) ⁄ y 2 +2x ( dy ⊗ dy ) ⁄ y 3
TABLE 2•3. Four basic binary operators for the algebra of C2 type objects.

where the operator “ ⊗ ” for the tangent of tangent vector denotes tensor product.

2.2.1 Tangent of Tangent Bundle


The data abstraction for a C2 type Tangent_of_Tangent_Bundle (see Figure 2.3) is represented by a C0*
type Scalar “u” for the base point “p”, and a C0* type Scalar/Vector “du” for the tangent vector “w” (Vector in
the case of spatial dimension > 1), and a C0* type Scalar/Matrix “ddu” for the tangent of tangent vector “dw”
(Matrix in the case of spatial dimension > 1). As the dual abstraction for the C1 type object explained in
page 97, high-level representation “u”, “du”, and “ddu” are all referring to physical memory spaces pointed to
low-level representation by three double* “v”, “dv”, and “ddv”.

spatial dimension = 1 spatial dimension = 3


u,xx u,xy u,xz
u,yx u,yy u,yz
u du ddu u u,x u,y u,z u,zx u,zy u,zz
base point tangent tangent of tangent
vector vector
Figure 2.3 Data abstraction of C2 type Tangent_of_Tangent_Bundle class.

104 Workbook of Applications in VectorSpace C++ Library


C2 Type Objects
Constructors
An example of using a variable dedicated constructor for C2 type Tangent_of_Tangent_Bundle class is
(project: “c2_examples”)

C2 x(0.0); // u = 0.0, du (= 1.0; default value), ddu (= 0.0; default value)


cout << ((C0) x) << endl; // 0.0
cout << d(x) << endl; // 1.0
cout << dd(x) << endl; // 0.0, or write “d2(x)” instead

For the purpose of a variable dedicated constructor, the default value of “ddu”(=0.0) is just the derivative of “du”
(= 1.0). For access to the second derivative information, free function “dd(const C2&)” (or “d2(const C2&)”) can
be used to retrieve the value of “ddu” (project: “c2_examples”).

C2 x(0.0), // (*u) = 0.0; (*du) = 1.0


f = 2.0 * x * (sin(x)+1.0); // f(x) = 2x[sin(x)+1], x ∈ C2 and f ∈ C2
cout << ((C0) f) << endl; // f(0.0) = 0.0 = 2.0*(0.0)*[sin(0.0)+1.0]
cout << d(f) << endl; // df(0.0) / dx = 2.0 = 2.0*(0.0)*cos(0.0) +
// 2.0*[sin(0.0)+1.0]
cout << dd(f) << endl; // d 2f(0.0)/dx2 = 4.0 = [2.0*(0.0)*-sin(0.0) +2.0*cos(0.0)]+
// 2.0*cos(0.0)

For spatial dimension greater than 1, we can write the dedicated constructor similarly to write that of the C1 type
Tangent_Bundle as (project: “c2_examples”)

C2 y(3.0, 3);
cout << ((C0) y) << endl; // 3.0
cout << d(y) << endl; // {0.0, 0.0, 0.0}T
cout << dd(y) << endl; // {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}}
The constant strings for C2 type Tangnet_of_Tangent_Bundle virtual constructors (use macro definition
“TANGENT_OF_TANGENT_BUNDLE”) and autonomous virtual constructors are shown in the following box.

Operators and Functions


We have introduced the second derivative function “dd(const C2&)” (or “d2(const C2&)”) along with the C2
type Tangent_of_Tangent_Bundle constructors. This is the most important new function in C2 type. The rest of
the operators and functions are listed in the following box.

Workbook of Applications in VectorSpace C++ Library 105


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

virtual constructor string VectorSpace C++ library definition priority

by reference
“C2&” C2 type Tangent_of_Tangent_Bundle 1
“C2*” pointer to C2 type Tangent_of_Tangent_Bundle 2
“double*, double*, double*” base point, tangent vector, tangent of tangent 3
vector, (spatial dim. = 1)
“double*, double*, double*, int” base point, tangent vector, tangent of tangent 4
vector, spatial dim.

by value
“int” spatial dim. 5
“const double&, const double&, base point, tangent vector, tangent of tangent 6
const double&” vector, (spatial dim. = 1)
“const C0&, const C0&, base point, tangent vector, tangent of tangent 7
const C0&” vector, (spatial dim. = 1)
“const double*, const double* base point, tangent vector, tangent of tangent 8
const double*” vector, (spatial dim. = 1)
“const C0*, const C0* base point, tangent vector, tangent of tangent 9
const C0*” vector, (spatial dim. = 1)
“const double*, const double*, base point, tangent vector, tangent of tangent 10
const double*, int” vector, spatial dim.
“const C0*, const C0*, int” base point, tangent vector, tangent of tangent 11
const C0*, int” vector, spatial dim.
“const C2&” C2 type Tangent_of_Tangent_Bundle 12
“const C2*” pointer to C2 type Tangent_of_Tangent_Bundle 13

Strings in C2 virtual constructor for Tangent_of_Tangent_Bundle object.

106 Workbook of Applications in VectorSpace C++ Library


C2 Type Objects

operator or function VectorSpace C++ library definition remark

symbolic operators
C0& operator &= ( ) assignment by reference
C0& operator = ( ) assignment by value
operator C0() casting operator; retrieve base point

arithmetic operators
C0 operator + ( ) const positive (primary casting) unary
C0 operator - ( ) const negative unary
C0 operator + (const C0&) const addition
C0 operator - (const C0&) const subtraction
C0 operator * (const C0&) const multiplication by a scalar; scalar product of two Vectors
C0 operator / (const C0&) const division (by a Scalar or a Matrix only) return a Vector
C0& operator += (const C0&) replacement addition
C0& operator -= (const C0&) replacement subtraction
C0& operator *= (const C0&) replacement multiplication (by a Scalar only)
C0& operator /= (const C0&) replacement division (by a Scalar only)

logic operators
int operator == (const C0&) const equal TRUE == 1
int operator != (const C0&) const not equal FALSE == 0
int operator >= (const C0&) const greater or equal
int operator <= (const C0&) const less or equal
int operator > (const C0&) const greater
int operator < (const C0&) const less

functions
int col_length() const spatial dimension
C0& d() the first derivative
C0& dd() or C0& d2() the second derivative
C0 pow(int) const power (applied to each element of the Vector)
C0 exp(const C0&) const exponent (applied to each element of the Vector)
C0 log(const C0&) const log (applied to each element of the Vector)
C0 sin(const C0&) const sin (applied to each element of the Vector)
C0 cos(const C0&) const cos (applied to each element of the Vector)

Partial listing of C2 type Tangent_of_Tangent_Bundle object arithmetic operators, logic operators and functions.

Workbook of Applications in VectorSpace C++ Library 107


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
2.2.2 Vector of Tangent of Tangent Bundle
The data abstraction for a C2 type Vector_of_Tangent_of_Tangent_Bundle (see Figure 2.4) is represented
by a C0* type Vector “u” for the base point “p”, and a C0* type Matrix “du” for the tangent vector “w”, and a
C0* type Submatrix “ddu” for the tangent of tangent vector “dw”. As in the dual abstraction for the C1 type
object explained in page 97, C2 type has high-level mathematical representation as “u”, “du”, and “ddu” which
in turn refer to physical memory spaces in low-level representation as double* type “v”, “dv”, and “ddv”.
spatial dimension = 3, manifold dimension = 3
u,xx u,xy u,xz
u,yx u,yy u,yz
u u,x u,y u,z u,zx u,zy u,zz

v,xx v,xy v,xz


v,yx v,yy v,yz
v v,x v,y v,z v,zx v,zy v,zz

w,xx w,xy w,xz


w,yx w,yy w,yz
w w,x w,y w,z w,zx w,zy w,zz

base point tangent tangent of tangent


vector vector
Figure 2.4 Data abstraction of C2 type Vector_of_Tangent_of_Tangent_Bundle class.

Constructors
A variable dedicated constructor for C2 type Vector_of_Tangent_of_Tangent_Bundle can be written as
(project: “c2_examples”)

double v[3] = {0.0, 1.0, 2.0};


C2 x(3, v); // du (= identity; default), ddu (= 0; default)
cout << ((C0) x) << endl; // {0.0, 1.0, 2.0}T
cout << d(x) << endl; // {{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}
cout << (+dd(x)) << endl; // {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0},
// {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0},
// {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}}

Note that dd(x) returns a “Nominal_Submatrix” which can not be directed to iostreams. We must use primary
casting “+” to convert it into a Matrix. Without this conversion the program will throw an exception and stop.

108 Workbook of Applications in VectorSpace C++ Library


C2 Type Objects
We apply the variable dedicated constructor to a minimization problem1

f(x1, x2) = 2x12 + x1x2 + x22 -12x1 -10 x2 Eq. 2•5

This elliptic objective functional can be approximated by Taylor expansion to the second order as2

1
f ( x ) ≅ f ( x i ) + f ,x ( x i )dx + --- dx T H ( x i )dx Eq. 2•6
2

where f,x(xi) is the so-called Jacobian matrix, and H(xi) = f,xx(xi) is the so-called Hessian matrix. f(x) is mini-
mized if its first derivatives with respect to dx vanishes. Therefore, if we take derivatives of f(x), set to zero, then
solve for dx, we obtain,

dx = - f,x(xi) / H(xi) Eq. 2•7

The elliptic nature of the objective functional guarantees that the Hessian matrix can be inverted. Eq. 2•7 is
known as the Newton’s formula, and xi+1 = xi+dx is the update for the algorithm. For the elliptic objective func-
tional such as Eq. 2•5, the approximation by Eq. 2•6 in quadratic form is exact. One iteration will give the exact
answer. Program Listing 2•2 in the following implemented the “classic Newton-Raphson method” that can be
used for less ideal cases when the objective functionals are not exactly quadratic.
The minimum solution of this elliptic objective functional “f” is {2, 4}T, which is the center of the ellipse “f =
constant”. We can verify this immediately with analytical geometry. The Newton-Raphson iterative procedure in
this case achieves convergence in just one iteration from the initial point (0, 0) to the final solution point (2, 4)
(see Figure 2.5).
The constant strings for Vector_of_Tangent_Bundle virtual constructors (use macro definition
“VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE”) and autonomous virtual constructors are shown in
the following box.

Operators and Functions


The most important operator other than those introduced in the Tangent_of_Tangent_Bundle is the selector
we have just used. The rest of the operators and functions are listed in the following box.

1. function without constrained conditions from D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-
Wesley Publishing Company, Inc., Reading, MA., p.426.
2. A similar equation is in p.225, Eq. 43 of D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley
Publishing Company, Inc., Reading, MA.

Workbook of Applications in VectorSpace C++ Library 109


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

#include “include/vs.h”
#define EPSILON 1.e-12
#define MAX_ITER_NO 10
int main() {
C2 x(2, (double*)0), f; initial values, x0 = {0.0, 0.0}T
int count = 0;
do {
f &= 2.0*x[0].pow(2) + x[0]*x[1] + x[1].pow(2) -12.0*x[0] f(x1, x2) = 2x12 + x1x2 + x22 -12x1 -10 x2
-10.0*x[1];
C0 dx = - d(f) / dd(f); Eq. 2•7, dx = - f,x(xi) / f,xx(xi)
(C0) x += dx; update xi+1 = xi+dx
} while(++count < MAX_ITER_NO &&
(double)norm(d(f))>EPSILON); check convergence condition
if(count == MAX_ITER_NO)
cout << “Warning: convergence failed, increment norm: ” if convergence failed output
<< ((double)norm(dx)) << endl; dx norm
else
cout << “solution (” << count << “): ” << ((C0)x) << endl; x = {2, 4}T
return 0;
}
Listing 2•2 Minimization of an elliptic objective functional using Vector_of_Tangent_of_Tangent _Bundle
(project: “newton_method_optimization”).

4 (2, 4) solution

0
(0,0) initial point

0 2 4 6 8

Figure 2.5 Newton-Raphson method for a quadratic functional.

110 Workbook of Applications in VectorSpace C++ Library


C2 Type Objects

virtual constructor string VectorSpace C++ library definition priority

by reference
“C2&” C2 type Vector_of_Tangent_of_Tangent_Bundle —
“C2*” C2* type Vector_of_Tangent_of_Tangent_Bundle —
“int, int, double*, double*, manifold dim, spatial dim, base point, tangent vector
double*” tangent of tangent vector 14

by value
“int, int” manifold dim., spatial dim. 15
“int, int, const double*, base point, tangent vector, tangent of tangent 16
const double*, const double*” vector, (spatial dim. = 1)
“const C0&, const C0&, base point (Vector), tangent vector (Matrix) —
const C0&” tangent of tangent vector (Submatrix)
“const C0*, const C0*, base point (Vector), tangent vector (Matrix) —
const C0*” tangent of tangent vector (Submatrix)
“const C2&” C1 type Vector_of_Tangent_of_Tangent_Bundle —
“const C2*” C1* type Vector_of_Tangent_of_Tangent_Bundle —

Strings in C2 virtual constructor for C2 type Vector_of_Tangent_of_Tangent_Bundle class.

Workbook of Applications in VectorSpace C++ Library 111


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

operator or function VectorSpace C++ library definition remark

symbolic operators
C0& operator &= ( ) assignment by reference
C0& operator = ( ) assignment by value
C0& operator [] (int) selector return Tangent_of_
Tangent_Bundle
operator C0() casting operator; retrieve base point

arithmetic operators
C0 operator + ( ) const positive (primary casting) unary
C0 operator - ( ) const negative unary
C0 operator + (const C0&) const addition
C0 operator - (const C0&) const subtraction
C0 operator * (const C0&) const multiplication by a scalar or scalar product
C0 operator / (const C0&) const division (by a Scalar only)
C0& operator += (const C0&) replacement addition
C0& operator -= (const C0&) replacement subtraction
C0& operator *= (const C0&) replacement multiplication (by a Scalar only)
C0& operator /= (const C0&) replacement division (by a Scalar only)

logic operators
int operator == (const C0&) const equal TRUE == 1
int operator != (const C0&) const not equal FALSE == 0
int operator >= (const C0&) const greater or equal
int operator <= (const C0&) const less or equal
int operator > (const C0&) const greater
int operator < (const C0&) const less

functions
int row_length() const manifold dimension
int col_length() const spatial dimension
C0& d() the first derivative; retrieve tangent vector
C0& dd() the second derivative; retrieve tangent of tangent vector
C0 pow(int) const power (applied to each element of the Vector)
C0 exp(const C0&) const exponent (applied to each element of the Vector)
C0 log(const C0&) const log (applied to each element of the Vector)
C0 sin(const C0&) const sin (applied to each element of the Vector)
C0 cos(const C0&) const cos (applied to each element of the Vector)

Partial listing of C2 type Vector_of_Tangent_of_Tangent_Bundle object arithmetic operators, logic operators and functions.

112 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
2.3 Linear Programming and Non-linear Optimization

2.3.1 Linear Programming


Two kinds of approaches in linear programming are explained in this section, namely the basic set method
(column pivoting) and the active set method (row pivoting). The example problem is1

maximize objective functional f(x) = 3x1 + x2 + 3x3 subject to

2x 1 + x 2 + x 3 ≤ 2
x 1 + 2x 2 + 3x 3 ≤ 5
2x 1 + 2x 2 + x 3 ≤ 6

x 1 ≥ 0, x 2 ≥ 0, x 3 ≥ 0

The pre-processing step of the linear programming is to (1) multiply “-1” on the objective functional to con-
vert the maximization problem into a minimization problem, (2) transform the first three inequality constraints
into equality constraints by adding positive slack variables x4, x5, and x6. That is

minimize objective functional f(x) = -3x1 - x2 - 3x3 subject to

2x 1 + x 2 + x 3 + x 4 = 2
x 1 + 2x 2 + 3x 3 + x 5 = 5
2x 1 + 2x 2 + x 3 + x 6 = 6

x 1 ≥ 0, x 2 ≥ 0, x 3 ≥ 0, x 4 ≥ 0, x 5 ≥ 0, x 6 ≥ 0

This is the so-called standard form in linear programming.

Basic Set Method


The above problem has 6 variables and 3 equality constraints. We can select 3 variables (set the others to
zero) and solve the 3 equality constraints. In condition that the 3 equations are linear independent we can have a
basic solution. If this basic solution satisfies non-negative constraints on the 6 variables, this solution is also
called basic feasible solution. The procedure of linear programming is to move from one basic feasible solution
to the other such that the value of the objective functional decreases to its minimum. For a linear objective func-
tional, the fundamental theorem of linear programming states that it is sufficient to consider only those basic fea-

1. example taken from D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley Publishing Company,
Inc., Reading, MA., p.46.

Workbook of Applications in VectorSpace C++ Library 113


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
sible solutions. In other words, the optimal value of the objective functional is always achieved at a basic
feasible solution for a linear objective functional.
The matrix form of the above problem can be written as

minimize f ( x ) = c DT x D + c BT x B
subject to D x D + Bx B = b
x D ≥ 0, x B ≥ 0

A = [D, B], cT = [cTD, cTB], xT = [xTD, xTB] Eq. 2•8

xD = {x1, x2, x3} is the non-basic variables, xB = {x4, x5, x6} is the basic variables. We choose the slack vari-
ables as initial basic variables for the obvious reason that the initial basic feasible solution is clearly xB = {2, 5,
6} without having to solve any set of equations. We can solve for xB from the equality constraints as

xB = B-1 (b-D xD)

Substituting this back to the objective functional gives

f ( x ) = ( cD
T – c T B –1 D ) x + c T B – 1 b
B D B Eq. 2•9

In Eq. 2•9 the “coefficient” of the non-basic variables xD is rTD ≡ c DT – c BT B –1 D . rD is known as the relative cost
which measures the cost of a non-basic variable relative to the current basic variables. Negative values of the rel-
ative cost in Eq. 2•9 decrease the value of the objective functional. A non-basic variable corresponding to the
most negative relative cost component in rD will be brought into the basic set, such that the objective functional
decreases the most. Denote a column in D corresponding to the non-basic variable selected to enter the basic set
as “d”. Bringing this non-basic variable into the current basic set means that we are moving with p = - B-1d as a
searching direction; that is moving away from xB along x = xB + α p. The smallest non-negative component in
vector α to satisfy xB + α p = 0 will be the first basis in the current basic set to be encountered (an adjacent
extreme point to d). Hence, this basis is to be selected to leave the basic set. The above process is to be repeated
until the components of relative cost rD are all positive.
The implementation of any non-trivial problem such as the finite difference method discussed in page 67
contains many logic steps that are not highly mathematical. In finite difference method we create a class “FD” to
handle the mapping of finite difference stencil to the global matrix using the concept of data abstraction. Pro-
gram Listing 2•3 (project: “linear_programming_basic_set”) implemented the basic set method. In current basic
set method we create a class “Basic_Set” to represent the basic and non-basic columns in the constraint equa-
tions and the coefficients of the objective functional as shown in Figure 2.6.
For the example problem, we write
C1 X(6, (double*)0), C(3, 6, (double*)0), f; // 6 variables, 3 constraints
C[0] = 2*X[0]+ X[1]+ X[2]+ X[3] ;
C[1] = X[0]+2*X[1]+3*X[2] + X[4] ;

114 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
C[2] = 2*X[0]+2*X[1]+ X[2] + X[5];
f &= -3*X[0]-X[1]-3*X[2];

Notice that both the constraint equations and the objective functional are declared as objects of C1 type. The tan-
gent vector of the C1 type objects will give the coefficients we need; i.e, the elements of A = d(C), and the ele-
ments of cT = d(f) in Eq. 2•8. The class “Basic_Set” is initialized by calling constructor

#include “include/vs.h”
class Basic_Set {
C0 *_A, *_c;
class basic set
int row_size, col_size, *_basic_order; constraint, and coefficients of the objec-
public: tive functional
Basic_Set(C0&, C0&);
~Basic_Set() { delete [] _basic_order; }
C0& A() { return *_A; }
C0& c() { return *_c; }
int basic_order(int i) {return _basic_order[i];}
void swap(int, int);
};
Basic_Set::Basic_Set(C0& dC, C0& df) {
row_size = dC.row_length(); col_size = dC.col_length();
_basic_order = new int[col_size];
for(int i = 0; i < col_size; i++) _basic_order[i] = i;
_A = &dC; _c = &df; initialize original variable order array
}
void Basic_Set::swap(int i, int j) {
int old_basic_order = _basic_order[i];
_basic_order[i] = _basic_order[j]; _basic_order[j] = old_basic_order; swap order
C0 old_Ai(row_size, (double*)0); old_Ai = (*_A)(i);
(*_A)(i) = (*_A)(j); (*_A)(j) = old_Ai;
C0 old_ci(0.0); old_ci = (*_c)[i]; swap columns
(*_c)[i] = (*_c)[j]; (*_c)[j] = old_ci;
}
swap objective functional coefficients

Listing 2•3 class Basic_Set data abstraction (project: “linear_programming_basic_set”).

swap
non-basic basic

A= = [D, B]

cT= [ ] = [cTD, cTB]


order array: 0 1 2 3 4 5 initial order
0 5 2 3 4 1 after swapping

Figure 2.6 Data abstraction of the basic set.

Workbook of Applications in VectorSpace C++ Library 115


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
Basic_Set BS(d(C), d(f));

The submatrices (D, B) and subvectors (cTD, cTB) in Eq. 2•8 can be written using referenced Matrix and refer-
enced Vector in VectorSpace C++ Library as

C0 D(3, 3, BS.A(), 0,0), B(3, 3, BS.A(), 0,3), c_D(3, BS.c(),0), c_B(3,BS.c(),3);

where the public member functions “Basic_Set::A()” and “Basic_Set::c()” provide access to the matrix “A” and
vector “cT” referenced to by the Basic_Set object. The most important function that the class “Basic_Set” per-
forms is to swap columns between the basic and non-basic set. This is provided by the public member function
“Basic_Set::swap(int, int)” with the two integer arguments indicating which two columns are to be swapped. A
private member integer array “_basic_order” is used to keep track down the original variable order. This original
variable order can be retrieved by using the public member function “Basic_Set::basic_order(int)”. Its integer
argument is the current column number.
Program Listing 2•4 implemented the steps of the so-called revised simplex method in linear programming. 1
These steps are

Step 1: relative cost—compute rTD ≡ c DT – c BT B –1 D , if ∀r D ≥ 0 stop,


Step 2:in—select non-basic column (say d) corresponding to the most negative rD to enter the basic set,
Step 3:out—compute p = B-1d, then α = xB / p. If no component in vector α is greater than 0, stop.
The column in the basic set corresponding to the smallest positive α is to leave the basic set.
Step 4: swap—swap columns selected in in and out.

The solution is x = {0.2, 0, 1.6, 0, 0, 4}T in the “standard form”, with the maximum value of the objective func-
tional as “5.4”, or the solution is x = {0.2, 0, 1.6}T in original form, neglecting the artificial “slack variables”.
The basic set method is the traditional simplex-tableau updating procedure that can be explained step-by-step
with minimum mathematics.2 Following is the active set method for linear programming that will be more
readily modified to inequality constrained nonlinear programming.

1. p.60 in D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley Publishing Company, Inc., Read-
ing, MA.
2. see Chapter 3 in D.G. Luenberger, 1989, same as the above.

116 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization

int main() { 6 variables, 3 constraints


C1 X(6, (double*)0), C(3, 6, (double*)0, (double*)0);
C[0] = 2*X[0]+ X[1]+ X[2]+ X[3] ;
2x 1 + x 2 + x 3 + x 4 = 2
C[1] = X[0]+2*X[1]+3*X[2] + X[4] ; x 1 + 2x 2 + 3x 3 + x 5 = 5
C[2] = 2*X[0]+2*X[1]+ X[2] + X[5]; 2x 1 + 2x 2 + x 3 + x 6 = 6
C1 f = -3*X[0]- X[1]-3*X[2];
double rhs[3] = {2.0, 5.0, 6.0};
f(x) = -3x1 - x2 - 3x3
C0 b(3, rhs), x_B; r.h.s. and basic feasible solution
Basic_Set BS(d(C), d(f)); define submatrices and subvectors as
C0 D(3, 3, BS.A(), 0, 0), B(3, 3, BS.A(), 0, 3), c_D(3, BS.c(), 0), c_B(3, BS.c(), 3);
int min_q;
A = [D, B], cT = [cTD, cTB]
do { non-basic column index to be selected
C0 B_inv = B.inverse(); do until all rD are positive
C0 r_D = c_D - c_B * B_inv * D; –1
x_B = B_inv * b;
step 1: rTD = c DT – c BT B D
min_q = -1; double min = 1.e-20;
for(int i = 0; i < 3; i++) if((double) r_D[i] < min) { min = r_D[i]; min_q = i; } step 2:select the most negative rD
if(min_q != -1) {
C0 p = B_inv * D(min_q);
step 3:determine adjacent extreme point
int min_i = -1; double min_alpha = 1.0e20; p = B-1d
for(int i = 0; i < 3; i++) if((double)p[i] > 0.0) {
double alpha = (double)(x_B[i] / p[i]);
if(alpha < min_alpha && alpha > 0.0) { min_alpha = alpha; min_i = i; }
α = xB / p
}
if(min_i == -1) cout << "This linear progamming problem is unbounded" << endl;
BS.swap(min_i+3, min_q);
}
step 4: swap non-basic and basic
} while(min_q != -1); until all rD > 0
for(int i = 0; i < 3; i++) ((C0)X)[BS.basic_order(i+3)] = x_B[i]; unscramble the solution
cout << "solution: " << ((C0)X) << endl << "maximum objective function: " <<
(3*((C0)X)[0]+((C0)X)[1]+3*((C0)X)[2]) << endl;
x = {0.2, 0, 1.6, 0, 0, 4}T
} 5.4

Listing 2•4 Basic set method for linear programming (project: “linear_programming_basic_set”).

Workbook of Applications in VectorSpace C++ Library 117


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
Active Set Method
For simplicity we consider the equality constraint first. For objective functional “f” and constraint equations
“C” (with “m” number of constraints and “n” number of variables). An extremum point occurs at x* where ∇f is
orthogonal to the tangent plane of C. That is ∇f can be written as a linear combination of ∇C. We can prove the
contrary. For example, at point x’, if ∇f is not orthogonal to the tangent plane, ∇f can be projected as a vector p
to the tangent plane. This means that moving along curve C on the direction of p increases (or decreases) the
value of “f”. Therefore, x’ is not an extremum point.

f=c
∇C

∇C ∇f ∇f
Tangent plane
p x*
Tangent plane x’

C(x)
Figure 2.7 Extremum point occurs at ∇f is a linear combination of ∇C.

With this relation between ∇f and ∇C at an extremum point, we introduce Lagrange multiplier λ (“m”
dimensional vector) as the coefficients of linear combination of components of ∇C to form ∇f

∇f +λT ∇C = 0 Eq. 2•10

In view of this, the Lagrangian functional, l(x, λ), can be introduced to represent the constrained optimization
problem as

l(x, λ) = f + λT C Eq. 2•11

For an extremum condition, setting the first-order derivatives of the Lagrangian functional Eq. 2•11 to zero gives
the Euler-Lagrange equations

l,x= ∇f + λT ∇C = 0
l,λ = C = 0 Eq. 2•12

This states that the first-order condition of the Lagrangian functional (1) is exactly Eq. 2•10, and (2) requires x to
stay on the constraints surface (C = 0). The second-order condition of the Lagrangian functional in the case of
the linear objective functional is just the Hessian H= f,xx being positive definite.
In the case of inequality constraints C ( x ) ≤ 0 , one can state that

either λi = 0 and Ci < 0, or λi > 0 and Ci = 0 Eq. 2•13

In the first part of Eq. 2•13, the constraint is satisfied in the interior of a feasible region (Ci < 0). The constraint
is inactive by setting the corresponding Lagrange multiplier λi = 0. In the second part of Eq. 2•13, the constraint
is on the edge of the feasible region. The constraint is active (Ci = 0), and the corresponding Lagrange multiplier

118 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
takes a positive value (λi > 0). This is the essence of the active set method for dealing with the inequality con-
straints. This “either - or” condition can be expressed as a trio

λ i ≥ 0 , A i ≤ 0, and λ i A i = 0 Eq. 2•14

This is the so-called Kuhn-Tucker condition. Although Eq. 2•14 is aesthetically more satisfying, we use Eq. 2•13
for practical coding of class “Active_Set”. The kernel of the problem is to compute the Lagrange multiplier
according to Eq. 2•10 as

λ = - ∇f / (∇A)T Eq. 2•15

where A denotes the active subset of C. When ∀λ i ≥ 0 , for “i” in the active set, the Kuhn-Tucker condition is
uphold, and the solution is optimal. Otherwise, select the constraint corresponding to the most negative λi, and
drop this constraint from the active set. The search direction p due to the deletion of this constraint satisfies1

Ap= - ei Eq. 2•16

where is the e i basis vector (with only “i”-th component = 1 and 0 elsewhere). Eq. 2•16 means that the active
constraints other than the “i”-th one is to be strictly satisfied (=0). We solve for p = - e i / A, and the next solution
is along the path x+α p. Therefore, the first constraint to be encountered (Ci = 0) along this search path is the
minimum positive α to satisfy Ci (x+α p) = 0. We have

min {αi = - Ci (xcurrent) / (d(C)i p), “i”-th constraint in the inactive set} Eq. 2•17

The constraint corresponding to this smallest positive αi (in the inactive set) will be added to the active set, A.
Program Listing 2•3 (project: “linear_programming_active_set”) implemented the class “Active_Set”. The
criterion to determine whether a constraint is active or not is replaced by

Ai > - ε, (instead of Ai = 0) Eq. 2•18

where ε is a small positive number. The “Active_Set” keeps track of the “active state” of each constraint in the
original constraint equations. Upon calling the public member function “Active_Set::activate()” the current
active set is assembled and a coefficient matrix will be formed. A public member function
“Active_Set::active_state(int)” requires an integer argument as the order of the original constraint equations and
returns the order of the current constraint equations in the active set. The coefficient matrix can be retrieved
using another free function “d( )” such as

C1 X(3, x), C(6, 3, (double*)0, (double*)0);


C[0] = 2*X[0]+ X[1]+ X[2] - 2;
C[1] = X[0]+2*X[1]+3*X[2] - 5;
C[2] = 2*X[0]+2*X[1]+ X[2] - 6;
C[3] = - X[0];

1. p. 176 in P.E. Gill, W. Murray, and M.H. Wright, 1981, “Practical Optimization”, Academic Press, Inc., San Diego.

Workbook of Applications in VectorSpace C++ Library 119


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
C[4] = - X[1];
C[5] = - X[2];
Active_Set A(C); // “C” is the constraints
A.activate(); // form active set and coefficient matrix
C0 lambda = - d(f) / (~d(A)); // Eq. 2•10 as λ = - ∇f / (∇A)T

The problem has been recast to the standard form for the active set method. For problems with equality con-
straints the Active_Set constructor can be called as

Active_Set A(C, 3);

A second integer argument number indicates the number of equality constraints. These equality constraints will
be always kept in the active set.
A minor technical detail of Active_Set is the private data member “_active_state” is initialized to “-1”. When
a constraint is determined to be included in the active set the value of “_active_state” is set to the order of the
constraint in the current active set. When a constraint is determined to be dropped from the active set, the value
of its “_active_state” is set to “-2”, which means this particular constraint can never be activated again. This
treatment may avoid possible “zigzagging or jamming” of the searching path caught in an infinite loop1.
Program Listing 2•3 implemented the active set algorithm. The core steps are

Step 1: Lagrange multiplier—compute λ = - ∇f / (∇A)T, if ∀λ i ≥ 0 stop, the solution is optimal.


Step 2: out—select the constraint corresponding to the most negative λi to be dropped from the active set.
Step 3: in— compute p = A-1ei, then, for all inactive “i”, αi = - Ci / (d(C)i p). If no αi is greater than 0, stop.
The constraint corresponding to the smallest positive αi is to be added to the active set.
Step 4: repeat Step 1-3.
Step 4 is written with a do-while control statement. The termination criterion is to have all Lagrange multi-
plier corresponding to the active constraints to be positive. This condition is the second part of Eq. 2•13.

1. see p. 330, Chapter 11 in D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley Publishing
Company, Inc., Reading, MA.

120 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization

#include "include/vs.h" class Active_Set


class Active_Set {
C0 _A;
coefficients of the active constraints
C1 *Constraint_ptr; C1* to constraint equations
int n_equality, size_c, n_active, *_active_state;
public:
Active_Set(C1& C, int n = 0);
~Active_Set() { delete [] _active_state; }
int active_state(int i) {return _active_state[i];} return order in active set
int active_no() { return n_active; }
C0& A() { return _A; }
total number of active constraints
void activate(); form active set
void deactivate(int i); drop out of the active set
friend C0& d(Active_Set&);
};
return active set coefficient matrix
Active_Set::Active_Set(C1& C, int n) {
Constraint_ptr = &C; number of equality constraints
n_equality = n;
size_c = Constraint_ptr->row_length();
_active_state = new int[size_c];
for(int i = 0; i < size_c; i++) _active_state[i] = -1; the initial state for all constraints is
}
void Active_Set::activate() {
“inactive”
n_active = 0;
for(int i = 0; i < n_equality; i++) { add equality constraints
_active_state[i] = n_active++;
}
for(int i = n_equality; i < size_c; i++) check inequality constraints to add on
if((double) ((C0)(*Constraint_ptr))[i] > -1.e-10) the active set
if(_active_state[i] >= -1) {
_active_state[i] = n_active++;
}
if(n_active > 0) _A &= C0(n_active, Constraint_ptr->col_length(),(double*)0); form coefficient matrix of the active set
for(int i = 0; i < size_c; i++)
if(_active_state[i] >= 0) _A[_active_state[i]] = d(*Constraint_ptr)[i];
}
void Active_Set::deactivate(int i) {
for(int j = 0; j < size_c; j++)
if(_active_state[j] == i) { _active_state[j] = -2; break; }
drop from active set
}
C0& d(Active_Set& a) { return a._A; }

Listing 2•5 class Active_Set data abstraction (project: “linear_programming_active_set”).

Workbook of Applications in VectorSpace C++ Library 121


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

int main() { 6 constraints, 3 variables


double x[3] = {0.0, 0.0, 0.0};
C1 X(3,x), C(6,3, (double*)0, (double*)0), f;
const int ALL_POSITIVE = 1;
const double EPSILON = 1.e-12; active set standard form
int lambda_flag;
f &= -3*X[0]-X[1]-3*X[2];
f(x) = -3x1 - x2 - 3x3
C[0] = 2*X[0]+ X[1]+ X[2] - 2; C[1] = X[0]+2*X[1]+3*X[2] - 5; 2x 1 + x 2 + x 3 – 2 , x 1 + 2x 2 + 3x 3 – 5
C[2] = 2*X[0]+2*X[1]+ X[2] - 6; C[3] = - X[0]; 2x 1 + 2x 2 + x 3 – 6 , -x4
C[4] = - X[1]; C[5] = - X[2];
Active_Set A(C);
-x5, -x6
do{
A.activate(); form active set and matrix d(A)
lambda_flag = ALL_POSITIVE;
C0 lambda = - d(f) / ~d(A);
step 1: λ = - ∇f / (∇A)T
int i_cache = -1;
double min_lambda = -EPSILON; step 2:select the most negative λi
for(int i = 0; i < A.active_no(); i++)
if((double)lambda[i] < -EPSILON) {
lambda_flag = !ALL_POSITIVE;
if((double)lambda[i] < min_lambda) {
min_lambda = lambda[i];
i_cache = i;
}
}
if(!lambda_flag) {
A.deactivate(i_cache);
drop from active set
C0 e(3,(double*)0); e[i_cache] = 1.0; step 3: adjacent extreme point
C0 p = - e / d(A); search direction p = A-1ei
double min_alpha = 1.e20;
int activate_flag = FALSE;
for(int i = 0; i < 6; i++) {
if(A.active_state(i) <= -1) {
double alpha, temp = (double)(d(C)[i]*p);
if(fabs(temp) > EPSILON)
αi = - Ci / (d(C)i p)
alpha = -(double)((((C0)C)[i])/temp); select smallest positive αi
if(alpha < min_alpha && alpha > 0.0) {
min_alpha = alpha;
activate_flag = TRUE;
}
}
}
if(activate_flag) {
((C0)X) += min_alpha * sd; update solution
((C0)C[0]) = 2*((C0)X[0])+ ((C0)X[1])+ ((C0)X[2]) - 2; update constraint values
((C0)C[1]) = ((C0)X[0])+2*((C0)X[1])+3*((C0)X[2]) - 5;
((C0)C[2]) = 2*((C0)X[0])+2*((C0)X[1])+ ((C0)X[2]) - 6;
((C0)C[3]) = - ((C0)X[0]); ((C0)C[4]) = - ((C0)X[1]); ((C0)C[5]) = - ((C0)X[2]);
}
}
} while(!lambda_flag);
until all λi are positive
cout << "solution: " << ((C0)X) << endl << "maximum objective functiona: " << x = {0.2, 0, 1.6}T
(3*((C0)X[0])+((C0)X[1])+3*((C0)X[2])) << endl; 5.4
return 0;
}

Listing 2•6 Active set method for linear programming (project: “linear_programming_active_set”).

122 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
2.3.2 Unconstrained Optimization
In this section, we deal with nonlinear objective functional without any constraint. The Newton-Raphson
method on page 110 is applied to an elliptic objective functional. The convergence is achieved in just one step. In
general, we may not encounter such an ideal situation. Therefore, we introduce a more challenging Rosenbrock’s
function.1

2
f(x1, x2) = 100 (x2-x12) + (1-x1)2 Eq. 2•19

The unique minimum point (1, 1) is at a “banana-shaped valley”. For all problems in this section, the initial point
is selected at (-1.2, 1) such that an “intelligent” search path will have to make a turn along the banana-shaped val-
ley to arrive at the minimum point. This objective functional could be used to test the robustness of an algorithm.
2

1 (-1.2, 1) (1, 1)

-1

-2
-2 -1 0 1 2
Figure 2.8 Rosenbrock’s function with minimum point at point (1, 1).

Classic Newton’s Method


Classic Newton’s method applied to the minimization of the Rosenbrock’s function is implemented in Pro-
gram Listing 2•7. Apart from the objective functional definition, the code is essential the same as Program List-
ing 2•2. The search path of the Newton iteration is shown in Figure 2.9. It takes only 6 iterations to get to the
final solution, because the rate of convergence of classic Newton’s method is known to be “quadratic”. However,
a wild search path was taken at the second iteration. We are just lucky that the search path did not bounce out of

1. from p. 96 in P.E. Gill, W. Murray, and M.H. Wright, 1981, “Practical Optimization”, Academic Press, Inc., San Diego.

Workbook of Applications in VectorSpace C++ Library 123


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

#include “include/vs.h”
#define EPSILON 1.e-12
#define MAX_ITER_NO 20
int main() {
double v[2] = {-1.2, 1.0}, energy_norm; initial values, x0 = {-1.2, 1}T
C2 x(2, v), f;
int count = 0;
do {
f &=100.0*(x[1]-x[0].pow(2)).pow(2)+(1.0-x[0]*x[1]).pow(2); f(x1, x2) = 100 (x2-x12)2 + (1-x1)2
C0 dx = - d(f) / dd(f); Eq. 2•7, dx = - f,x(xi) / f,xx(xi)
(C0) x += dx;
energy_norm = norm(dx*(C0)f);
update xi+1 = xi+dx
} while(++count < MAX_ITER_NO && energy_norm > EPSILON); energy norm = || dx f ||
if(count == MAX_ITER_NO) if convergence failed, output
cout << “Warning: convergence failed, energy norm: ” << energy_norm << endl;
else
energy norm
cout << “solution (” << count << “): ” << ((C0)x) << endl; x = {1, 1}T
return 0;
}

Listing 2•7 Minimization of Rosenbrock’s function using classic Newton’s method (project:
“newton_rosenbrock”).

-1

-2

-3

-2 -1 0 1 2

Figure 2.9 Searching path of Rosenbrock’s function using classic Newton’s


method.

124 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
the banana-shaped valley. Indeed, there is no guarantee that classical Newton’s method will always converge.
In fact, the wild search path in classical Newton method can be tamed by using line search method with either
bisection, or golden section1. The line search direction p can be taken from Newton’s formula that

p = - f,x(xi) / f,xx(xi) Eq. 2•20

Along p the solution is updated according to xi+1 = xi+α p, where α is a scalar parameter, and its optimal value is
determined by using line search, or even a scalar version of classical Newton’s method (see next section on
steepest descent method). We consider bisection and golden section here. For line search algorithm, the mini-
mum of a function is searched by evaluating the function and then comparing its values at selected bracketing
points. The basic idea is to have the bracketing interval contains the point with the minimum function value, and
at the same time make the bracketing interval smaller and smaller in an iterative algorithm.
Given a bracketing interval [a, c] for bisection method, the interval contains the point corresponding to the
minimum function value. At the middle of the interval is the point, b = (a+c)/2. The next bracketing point x is
taken as the middle of [b, c]; i.e., x = (b+c)/2. If f(x) > f(b), the next bracketing points are [a, x], otherwise, the
next bracketing points are [b, c]. Repeating this process, the bracketing interval will become smaller and smaller.
In the worst scenario, the selected intervals always lie on the larger segments. The bracketing intervals will
reduce at the rate of 0.75 2n = 0.5625n, where 2n is the number of repeated iterations. On the other hand, the best
case will be reducing at the rate of 0.252n = 0.0625n. On an average case, there is 50% chance of selecting either
larger or smaller segments, so the reducing rate is 0.25n 0.75n = 0.1875n.
Golden section finds an optimal ratio to avoid the worst case scenario compared to bisection method. Con-
sidering a triplet of points [a, b, c] with the ratio of interval [a, b] to interval [a, c] as α, the interval [b, c] to inter-
val [a, c] ratio will be 1-α. The next bracketing point x, lies to the right of b with interval [b, x] to interval [a, c]
ratio as β. First, since after comparison of function values the selected bracketing point can be either b or x, we
demand the symmetry of the two points by requiring that [a, x] (normalized length = α + β) and [b, c] (normal-
ized length = 1-α) to be equal. Therefore,

α + β = 1- α Eq. 2•21

Secondly, if x is to be selected as the next bracketing point, the ratio of interval [b, x] to interval [b, c] (= β/(1-α))
should be self-similar to the original ratio of interval [a, b] to interval [a, c] (= α). Therefore,

β/(1-α) = α Eq. 2•22

Eliminating β in Eq. 2•21 and Eq. 2•22, we have

α2 - 3α + 1 = 0 Eq. 2•23

One of the roots of Eq. 2•23 that is between [0, 1] is the ratio α = 0.381971, with the ratio of selecting the larger
segment as 1-α = 0.61803. Now, for the worst scenario the convergence rate reduces from 0.752n to 0.618032n.

1. p. 350 for “bisection”, and p. 399 for “golden section”, in W.H. Press, S.A. Teukolsky, W.T. Vetterlin, and B.P. Flannery,
1992, “ Numerical Recipes”, Cambridge University Press, Cambridge, UK.

Workbook of Applications in VectorSpace C++ Library 125


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
However, it is easy to see both the best case scenario and the average case scenario for golden section reduces
the bracketing length at a slower rate than bisection method. Program Listing 2•8 implements golden section line
search with classical Newton’s method for defining the search direction. The result is shown in Figure 2.10,
where the search path follows the banana shaped valley nicely. In Chapter 4, the least squares formulation for a
nonlinear finite element method on page 331 converges only after using the line search algorithm. In Chapter 5,
a relative large incremental step can be taken in a finite deformation elastoplastic finite element problem only
when the line search method is applied.
#include “include/vs.h”
static double EPSILON = 1.e-12; static int MAX_ITER_NO = 100
int main() {
double v[2] = {-1.2, 1.0}; C2 X(2, v); C0 d_x, p; int count = 0; initial values, x0 = {-1.2, 1}T
do {
C2 f = 100.0*(X[1]-X[0].pow(2)).pow(2)+(1-X[0]).pow(2);
f(x1, x2) = 100 (x2-x12)2 + (1-x1)2
p &= -d(f)/dd(f);
double left = 0.0, right = 1.0, length = right-left; p = - f,x(xi) / f,xx(xi)
C1 x0(0.0), x1(0.0), phi(0.0), alpha(0.0); initial bracket [0, 1]
do {
golden section at “x”
double alpha_temp = (C0) alpha = (left + 0.618 * length);
x0 = ((C0)X)[0] + alpha * p[0], x1 = ((C0)X)[1] + alpha * p[1];
phi = 100*(x1-x0.pow(2)).pow(2)+(1-x0).pow(2); φ(αx)
double golden_phi = (C0)phi; (C0) alpha = (left + 0.382 * length);
golden section at “b”
x0 = ((C0)X)[0] + alpha * p[0], x1 = ((C0)X)[1] + alpha * p[1];
phi = 100*(x1-x0.pow(2)).pow(2)+(1-x0).pow(2); φ(αb)
double left_phi = (C0)phi;
if(golden_phi < left_phi) { left = left + 0.382 * length; (C0)alpha = alpha_temp;
comparing φ(αx), and φ(αb) and reduce
} else { right = left + 0.618 * length; }
length = right-left; the bracketing interval
} while(length > 1.e-3);
d_x &= ((C0)alpha)*p; ((C0)X) += d_x;
update xi+1 = xi+α p
cout << "solution " << (++count) << ": " << "{" << ((C0)X)[0] << ", "
<< ((C0)X)[1] << "}" << endl;
} while(((double)norm(p)) > EPSILON && count < MAX_ITER_NO); until || p || converges
return 0;
x = {1, 1}T
}

Listing 2•8 Minimization of Rosenbrock’s function using classic Newton’s method (project:
“newton_rosenbrock” with macro definition “__TEST_GOLDEN_SECTION” defined at compile time).
2

-1

-2
-2 -1 0 1 2

Figure 2.10 Golden section line search with Newton’s method for search direction.

126 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
Steepest Descent Method
The classic Newton’s method requires second derivative information. In practical computation, this informa-
tion can be very costly. For example, in finite element method (see Chapter 4), this is equivalent to form the so-
called stiffness matrix. Even worse is when the problem size is large to get the inverse of the second derivative
information means we need to have a costly computation using matrix solver. It can be very demanding in terms
of computing time and memory space. The steepest descent method requires only first derivative information in
which the search direction p is

p = - ∇f Eq. 2•24

That is the search direction is taken along the negative gradient direction. This search direction makes intuitive
sense for the objective functional to decrease in the direction of the negative gradient. The solution is updated
through xi+1 = xi + α p, where the scalar α is the line search parameter. We seek a value of α that gives the mini-
mum value of f along the search direction p. For this one variable(α) optimization problem, the scalar version of
the Newton’s method can be used for solving optimal value of α. We may replace it with a more primitive
method such as the golden section line search1 described in previous section.
#include “include/vs.h”
#define EPSILON 1.e-12
#define MAX_ITER_NO 20
int main() {
double v[2] = {-1.2, 1.0}; initial values, x0 = {-1.2, 1}T
C1 X(2, v);
C0 dx;
int count = 0;
do { f(x1, x2) = 100 (x2-x12)2 + (1-x1)2
C1 f = 100.0*(X[1]-X[0].pow(2)).pow(2)+(1-X[0]).pow(2);
p = - g = - f,x(xi); the gradient
C0 g = d(f);
C2 alpha(0.0);
C0 d_alpha; line search along negative gradient
do {
xi+1 = xi + α p
C2 x0 = ((C0)X)[0] + alpha * -g[0],
x1 = ((C0)X)[1] + alpha * -g[1]; φ(xi+1)
C2 phi = 100*(x1-x0.pow(2)).pow(2)+(1-x0).pow(2); dα = -dφ / d 2φ; Newton’s formula
d_alpha &= - d(phi) / dd(phi);
α += dα
((C0)alpha) += d_alpha;
} while(((double)norm(d_alpha)) > EPSILON); dx = α p
dx &= ((C0)alpha)*(-g);
((C0)X) += dx;
update xi+1 = xi+dx
cout << "solution " << (++count) << ": " << ((C0)X) << endl;
} while(((double)norm(dx)) > EPSILON &&count < MAX_ITER_NO); norm = || dx ||
cout << "solution: " << ((C0)X) << endl; x = {1, 1}T
}

Listing 2•9 Minimization of Rosenbrock’s function using steepest descent method (project:
“steepest_descent”).

1. see p. 353 for bisection and p.397 for golden section search in W.H. Press, S.H. Teukolsky, W.T. Vetterling, and B.P. Flan-
nery, 1992, “Numerical Recipes in C”, Cambridge University Press, Cambridge, U.K.

Workbook of Applications in VectorSpace C++ Library 127


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
Program Listing 2•9 implements steepest descent method. The line search parameter α, is the only “variable”
in updating formula xi+1 = xi + α p, where xi and p are regarded as constants, and α is the parameter to search for
the minimum objective functional value. Therefore, φ in the definition for the objective functional in line search
algorithm is

f(x1, x2) = φ(xi+1(α)) = φ(α)

where “f” depends on 2 variables, or in more general case a multi-variable functional, while φ only depends on
one variable α. We note the difference of cost between doing multi-variable Newton’s method as described on
page 110 and one-parameter Newton’s method for the line search algorithm here. The resultant search path of
steepest descent method is shown in Figure 2.11. First of all the path shows the typical zigzag pattern consisting
of alternating orthogonal search directions. The convergence rate is extremely slow. After 100 iterations the
solution is still at (0.6, 0.36). The convergence becomes ever slower when it is approaching the true solution (1,
1). At 9660 iterations, the solution is still at (0.999941, 0.999883). Although each iteration in steepest descent
method is much cheaper than in Newton’s method, the Newton’s method takes only 6 iteration to get to (1, 1).

-1

-2
-2 -1 0 1 2
Figure 2.11 The search path of the steepest descent method up to 100 iterations.

128 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
Combined Newton and Steepest Descent Method
Newton’s method is fast, but we can only cross our fingers to see whether the initial point given is close
enough to the minimum that a convergent course will be taken. On the other hand, the steepest descent method
has a much more stable searching path, but its convergence rate is unacceptable. From Eq. 2•7 on page 109,
Newton’s formula is

dx = - f,x(xi) / f,xx(xi) = - g / H

where g (= f,x) is the gradient vector and H (= f,xx) is Hessian matrix. Now consider taking H = I, the above equa-
tion becomes

dx = - g

that is Newton’s method has degenerated into steepest descent method. A weighted approach to combine these
two methods together is1

dx = - α M g, where M = [ε I + H]-1 Eq. 2•25

α is the line search parameter in steepest descent method. However, the selection of the weighting parameter ε is
a matter of art. A more systematic way of implementing Eq. 2•25, or a probably more intelligent way, is to use
modified Cholesky decomposition2 introduced on page 32. The basic idea is to set M = H-1. Since Hessian
matrix is symmetrical, we can apply Cholesky decomposition. The problem with the Newton’s method is that the
objective functional may not be quadratic and the Hessian matrix may not be positive definite. When we apply
the Cholesky decomposition, we modify small or negative diagonals according to

d = max {d , δ}

where d is the modified diagonals and δ is a small positive number to be supplied to the modified Choelsky
decomposition on page 32. That is the degeneration of Newton’s method to steepest descent method occurs only
when the positive definitiveness of the Hessian matrix is in question.
Program Listing 2•10 implements combined steepest descent and Newton method using modified Choleksy
decomposition. The search path is shown in Figure 2.12. It takes 13 iterations to get to point(1, 1) about two
times iterations compared to the classic Newton’s method. However, the wild search path in Figure 2.9 has been
tamed successfully. Combined Newton and steepest descent method seems to be a more robust method than clas-
sic Newton method and steepest descent method.

1. p. 226-227 in D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley Publishing Company, Inc.,
Reading, MA.
2. p. 108-111 in P.E. Gill, W. Murray, and M.H. Wright, 1981, “Practical Optimization”, Academic Press, Inc., San Diego.

Workbook of Applications in VectorSpace C++ Library 129


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

#include “include/vs.h”
#define EPSILON 1.e-12
#define MAX_ITER_NO 20
int main() {
double v[2] = {-1.2, 1.0};
C2 X(2, v);
initial values, x0 = {-1.2, 1}T
C0 dx;
int k = 0;
do {
C2 f = 100.0*(X[1]-X[0].pow(2)).pow(2)+(1.0-X[0]).pow(2);
f(x1, x2) = 100 (x2-x12)2 + (1-x1)2
C0 g = d(f); modified Cholesky decomposition on M
Cholesky mcd(dd(f), EPSILON); p = - g / M;
C0 p = mcd * (-g);
C2 alpha(0.0), x0, x1;
line search along p
C0 d_alpha; xi+1 = xi + α p
do { φ(xi+1)
x0 &= ((C0)X)[0] + alpha * p[0]; x1 &= ((C0)X)[1] + alpha * p[1];
C2 phi = 100.0*(x1-x0.pow(2)).pow(2)+(1.0-x0).pow(2);
dα = -dφ / d2φ; Newton’s formula
if(fabs((double)dd(phi)) > EPSILON) d_alpha &= -d(phi) / dd(phi); α += dα
else break;
((C0)alpha) += d_alpha;
} while(((double)norm(d_alpha)) > 1.e-8);
dx = α p
dx = ((C0)alpha)*p; update xi+1 = xi+dx
((C0)X) += dx; norm = || dx ||
} while(((double)norm(dx)) > EPSILON);
cout << "final solution: " << ((C0)X) << endl;
x = {1, 1}T
}

Listing 2•10 Minimization of Rosenbrock’s function using modified Cholesky decomposition (project:
“combined_newton_and_steepest_descent”).

-1

-2
-2 -1 0 1 2
Figure 2.12 Search path using modified Cholesky decomposition.

130 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
Conjugate Gradient Method
When steepest descent method was introduced, one of the advantage mentioned is that the costly second
derivative information is not necessary, and the need to invert it can also be avoided. The inversion of the second
derivative information means that the solution of a matrix problem is bypassed. The above combined Newton
and steepest descent method although solves the problem of slow convergence of the steepest descent method, it
require the second derivative information and its inversion just as in classic Newton’s method. For some prob-
lems the second derivative information may not be available or very expensive to compute; e.g., the global stiff-
ness matrix of the finite element method in Chapter 4. What we should do is to fix the steepest descent method
that only requires first derivative information, and to improve its convergence rate. The basic idea is to make the
subsequent search direction pi+1 orthogonal to all previous search directions {p1. p2. ... pi }. For a quadratic
objective functional of dimension “n”, theoretically, “n” iterations will be sufficient to reach the exact solution.
We first approximate a general objective functional in quadratic form as

1
f ( x ) ≅ --- x T H x – b T x Eq. 2•26
2

The solution is sought along x i+1 = x i + α p i , where dx = α p i. Minimize f(x i+1) with respect to dx (where x i+1
= x i + dx) , we get

αi = - (pi)T∇f i / ( (pi)THpi)-1 Eq. 2•27

where ∇f = Hx - b = g. Therefore, we have

gi+1 - gi = H (xi+1- xi) = H (αi pi) Eq. 2•28

Since search directions pi are orthogonal to each other, we have (pi)TH pj = 0, for i ≠ j. From Eq. 2•28, we have

(gi+1 - gi)T pj = αi (pi)TH pj = 0. Eq. 2•29

The conjugate direction pi+1 that is orthogonal to all its previous directions is taken as

pi+1 = - gi+1 + Σ βi pi Eq. 2•30

Pre-multiplying Eq. 2•30 with (gi+1 - gi)T we have left-hand-side of Eq. 2•30 equal zero. In view of Eq. 2•29, βi
can be solved for as

βi = (gi+1 - gi)Tgi+1 / [(gi+1 - gi)Tpi] Eq. 2•31

Applying the orthogonal relations to Eq. 2•31 we have the Fletcher-Reeves formula

βi = (gi+1)Tgi+1 / [ (gi)Tgi] Eq. 2•32

or Polak-Ribiere formula

βi = (gi+1 - gi)Tgi+1 / [ (gi)Tgi] Eq. 2•33

Workbook of Applications in VectorSpace C++ Library 131


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
Program Listing 2•11 implements conjugate gradient method. The basic steps are1

Step 1: search direction—compute p0 = - g0 = - ∇f(x0)T,


Step 2: loop—partial conjugate gradient method, loop over “n” dimension
a: line search—x i+1 = x i + α p i to minimize f(x i+1) in place of Eq. 2•27
b: gradient at x i+1—gi+1 = ∇f(xi+1)T
c: search direction—pi+1 = - gi+1 + βi pi (Eq. 2•29), where
Fletcher-Reeves: βi= (gi+1)Tgi+1 / [ (gi)Tgi] (Eq. 2•32)or,
Polak-Ribiere: βi = (gi+1 - gi)Tgi+1 / [ (gi)Tgi] (Eq. 2•33)
Step 3: restart—repeat Step 1 and 2, and reset x0 = xn.
#include “include/vs.h”
int main() {
double v[2] = {-1.2, 1.0};
const double EPSILON = 1.e-12;
const int MAX_NO_OF_ITERATION = 30; initial values, x0 = {-1.2, 1}T
int k = 0;
C1 X(2, v);
C0 dx, p;
do {
for(int i = 0; i < 2; i++) {
C1 f = 100.0*(X[1]-X[0].pow(2)).pow(2)+(1.0-X[0]).pow(2);
f(x1, x2) = 100 (x2-x12)2 + (1-x1)2
C0 g = d(f); p = - g;
if(i == 0) p &= - g; line search along p
C2 alpha(0.0), x0, x1;
C0 d_alpha;
xi+1 = xi + α p
do { φ(xi+1)
x0 &= ((C0)X)[0] + alpha * p[0]; x1 &= ((C0)X)[1] + alpha * p[1]; dα = -dφ / d2φ; Newton’s formula
C2 phi = 100.0*(x1-x0.pow(2)).pow(2)+(1.0-x0).pow(2);
if(fabs((double)dd(phi)) > EPSILON) d_alpha &= -d(phi) / dd(phi);
α += dα
else break;
((C0)alpha) += d_alpha;
} while(((double)norm(d_alpha)) > 1.e-8);
dx &= ((C0)alpha)*p;
dx = α p
((C0)X) += dx; update xi+1 = xi+dx
if(i != 1 && ((double)norm(dx)) > EPSILON) {
C1 f1 = 100.0*(X[1]-X[0].pow(2)).pow(2)+(1.0-X[0]).pow(2);
C0 g1 = d(f1),
norm = || dx ||
beta = g1.pow(2) / g.pow(2); gi+1 = ∇f(xi+1)T
p = -g1 + beta * p; βi= (gi+1)Tgi+1 / [ (gi)Tgi]
}
cout << "solution(" << ++k << "): " << ((C0)X) << endl;
pi+1 = - gi+1 + βi pi
}
} while(((double)norm(dx)) > EPSILON && k < MAX_NO_OF_ITERATION);
cout << "The final solution: " << ((C0)X) << endl;
}
x = {1, 1}T

Listing 2•11 Minimization of Rosenbrock’s function using conjugate gradient method (project:
“conjugate_gardient_method”).

1. p. 253 in D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley Publishing Company, Inc.,
Reading, MA.

132 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
In the above steps, conjugate gradient algorithm is reset every “n” search directions. This is known as partial
conjugate gradient method. Recall that for a quadratic functional, theoretically, the conjugate gradient method
should arrive at the solution in “n” iterations. However, there are truncation errors in numerical computation. The
repeated loop is necessary to get to the final solution.
The result of the conjugate gradient method using only first derivative information is shown in Figure 2.13.
We set the program to terminate at 30 iterations. The solution at this stage is (0.995522, 0.990224). The conver-
gence rate has a dramatic improvement over the steepest descent method in which after 100 iterations the solu-
tion only gets up to the point (0.6, 0.36), and the result is an unacceptable in any standard. In Chapter 5, we
implemented the conjugate gradient method for an elastoplastic finite element problem.

-1

-2
-2 -1 0 1 2

Figure 2.13 Conjugate gradient method using only first derivative information, and 30
iterations.

Quasi-Newton Method
The advantage of both steepest descent method and conjugate gradient method is that it requires only first
derivative information. This is important especially for problems with large number of variables. However, we
saw classic Newton’s method or its modification that have second derivative information enjoys a faster conver-
gence rate. The strategy is we want to stick with first derivative method because of economical consideration.
Since along the iterative steps we have two sequences of first derivative information, {x0, x1, ... , xi} and {g0, g1,

Workbook of Applications in VectorSpace C++ Library 133


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
..., gi}, we can use these sequences of first derivative information to construct an approximated second derivative
information. For search direction pi = xi+1 - xi, and qi = gi+1 - gi, the finite difference quotient gives

H(xi) = (gi+1-gi)/(xi+1 - xi) Eq. 2•34

Eq. 2•34 is Hi pi = qi, which is also known as quasi-Newton condition. pi = qi / Hi = Bi qi , where Bi = (Hi)-1 is the
inverse of Hessian. We seek a rank one update formula with the form of

i+1 i
B = B +u⊗v Eq. 2•35

where u and v are vectors, which need further constraints. Enforcing quasi-Newton condition first, it can be
shown that the second term is

i i i
(p – B q ) ⊗ v
u ⊗ v = -------------------------------------
i
- Eq. 2•36
v•p

Bi+1 in Eq. 2•35 satisfy quasi-Newton condition but is not symmetrical. We can symmetrized it as (denoted with
superscript “s”)

(Bi+1)s = (Bi+1 + (Bi+1)T) / 2 Eq. 2•37

However, (Bi+1)s may not satisfy quasi-Newton condition. We can use the above two steps (1) quasi-Newton
condition, and (2) symmetrization repeatedly to yield a sequence of Bi+1. The limit of the sequence gives the
updating formula which is known as the Davidon-Fletcher-Powell (DFP) method

i i i i i i
i+1 i p ⊗p (B q ) ⊗ (B q )
B DFP = B + -----------------
i
- – ----------------------------------------
i i i i
Eq. 2•38
p •p q • (B q )

This is a rank-two update formula. If the update is performed on the Hessian H itself instead of its inverse B, we
have a complementary formula by substituting B for H, and p for q and vice versa. Then, taking inverse of this
expression gives the alternative Broyden-Fletcher-Goldfarb-Shanno (BFGS) updating formula

i i
i+1  q • ( B q ) p ⊗ p p ⊗ ( B q ) + ( B q ) ⊗ p
i i i i i i i i i
B BFGS = B i +  1 + --------------------------
i
- ------------------ – ----------------------------------------------------------------
i i i i
Eq. 2•39
 q • pi  p • p q •p
Program Listing 2•12 implemented the quasi-Newton method. The basic steps are1

Step 1: search direction—compute di = - Bi gi ,


Step 2: loop—partial quasi-Newton method, loop over “n” dimension
a: line search—x i+1 = x i + α d i to minimize f(x i+1), we get x i+1 , p i = α d i and

1. p. 265-267 in D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley Publishing Company, Inc.,
Reading, MA.

134 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
b: gradient difference—gi+1 = ∇f(xi+1)T, qi = gi+1 - gi
c: inverse of Hessian update—
i i i i i i
i+1 i p ⊗p (B q ) ⊗ (B q )
DFP: BDFP = B + -----------------
i
- – ----------------------------------------
i i i i
, or
p •p q • (B q )
i i
 q • ( B q ) p ⊗ p p ⊗ ( B q ) + ( B q ) ⊗ p
i i i i i i i i i
BFGS: B iBFGS
+1
= B i +  1 + --------------------------
- -----------------
- – ----------------------------------------------------------------
i i i i i
 q • pi  p • p q •p
Step 3: restart—repeat Step 1 and 2, and reset Bi .
There are two ways to think of initial B. The first is that B may not be available at all. So initially B is set to
identity matrix. The second is that B computation is very expensive, so B is only computed at the initial step of
every restart. In between quasi-Newton method takes over without having to have the second derivative informa-
tion. This method is popular in finite element method, in which the formation of global stiffness matrix and its
solution is equivalent to compute the inverse of Hessian—B. The result of BFGS computation is shown in Figure
2.14. It takes 34 iterations to arrive at the solution point (1, 1). In Chapter 5 we show an example of an elastoplas-
tic finite element problem implemented with the BFGS method.

-1

-2
-2 -1 0 1 2
Figure 2.14 Searching path of the BFGS method. The solution point (1, 1) is arrived at after
34 iterations.

Workbook of Applications in VectorSpace C++ Library 135


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

#include “include/vs.h”
int main() {
double v[2] = {-1.2, 1.0};
initial values, x0 = {-1.2, 1}T
const double EPSILON = 1.e-12;
const int MAX_NO_OF_ITERATION = 100;
int k = 0;
C0 dx;
C1 X(2, v);
C2 x(2, v);
do {
((C0)x) = ((C0)X);
C2 F = 100.0*(x[1]-x[0].pow(2)).pow(2)+(1.0-x[0]).pow(2); f(x1, x2) = 100 (x2-x12)2 + (1-x1)2
C0 B = dd(F).inverse(); B = H-1, initial inverse of Hessian
for(int i = 0; i < 2; i++) {
C1 f = 100.0*(X[1]-X[0].pow(2)).pow(2)+(1.0-X[0]).pow(2);
C0 g = d(f), d = B*(-g); d = - B g;
C2 alpha(1.0), x0, x1;
C0 d_alpha;
do {
line search along d
x0 &= ((C0)X)[0] + alpha * d[0]; xi+1 = xi + α d
x1 &= ((C0)X)[1] + alpha * d[1];
C2 phi = 100.0*(x1-x0.pow(2)).pow(2)+(1.0-x0).pow(2);
if(fabs((double)dd(phi)) > EPSILON) d_alpha &= -d(phi) / dd(phi);
φ(xi+1)
else break; dα = -dφ / d2φ; Newton’s formula
((C0)alpha) += d_alpha; α += dα
} while(((double)norm(d_alpha)) > 1.e-8);
dx &= ((C0)alpha)*d;
dx = α d
((C0)X) += dx; // update the solution update xi+1 = xi+dx
if(((double)norm(d_x)) > EPSILON) { norm = || dx ||
C1 f1 = 100*(X[1]-X[0].pow(2)).pow(2)+(1.0-X[0]).pow(2);
C0 g1 = - d(f1), p = dx, q = g1 -g, Bq = B * q;
p i = dx , qi = gi+1 - gi
B += (1.0+(q*Bq)/(q*p))*((p%p)/(p*q)) - (p%Bq+Bq%p)/(q*p); BFGS updating formula Eq. 2•39
}
cout << "solution(" << (++k) << "): " << ((C0)X) << endl;
}
} while(k < MAX_NO_OF_ITERATION && ((double)norm(dx)) > EPSILON);
cout << "Final solution: " << ((C0)X) << endl; x = {1, 1}T
}

Listing 2•12 Minimization of Rosenbrock’s function using BFGS method (project: “quasi_newton_bfgs”).

136 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
2.3.3 Constrained Optimization
In Section 2.3.1 on linear programming, we introduced (1) the basic set method and (2) the active set method.
In this section we will introduce (1) the reduced gradient method and (2) the gradient projection method. For
nonlinear optimization, these two methods are parallel to the previous two methods, respectively.

Reduced Gradient Method


The problem is expressed in a form, keeping close to the notations in linear programming,
minimize f(xB, xD)
subject to B xB + D xD = b
x B ≥ 0, x D ≥ 0

where “B” and “D” split the basic and non-basic sets of the constraint coefficients and variables. From the con-
straint equations we have

xB = B-1 b - B-1 D xD

Therefore, the objective functional can be expressed in xD alone as

f(B-1 b - B-1 D xD, xD)

The reduced gradient rT is the derivative of the objective functional f with respect to xD as

d xB
r T = ∇x B f ( x B, x D ) ---------- + ∇x D f ( x B, x D ) = ∇xD f ( x B, x D ) – ∇xB f ( x B, x D ) B –1 D = c D – c B B – 1 D Eq. 2•40
dx D

where c B = ∇xB f ( x B, x D ) and c D = ∇x D f ( x B, x D ) . “Basic_Set” class needs some modification for nonlinear
expression as shown in Program Listing 2•13. The coefficient vector of the objective functional cT = ∇f, and the
coefficient matrix of the constraint equations A [B, D] = dC, are now accessed through free functions “C0&
df(Basic_Set&)” and “C0& dC(Basic_Set&)”, which are declared as friend of class “Basic_Set”. The pointer to
variables are also declared as a private data member of the “Basic_Set”, which can be accessed by the member
function “C0& X()”
We consider a specific example1 already written in standard form as

minimize x12 +x22 + x32 + x42 - 2x1 - 3x4


subject to 2x1 + x2 + x3 + 4x4 = 7
x1 + x2 + 2x3 + x4 = 6
x 1 ≥ 0, x 2 ≥ 0, x 3 ≥ 0, x 4 ≥ 0

1. p. 347 in D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley Publishing Company, Inc.,
Reading, MA.

Workbook of Applications in VectorSpace C++ Library 137


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

#include “include/vs.h”
class Basic_Set {
C1 *_A, *_c, *_X;
class basic set
int row_size, col_size, *_basic_order; constraint, coefficients of the objective
public: functional, and variables
Basic_Set(C1&, C1&, C1&);
~Basic_Set() { delete [] _basic_order; }
int basic_order(int i) {return _basic_order[i];}
void swap(int i, int j);
C0& X();
friend C0& dC(Basic_Set&);
friend C0& df(Basic_Set&);
};

C0& Basic_Set::X() { return (*(_X)).F(); } variables

C0& dC(Basic_Set& a) { return d(*(a._A)); } constraint coefficients


C0& df(Basic_Set& a) { return d(*(a._c)); } objective functional coefficients

Basic_Set::Basic_Set(C1& C, C1& f, C1& X) {


row_size = C.row_length();
col_size = C.col_length();
_basic_order = new int[col_size];
for(int i = 0; i < col_size; i++) _basic_order[i] = i; initialize original variable order array
_A = &C; _c = &f; _X = &X;
}

void Basic_Set::swap(int i, int j) { swap order


int old_basic_order = _basic_order[i];
_basic_order[i] = _basic_order[j]; _basic_order[j] = old_basic_order;
C0 old_Ai(row_size, (double*)0); swap columns
old_Ai = d(*_A)(i); d(*_A)(i) = d(*_A)(j); d(*_A)(j) = old_Ai;
C0 old_ci(0.0); old_ci = d(*_c)[i];
d(*_c)[i] = d(*_c)[j]; d(*_c)[j] = old_ci; swap objective functional coefficients
C0 old_Xi(0.0); old_Xi = ((C0)(*_X))[i];
((C0)(*_X))[i] = ((C0)(*_X))[j]; ((C0)(*_X))[j] = old_Xi; swap variables
}

Listing 2•13 class Basic_Set data abstraction for nonlinear problem (project: “reduced_gradient”).

Program Listing 2•14 implements the reduced gradient method using class “Basic_Set” in Program Listing
2•13. The basic steps are

Step 1: reduced gradient—rT = cD - cB B-1 D,


Step 2: ∆xD—if ri < 0 or xDi > 0, ∆xDi = -ri else ∆xDi = 0
Step 3: ∆xB—if ∀ ∆xDi = 0, current solution is optimal, else ∆xB= −B-1 D∆xD
Step 4: feasible bounds—max { αB: xB+αB ∆xB ≥ 0 }, and max { αD: xD+αD ∆xD ≥ 0 }
Step 5: line search—min{f(x + α ∆x): 0 ≤ α ≤ α B, 0 ≤ α ≤ α D }, update with xi+1 = xi + α∆x
Step 6: swap—if α ≥ α B swap the vanishing xB with vanishing xD, and corresponding [B, D] and [cB, cD] .

The difference from the linear programming version is evident now that the fundamental theorem of linear pro-
gramming is not applicable any more; i.e., the extremum value may occur in the middle of an edge or even in the

138 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
interior of the feasible domain. It is not sufficient to check from vertex to vertex for optimal objective functional
value as was done in linear programming. The consequence is that a line search is performed for the minimum
value while the vertex value is used only as an upper bound (Step 4, and 5). The result of the optimal solution is
at x = {1.148, 0.683, 1.806, 0.553}T with the minimum objective functional value, “f = 1.40348”.

Workbook of Applications in VectorSpace C++ Library 139


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

#include “include/vs.h”
int main() {
initial feasible point, x0 = {2, 2, 1, 0}T
double rhs[2] = {7.0, 6.0}, v[4] = {2.0, 2.0, 1.0, 0.0}, norm_dxd,
EPSILON = 1.e-12, HUGE = 1.e20, RELAXED = 1.e3; constraints and objective functional
int k = 0, MAX_NO_OF_ITER = 10;
C1 X(4, v), C = VECTOR_OF_TANGENT_BUNDLE("int, int", 2, 4);
C[0] = 2x1 + x2 + x3 + 4x4
C[0] = 2*X[0]+ X[1]+ X[2]+4*X[3];
C[1] = X[0]+ X[1]+2*X[2] + X[3]; C[1] = x1 + x2 + 2x3 + x4
C1 f = X[0].pow(2)+X[1].pow(2)+X[2].pow(2)+X[3].pow(2)-2*X[0]-3*X[3]; f(x) = x12 +x22 + x32 + x42 - 2x1 - 3x4
Basic_Set BS(C, f, X);
A = [B, D], cT = [cBT, cDT],
C0 B(2, 2, dC(BS), 0, 0), D(2, 2, dC(BS), 0, 2), c_B(2, df(BS), 0), c_D(2, df(BS), 2);
C0 X_B(2, BS.X(), 0), X_D(2, BS.X(), 2), b(2, rhs), x(4, (double*)0);; xT = [xBT, xDT], bT = {7, 6}T
do { ∆xB, ∆xD
C0 d_X_B(2, (double*)0),d_X_D(2, (double*)0),
Step 1: rT = cD - cB B-1 D
B_inv = B.inverse(), r_D = c_D - c_B * B_inv * D;
for(int i = 0; i < 2; i++) Step 2: ∆xDi = -ri
if((double) r_D[i] < -EPSILON || (double) X_D[i] > EPSILON) d_X_D[i] = - r_D[i]; ∆xDi = 0
else d_X_D[i] = 0.0;
Step 3: if not ∀ ∆xDi = 0
if((norm_dxd = norm(d_X_D)) > RELAXED*EPSILON) {
d_X_B = - B_inv * D * d_X_D; ∆xB= −B-1 D∆xD
double alpha_B=HUGE,alpha_D=HUGE,ratio_B,ratio_D;int min_B=-1,min_D=-1; Step 4:
for(int i = 0; i < 2; i++) if((double)d_X_B[i] < EPSILON) {
max { αB: xB+αB ∆xB ≥ 0 },
ratio_B = (double) - X_B[i]/d_X_B[i];
if(ratio_B < alpha_B) { alpha_B = ratio_B; min_B = i; }
}
for(int i = 0; i < 2; i++) if((double)d_X_D[i] < EPSILON) {
max { αD: xD+αD ∆xD ≥ 0 }
ratio_D = (double) - X_D[i]/d_X_D[i];
if(ratio_D < alpha_D) { alpha_D = ratio_D; min_D = i; }
} Step 5:
C0 d_X = d_X_B & d_X_D, d_alpha;
∆xT = [∆xBT, ∆xDT]
C2 alpha(0.0), x[4];
do { line search along ∆x
for(int i = 0; i < 4; i++) xi+1 = xi + α ∆x
x[i] = ((C0)X)[BS.basic_order(i)] + alpha * d_X[BS.basic_order(i)];
φ(xi+1)
C2 phi = x[0].pow(2)+x[1].pow(2)+x[2].pow(2)+x[3].pow(2)-2*x[0]-3*x[3];
if(fabs((double)dd(phi)) > EPSILON) d_alpha &= -d(phi) / dd(phi); dα = -dφ / d 2φ; Newton’s formula
else break; α += dα
((C0)alpha) += d_alpha;
min{f(x + α ∆x):
} while((double)norm(d_alpha) > RELAXED*EPSILON);
if((double)(C0) alpha >= alpha_B) { 0 ≤ α ≤ α B, 0 ≤ α ≤ α D }
(C0) alpha = alpha_B; Step 6: update B and D
BS.swap(min_B, min_D+2);
}
if((double)(C0) alpha > alpha_D) (C0) alpha = alpha_D;
d_X *= ((C0)alpha); dx = α d
((C0)X) += d_X;
update xi+1 = xi+dx
} f = X[BS.basic_order(0)].pow(2)+X[BS.basic_order(1)].pow(2)+
X[BS.basic_order(2)].pow(2)+X[BS.basic_order(3)].pow(2) update f
-2*X[BS.basic_order(0)]-3*X[BS.basic_order(3)];
df(BS) = d(f);
update c
} while(++k < MAX_NO_OF_ITER && norm_dxd > RELAXED*EPSILON);
for(int i = 0; i < 4; i++) x[i] = ((C0)X)[BS.basic_order(i)]; update x
C0 fp = x[0].pow(2)+x[1].pow(2)+x[2].pow(2)+x[3].pow(2)-2*x[0]-3*x[3]; x = {1.148, 0.683, 1.806, 0.553}T
cout << "The final solution: " << x << endl << "f: " << fp << endl;
f = 1.40348
}

Listing 2•14 Reduced gradient method (project: “reduced_gradient”).

140 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
Gradient Projection Method
An inequality constrained problem will be tackled in this section using the active set method. In Figure 2.7 on
the introduction of Lagrange multiplier method we showed that for a point to be at extremum condition the gra-
dient of the objective functional ∇f can be expressed as linear combination of gradient of the active constraint
equations ∇A such as the first-order condition in Eq. 2•10

∇f +λT ∇A = 0

This optimal condition states that the two gradients are parallel to each other (in 2-D representation). In general
during the course of optimization, where the convergence has not yet achieved, these two gradients are not paral-
lel to each other, and we can project ∇f on the tangent plane such as at the point x’ (as in Figure 2.7). The pro-
jected gradient on the tangent plane is a vector d to be used as search direction is expressible as (see also Figure
2.15

p = −∇f − ∇A λ
T
Eq. 2•41
T
where the second term - ∇A λ is the component orthogonal to the tangent plane. In view of Eq. 2•41, p vanishes
when the left-hand-side equals zero; i.e., the first-order condition of an extremum point is satisfied. Since the
search direction p on the tangent plane is orthogonal to the gradient of the constraint equations ∇A, we have the
orthogonal relationship as ∇A p = 0. Pre-multiply Eq. 2•41 with ∇A and solve for λ,1

T -1
λ = -(∇A ∇A ) ∇A∇f Eq. 2•42

then, substitute back to Eq. 2•41 to eliminate λ, we obtain


T T -1
p = -[I - ∇A (∇A ∇A ) ∇A] g = - P g,
T T -1
where P = [I - ∇A (∇A ∇A ) ∇A] is the projection matrix which projects the negative gradient “- g” to the tan-
gent plane to define the search direction “p”.
“Active_Set” class has already been shown in Program Listing 2•3. Program Listing 2•15 implements the
gradient projection method. The basic steps are2

hypotenuse: -g = −∇f T
∇A λ

T
x p = ∇f - ∇A λ
tangent plane

Figure 2.15 Project negative gradient to the tangent plane as p (search direction).

1. p. 330-331 in D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley Publishing Company, Inc.,
Reading, MA.
2. modified from p. 332-333 in D.G. Luenberger, 1989, same as the above.

Workbook of Applications in VectorSpace C++ Library 141


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
Step 1: active set,
Step 2: projection matrix,
if number of constraints ≥ 1, P = [I - ∇AT (∇A ∇AT)-1 ∇A] , p = - P∇f
else p = - ∇f (no constraint, degenerate to steepest descent method)
Step 3: if p ≠ 0,
feasible bounds—min { αi: αi = - Ci / (d(C)i p)}
line search—min{f(x + α p): 0 ≤ α ≤ α i }, update with xk+1 = xk + α p
go to step 1
Step 4: else if p = 0,
Kuhn-Tucker condition—I = {i | Ci = 0}, for i ∈ I, if ∀ λi ≥ 0; break
deactivate—else deactivate the constraint with the most negative λ.
go to step 1.
A specific example solved in Program Listing 2•15 is 1
f(x1, x2) = 2x12 + x1x2 + x22 -12x1 -10 x2
subject to x1 + x2 ≤ 4
-x1 ≤ 0
-x2 ≤ 0

The objective functional is the same one that has been solved in Eq. 2•5 without constraints, and implemented in
Program Listing 2•2. The result of the present constrained optimization of Eq. 2•5 is shown in Figure 2.16. The
search path taken by this program is: (1) the initial feasible point is set at (0, 0). The first active set formed con-
sists of the second and the third constraints (-x1 ≤ 0, and -x2 ≤ 0). This initial active set will produce no search
direction (p = 0). (2) The Lagrange multiplier of this active set is {-12, -10}T. Therefore, the second constraint (
-x1 ≤ 0 with λ = -12) is dropped from the active set. (3) The search path, p, is along the x-axis. A line search
will be activated to find the minimum objective functional value of f(x1, x2) at (3, 0). (4) the third constraint is
dropped (-x2 ≤ 0 with λ = -10) from the active set leaving no constraint in the set. A steepest descent of objec-
tive functional f(x1, x2) produces a search direction parallel to y-axis. The first constraint (x1 + x2 ≤ 4) will be
encountered along this search direction at (3, 1). (5) With only one constraint left, a line search is performed
which gives (1.5, 2.5) as the minimum point. Since the only Lagrange multiplier is positive. (1.5, 2.5) is taken as
the final optimal solution.
In the gradient projection method, when there is no constraint, the method reduces to steepest descent
method. Figure 2.11 on page 128 shows an example of how steepest descent method can be a nightmare in the
computation. The steepest descent is really the backbone behind the gradient projection method. We need a sec-
ond-order method to improve the convergence rate.

1. p. 426-427 in D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley Publishing Company, Inc.,
Reading, MA.

142 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization

#include “include/vs.h”
int main() {
double v[2] = {0.0, 0.0}, EPSILON = 1.e-12; int ALL_POSITIVE = TRUE, lambda_flag; initial feasible point (0, 0)
C1 X(2,v), C = VECTOR_OF_TANGENT_BUNDLE("int, int", 3, 2);
C1 f = TANGENT_BUNDLE("int", 2);
f(x1, x2) = 2x12 + x1x2 + x22 -12x1 -10 x2
f = 2*X[0].pow(2)+X[0]*X[1]+X[1].pow(2)-12*X[0]-10*X[1];
C[0] = X[0] + X[1] -4; subject to x1 + x2 ≤ 4
C[1] = -X[0] ; C[2] = - X[1]; x1 ≤ 0, x2 ≤ 0
Active_Set A(C);
initialize active set
for(;;) {
A.activate(); Step 1. form active set
lambda_flag = !ALL_POSITIVE; Step 2: projection matrix
C0 lambda, p;
no constraint degenerated to
if(A.active_no() == 0) p &= -d(f);
else { steepest descent p = - ∇f
T -1
lambda &= - (d(A)*d(f)) / (d(A)*~d(A)); λ = -(∇A ∇A ) ∇A ∇f
p &= -d(f)- d(A)*lambda;
p = - ∇f - ∇ATλ , Eq. 2•41
}
if(fabs((double)norm(p)) > EPSILON) { Step 3: if p ≠ 0
double min_alpha = 1.e20; min{αi:αi =- Ci / (d(C)i p)}
for(int i = 0; i < 3; i++)
if(A.active_state(i) <= -1) {
double alpha, temp = (double)(d(C)[i]*p);
if(fabs(temp) > EPSILON) alpha = -(double)(((C0)C)[i]/temp);
if(alpha < min_alpha && alpha > 0.0) { min_alpha = alpha; active_flag = TRUE; }
}
C0 d_alpha(0.0); C2 alpha(0.0), x0, x1, F; line search
do { min{f(x + α p): 0 ≤ α ≤ α i }
x0 = ((C0)X[0]) + alpha * p[0]; x1 = ((C0)X[1]) + alpha * p[1];
F &= 2*x0.pow(2)+x0*x1+x1.pow(2)-12*x0-10*x1;
d_alpha = - d(F)/dd(F);
((C0)alpha) += d_alpha;
} while((double)norm(d_alpha) > EPSILON);
if((double)((C0)alpha) < min_alpha) min_alpha = (double)((C0)alpha);
C0 dx = min_alpha * p; ((C0)X) += dx; updates
((C0)C[0]) = ((C0)X[0]) + ((C0)X[1]) -4; xk+1 = xk + α p
((C0)C[1]) = -((C0)X[0]) ; ((C0)C[2]) = ((C0)X[1]);
C(xk+1)
f = 2*X[0].pow(2)+X[0]*X[1]+X[1].pow(2)-12*X[0]-10*X[1];
} else { f(xk+1)
int i_cache = -1; double min_lambda = -EPSILON; Step 4: if p = 0
lambda_flag = ALL_POSITIVE;
most negative λ
for(int i = 0; i < A.active_no(); i++)
if((double)lambda[i] < -EPSILON) { Kuhn-Tucker condition; Eq.
lambda_flag = !ALL_POSITIVE; 2•14
if((double)lambda[i] < min_lambda) { min_lambda = lambda[i]; i_cache = i; }
λ i ≥ 0 , A i ≤ 0, and λ i A i = 0
}
if (lambda_flag) break; until for Ai = 0 ⇒ ∀ λi ≥ 0
else A.deactivate(i_cache); drop constraint corresponding to
}
the most negative λ
cout << ((C0)X) << endl;
}
cout << "solution: " << ((C0)X) << endl;
return 0;
}

Listing 2•15 Gradient projection method (project: “gradient_projection”).

Workbook of Applications in VectorSpace C++ Library 143


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

8 x2

6
f(x1, x2) = 2x12 + x1x2 + x22 -12x1 -10 x2

(2, 4)
4

feasible region
(1.5, 2.5)
2
x1 + x2 = 4
(3, 1)
x1
0
(0, 0) (3, 0)

0 2 4 6 8
Figure 2.16 Active set method on a constrained quadratic functional

144 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
Lagrange Method
For simplicity we restrict the problem to linear equality constraints with quadratic objective functional; i.e.,
the equality constrained quadratic programming. For generalization to the inequality constrained problem with
inequality constraint equation as C(x). A(x), the subset of C(x), will be the currently active constraints. Consider
a quadratic program
1
minimize f(x) = -2 x T H x + g T x

subject to A(x) = Ax - b = 0

where H = f,xx is the Hessian matrix, and g = f,x is the gradient vector. The Lagrangian functional using the
Lagrange multiplier method such as Eq. 2•11 on page 118 is

T 1 T
l ( x, λ ) = f ( x ) + λ A ( x ) = --- x T H x + g T x + λ ( Ax – b ) Eq. 2•43
2

The Euler-Lagrange equations give the first-order optimal conditions of the Lagrangian functional with respect
to x and λ.

l,x(x,λ) = Hx+ATλ + g = 0
l,λ(x,λ) = Ax - b = 0 Eq. 2•44

The second-order optimal condition requires the Hessian matrix H be positive definite. That is always true for a
quadratic functional. An incremental version with xi+1 = xi +∆x can be substituted in f(x) and A(x). One can view
the expression f ( x i + ∆x ) ≅ f ( x i ) + gT ∆x + 1--- ( ∆x )T H ∆x as an approximation using second-order Taylor expan-
2
sion to the objective functional, with the current active constraint equations as

A(xi+1) = A(xi +∆x) ≅ A(xi) + ∇A ∆x = A(xi) + A ∆x = 0

With these relations, the Euler-Lagrange equations can be re-written in matrix form with the incremental solu-
tion, ∆x, as

H A
T
∆x – ∇f ( x i )
= Eq. 2•45
A 0 λ –A ( x i )

Using first equation in Eq. 2•45, we get ∆x = H-1 (−∇f(xi ) - ATλ). Notice that we have relied on the symmetrical
positive definitiveness of H to have its inverse. Substituting this back to eliminate ∆x in the second equation
gives an equation: AH-1 (−∇f(xi ) - ATλ) = -A(xi). Solving this equation for λ gives

λ = (AH-1AT)-1 [A(xi) - AH-1∇f(xi )] Eq. 2•46

We first compute λ, then, solve ∆x as

∆x = H-1 (−∇f(xi ) - ATλ). Eq. 2•47

Workbook of Applications in VectorSpace C++ Library 145


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
In summary, we compare the Lagrange method to the previous methods. One can view Eq. 2•41 and Eq. 2•42
for the gradient projection method as the simplified approximation of Eq. 2•46 and Eq. 2•47 for the Lagrange
method. First, in the gradient projection method we use only the first derivative information. If we set H-1 = I in
Eq. 2•47 we get ∆x = - ∇f(xi ) - ATλ which is Eq. 2•41 (with p = ∆x). Secondly, having H-1 = I in mind, gradient
projection method is projecting the gradient on the tangent plane of the constraint surface “A ∆x = 0” instead of
the approximated constraint surface “A ∆x = - A(xi)” in Lagrange method. Plugging in the tangent plane “A ∆x
= 0” in Eq. 2•45 and H-1 = I, the Eq. 2•46 becomes λ = -(∇A ∇A ) ∇A∇f, which is exactly Eq. 2•42. Therefore,
T -1

in Lagrange method, we use gradient projection Eq. 2•42 in place of Eq. 2•46. The procedure of using such
approximated Lagrange multiplier in Lagrange method is an example known as the multiplier update method.
The class “Active_Set” needs some modification (see Program Listing 2•16). In the previous examples, in
linear programing and gradient projection method, class “Active_Set” only needs to store and update the tangent
plane (of the constraint surface) information. In Lagrange method, besides the tangent plane information, the
information on the constraint surface itself, specifically A(xi), also needs to be stored and updated; i.e., “A ∆x =
- A(xi)” instead of “A ∆x = 0”.
#include “include/vs.h”
class Active_Set {
C1 _A, &Constraint;
class Active_Set
int n_equality, size_c, n_active, *_active_state; A(active constraints) and C
public:
Active_Set(C1& C, int n = 0);
~Active_Set() { delete [] _active_state; }
int active_state(int i) { return _active_state[i];}
int active_no() { return n_active; }
operator C0() { return ((C0)_A); }
void activate();
A(xi)
void deactivate(int i, int k = -2);
friend C0& d(Active_Set&); ∇A
};
Active_Set::Active_Set(C1& C, int n) : Constraint(C) {
n_equality = n; initialize active set
size_c = Constraint.row_length();
_active_state = new int[size_c];
for(int i = 0; i < size_c; i++) _active_state[i] = -1;
}
void Active_Set::activate() { activate active set
n_active = 0;
for(int i = 0; i < n_equality; i++) _active_state[i] = n_active++;
for(int i = n_equality; i < size_c; i++)
if((double) ((C0)Constraint)[i] > -1.e-10 &&_active_state[i] >= -1)
_active_state[i] = n_active++;
if(n_active > 0) {
_A &=VECTOR_OF_TANGENT_BUNDLE("int, int", n_active, Constraint.col_length());
for(int i = 0; i < size_c; i++) if(_active_state[i] >= 0) _A[_active_state[i]] = Constraint[i]; update A from C
}
}
void Active_Set::deactivate(int i, int k) { deactive a constraint
for(int j = 0; j < size_c; j++) if(_active_state[j] == i) { _active_state[j] = k; break; } }
C0& d(Active_Set& a) { return d(a._A); }

Listing 2•16 class Active_Set data abstraction for both Lagrange method and gradient projection method
(project: “lagrangian_and_gradient_projection”).

146 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
With the close relationship of gradient projection method and Lagrange method, we should implement the
two procedures in one program (see Program Figure 2.17). The macro definition “__LAGRANGE” is to be set at
compile time to build an executable module for Lagrange method. Without this macro definition set, gradient
projection method will be built as default. Program Listing 2•15 for gradient projection method and the current
Program Listing 2•17 is almost identical.
The result of Lagrange method is shown in Figure 2.17. The obvious difference is that when there is no con-
straint, Lagrange method takes a classic Newton direction, instead of a steepest descent direction, aiming at the
point (2, 4) which encounters the constraint of x 1 + x2 = 4 at point (2 2--- , 1 1--- ). This different search direction of
3 3
course doesn’t affect to the reach of the final solution at (1.5, 2.5).
f(x1, x2) = 2x12 + x1x2 + x22 -12x1 -10 x2
8 x2

(2, 4)
4

feasible region (1.5, 2.5)

2
(2.6667, 1.3333)
x1 + x2 = 4
x1
0
(0, 0) (3, 0)

0 2 4 6 8

Figure 2.17 Lagrange method with active set method on a constrained quadratic
functional

Workbook of Applications in VectorSpace C++ Library 147


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
#include “include/vs.h”
int main() {
double v[2] = {0.0, 0.0}, EPSILON = 1.e-12; int ALL_POSITIVE = TRUE, lambda_flag; initial feasible point (0, 0)
C1 X(2,v), C = VECTOR_OF_TANGENT_BUNDLE("int, int", 3, 2);
#if defined(__LAGRANGE) f(x1, x2) = 2x12 + x1x2 + x22 -12x1 -10 x2
C2 X2(2, v), f = 2*X2[0].pow(2)+X2[0]*X2[1]+X2[1].pow(2)-12*X2[0]-10*X2[1];
#else
C1 f = 2*X[0].pow(2)+X[0]*X[1]+X[1].pow(2)-12*X[0]-10*X[1];
#endif subject to x1 + x2 ≤ 4
C[0] = X[0] + X[1] -4; C[1] = -X[0]; C[2] = - X[1];
Active_Set A(C); x1 ≤ 0, x2 ≤ 0
for(;;) { initialize active set
A.activate(); lambda_flag = !ALL_POSITIVE; C0 lambda, p; Step 1. form active set
if(A.active_no() == 0)
#if defined(__LAGRANGE) Step 2: search direction p
p &= -d(f)/dd(f) no constraint degenerate to
#else classic newton p = - ∇f / ∇2f,
p &= -d(f);
#endif or, steepest descent p = - ∇f
else {
λ = (AH A )
#if defined(__LAGRANGE) -1 T -1
lambda &= (((C0)A)-d(A)*dd(f).inverse()*d(f))/(d(A)*dd(f).inverse()*(~d(A)));
[A(xi) - AH ∇f(xi )]
-1
p &= - dd(f).inverse()*(d(f)+(~d(A))*lambda);
p = H (−∇f(x ) - ATλ)
-1 i
#else
λ = -(∇A ∇A ) ∇A∇f
lambda &= - (d(A)*d(f)) / (d(A)*~d(A)); T -1
or,
p &= -d(f)- d(A)*lambda;
p = - ∇f - ∇A λ , Eq. 2•41
T
#endif
} Step 3: if p ≠ 0
if(fabs((double)norm(p)) > EPSILON) { double min_alpha = 1.e20; min{αi : αi = - Ci / (d(C)i p)}
for(int i = 0; i < 3; i++) if(A.active_state(i) <= -1) {
double alpha, temp = (double)(d(C)[i]*p);
if(fabs(temp) > EPSILON) alpha = -(double)(((C0)C)[i]/temp);
if(alpha < min_alpha && alpha > 0.0) { min_alpha = alpha; active_flag =
TRUE;}}
C0 d_alpha(0.0); C2 alpha(0.0), x0, x1, F;
do { x0 = ((C0)X[0]) + alpha * p[0]; x1 = ((C0)X[1]) + alpha * p[1]; line search
F &= 2*x0.pow(2)+x0*x1+x1.pow(2)-12*x0-10*x1; min{f(x + α p): 0 ≤ α ≤ α i}
d_alpha = - d(F)/dd(F); ((C0)alpha) += d_alpha;
} while((double)norm(d_alpha) > EPSILON);
if((double)((C0)alpha) < min_alpha) min_alpha = (double)((C0)alpha); updates
C0 dx = min_alpha * p; ((C0)X) += dx; xk+1 = xk + α p
((C0)C[0]) = ((C0)X[0]) + ((C0)X[1]) -4;
((C0)C[1]) = -((C0)X[0]) ; ((C0)C[2]) = ((C0)X[1]); C(xk+1)
#if defined(__LAGRANGE)
((C0)X2) = ((C0)X) ; f(xk+1)
f = 2*X2[0].pow(2)+X2[0]*X2[1]+X2[1].pow(2)-12*X2[0]-10*X2[1];
#else
f = 2*X[0].pow(2)+X[0]*X[1]+X[1].pow(2)-12*X[0]-10*X[1];
#endif Step 4: if p = 0
} else {
int i_cache = -1; double min_lambda = -EPSILON; lambda_flag = ALL_POSITIVE; most negative λ
for(int i = 0; i < A.active_no(); i++) Kuhn-Tucker condition; Eq.
if((double)lambda[i] < -EPSILON) { lambda_flag = !ALL_POSITIVE; 2•14
if((double)lambda[i] < min_lambda) { min_lambda = lambda[i]; i_cache = i; } }
if (lambda_flag) break; else A.deactivate(i_cache); λ i ≥ 0 , A i ≤ 0, and λ i Ai = 0
} until for Ai = 0 ⇒ ∀ λi ≥ 0
cout << ((C0)X) << endl; drop constraint corresponding to
} cout << "solution: " << ((C0)X) << endl;

Listing 2•17 Lagrange method and gradient projection method (project: “lagrangian_and_gradient_projection”).

148 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
Range Space and Null Space Methods
Both the reduced gradient method and the gradient projection method are known as reduced gradient type
method collectively. They have strong flavor of linear programming in them. At this point, for those who are
familiar with numerical linear algebra, we introduce the range space method and the null space method. The
range space and null space methods use the relatively efficient and reliable QR decomposition, that we have
introduced in Chapter 1. When rank deficiency in the constraint spaces becomes a concern, the more costly sin-
gular value decomposition can be used.
T
From Eq. 2•41, p = - ∇f - ∇A λ. The search direction p is lying on the tangent plane of the constraint surface
A; that is p is orthogonal to the gradient of the constraint surface A (=∇A) as (see also Figure 2.15)

A p = 0. Eq. 2•48

In other word, Eq. 2•48 expresses that the search direction p is in the null space of A. At the optimal condition, p
= 0, the negative gradient of the objective functional “- ∇f” is the linear combination of the range space of A (=∇
A); i.e., “- ∇f = ATλ”.

Range Space Method: Recall Eq. 2•46 and Eq. 2•47 from Lagrange method and consider projection of negative
gradient “−∇f” on the tangent plane M= {y| ∇Ay= 0}and gradient of constraint surface ∇A, we have

-1 T -1 -1
λ = - (AH A ) AH ∇f
-1 T
p = - H (∇f + A λ) Eq. 2•49
T T
A is of size m × n and 0 ≤ m ≤ n. We can perform QR decomposition on A , that is A = QR. Assuming no
T
degeneracy condition, the first “m” columns of Q span the range space of A . Denoting Y (matrix of size n × m)
T≡
consists the “m” columns of the range space. Substituting A YR into the first equation of Eq. 2•49, we have
the range space method1

T -1 -1 T -1
λ = - (Y H Y) Y H ∇f
-1
p = - H (∇f + Yλ) Eq. 2•50
T
On page 36 we discussed that the round-off error could accumulate in the multiplication of the normal form A A
in the least square problem, where the QR decomposition is used to control the condition number of the problem.
-1 T
In the first equation of Eq. 2•49, the condition number can increase by the multiplication operations in AH A ,
and we may run into trouble when its inverse is taken. Since columns of Y are orthonormal, in the first equation
T -1 -1
of Eq. 2•50, the condition number of Y H Y is as good as that of H . Therefore, the range space method with
Eq. 2•50 is numerically superior to Eq. 2•49.
The range space method is implemented in Program Listing 2•18 for solving the same problem that the
reduced gradient method solved in Program Listing 2•14. The core steps are

1. see p. 183-184 in P.E. Gill, W. Murray, and M.H. Wright, 1981, “Practical Optimization”, Academic Press, Inc., San
Diego.

Workbook of Applications in VectorSpace C++ Library 149


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

#include “include/vs.h”
int main() {
const double EPSILON = 1.e-12;
const double RELAXED = 1.e3;
const int MAX_NO_OF_ITERATION = 10;
double v[4] = {2.0, 2.0, 1.0, 0.0};
C2 X(4, v);
C2 C = VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE("int, int", 2, 4);
C[0] = 2*X[0]+ X[1]+ X[2]+4*X[3] - 7.0;
C[1] = X[0]+ X[1]+2*X[2]+ X[3] - 6.0;
C2 f = X[0].pow(2)+X[1].pow(2)+X[2].pow(2)+X[3].pow(2)-2*X[0]-3*X[3];
C0 p = VECTOR("int", 4);
C0 A = d(C), Q = QR(~A).Q(); AT = QR
C0 Y = MATRIX("int, int", 4, 2);
for(int i = 0; i < 2; i++) Y(i) = Q(i); Y has columns in the range space of Q
int k = 0;
do { -1
H
C0 H_inv = dd(f).inverse();
λ = - (YTH Y) YTH ∇f
-1 -1 -1
C0 lambda_bar = - ((~Y)*H_inv*Y).inverse() * (~Y) *H_inv* d(f);
p = - H (∇f + Yλ)
-1
p = - H_inv*(Y*lambda_bar+d(f));
((C0)X) += p;
f = X[0].pow(2)+X[1].pow(2)+X[2].pow(2)+X[3].pow(2)-2*X[0]-3*X[3];
++k;
cout << "solution{" << k << "): " << ((C0)X) << endl << "f: " << ((C0)f) << endl;
} while(k < MAX_NO_OF_ITERATION && (double)norm(p) > RELAXED*EPSILON); x = {1.148, 0.683, 1.806, 0.553}T
cout << "The final solution: " << ((C0)X) << endl << "f: " << ((C0)f) << endl;
} f = 1.40348

Listing 2•18 Range space method (project: “range_space”).

Step 1: range space, QR decomposition and form Y


Step 2: search direction,
Lagrange multiplier estimate: λ = - (YTH Y) YTH ∇f
-1 -1 -1

search direction: p = -H (∇f + Yλ)


-1

For inequality constrained problems, these core steps need to be embedded in a program with the aid of class
“Active_Set” such as what is implemented in Program Listing 2•17.

Null Space Method: Consider search direction p on tangent plane M= {y| ∇Ay= 0}, we have any such direction
satisfies

Ap=0

Denote Z contains “n - m” columns of the null space. The search direction p is the linear combination of the col-
umns of Z as

p = Z pz Eq. 2•51

where pz is a vector of size “n-m”. Taylor expansion to second-order such as Eq. 2•6 on page 109 with xi+1 = xi +
p is

150 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization

1
f ( x i + p ) ≅ f ( x i ) + f,x ( x i )p + --- p T H ( x i )p
2

Substituting Eq. 2•51 into the increment p of the last equation yields

1
f ( x i + p ) = f ( x i + Zp Z ) ≅ f ( x i ) + f ,x ( x i )Zp Z + --- p ZT Z T H ( x i )Zp Z Eq. 2•52
2

Minimization of Eq. 2•52 gives

p (= Z pz) = - Z(ZTHZ) ZT∇f


-1
Eq. 2•53

Denoting the projected Hessian as Hz = ZTHZ, and the projected gradient as (∇f)z = ZT∇f, we recover the classic
Newton method on the null space as

pz = - (∇f)z / Hz

Therefore, the meaning of the “n-m” vector pz is clear. From first equation of Eq. 2•45 we have Hp+ATλ + ∇f =
0. Substituting Eq. 2•53 into this equation, we can solve for the Lagrange multiplier if necessary (such as for ine-
quality constrained problems where values of λ is needed for the active set method)

λ = - (AAT)-1A(Hp+ ∇f) Eq. 2•54


Program Listing 2•19 implemented the null space method. The kernel steps are simple

Step 1: null space, QR decomposition and form Z


Step 2: search direction, p =- Z(ZTHZ) ZT∇f
-1

In retrospect, we should discuss the counterpart (dual) of the projected Hessian and projected gradient of null
space Hz = ZTHZ, and (∇f)z = ZT∇f, respectively. Assume x* as a local solution of the primal problem: minimize
f(x) subject to A(x) = 0. The lagrangian functional from Eq. 2•11 can be re-written for the dual problem as

l(x*, λ) = φ(x*(λ), λ) = φ(λ) = f(x*(λ)) + λT A(x*(λ))

The first order derivative of φ(λ) is

∇ λ φ ( λ ) = [ ∇f ( x∗ ( λ ) ) + λ T ∇A ( x∗ ( λ ) ) ]∇ λ x∗ ( λ ) + A ( x∗ ( λ ) ) Eq. 2•55

From the term, ∇f ( x∗ ( λ ) ) + λ T ∇A ( x∗ ( λ ) ) = 0 , we have

∇ λ φ ( λ ) = A ( x∗ ( λ ) ) Eq. 2•56

For the second order derivative of φ(λ)

∇ λ φ ( λ ) = ∇ λ A ( x∗ ( λ ) )∇ λ x∗ ( λ )
2
Eq. 2•57

Workbook of Applications in VectorSpace C++ Library 151


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

#include “include/vs.h”
int main() {
const double EPSILON = 1.e-12;
const double RELAXED = 1.e3;
const int MAX_NO_OF_ITERATION = 10;
double v[4] = {2.0, 2.0, 1.0, 0.0};
C2 X(4, v);
C2 C = VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE("int, int", 2, 4);
C[0] = 2*X[0]+ X[1]+ X[2]+4*X[3] - 7.0;
C[1] = X[0]+ X[1]+2*X[2]+ X[3] - 6.0;
C2 f = X[0].pow(2)+X[1].pow(2)+X[2].pow(2)+X[3].pow(2)-2*X[0]-3*X[3];
C0 p = VECTOR("int", 4);
C0 A = d(C);
C0 Q = QR(~A).Q();
C0 Z = MATRIX("int, int", 4, 2); AT = QR
for(int i = 0; i < 2; i++)Z(i) = Q(i+2); Z has columns in the null space of Q
int k = 0;
do {
p = Z * ((~Z)*dd(f)*Z).inverse() * (~Z) * -d(f); p = - Z(ZTHZ)-1 ZT∇f
((C0)X) += p;
f = X[0].pow(2)+X[1].pow(2)+X[2].pow(2)+X[3].pow(2)-2*X[0]-3*X[3];
++k;
cout<< "solution{" << k << "): " << ((C0)X) << endl << "f: " << ((C0)f) << endl;
} while(k < MAX_NO_OF_ITERATION && (double)norm(p) > RELAXED*EPSILON);
cout << "The final solution: " << ((C0)X) << endl << "f: " << ((C0)f) << endl; x = {1.148, 0.683, 1.806, 0.553}T
}
f = 1.40348
Listing 2•19 Null space method (project: “null_space”).

Taking derivative of ∇f ( x∗ ( λ ) ) + λ T ∇A ( x∗ ( λ ) ) = 0 with respect to λ we have

H ( x∗ ( λ ), λ )∇ λ x∗ ( λ ) + ∇ λ A ( x∗ ( λ ) ) = 0 Eq. 2•58

Therefore, we get

T
∇ λ x∗ ( λ ) = – H –1 ( x∗ ( λ ), λ )∇ λ A ( x∗ ( λ ) ) Eq. 2•59

Substituting Eq. 2•59 back to Eq. 2•57 we have

∇ λ φ ( λ ) = – ∇ λ A ( x∗ ( λ ) )H –1 ( x∗ ( λ ), λ )∇ λ A ( x∗ ( λ ) )
2 T
Eq. 2•60

2
That is ∇φ = A(x), and ∇ φ = - AT H-1 A. The classic Newton method for the dual problem coincides with the
first term in Eq. 2•46 of Lagrange method. The Hessian of the dual, “- AT H-1 A”, governs the convergence rate
of the dual problem.
It is helpful to point out that the existence and uniqueness of the constrained problem is known to be associ-
ated with the abstract form of a saddle-point problem1 (see Figure 2.18). A saddle function is shown as the

1. p. 30 in M.M. Sewell, 1987, “Maximum and minimum principles”, Cambridge University Press, Cambridge, UK.

152 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization

saddle point

λ
Figure 2.18 An example saddle function l(x, λ)= x2 - λ2, where x is the
minimizer and λ is the maximizer.
Lagrangian functional l(x, λ), which is minimized with respect to x and maximized with respect to λ. Requiring
both positive definitiveness of l,xx and negative definitiveness of l, λλ gives a strong (sufficient) condition for a
unique solution.
In summary, the range space method works on “m” dimensional space, where the most important step is to
estimate the Lagrange multiplier, a vector of length “m”. The null space method works on “n-m” dimensional
space, where we compute projected search direction pz (of length “n-m”) from projected gradient and projected
Hessian as pz = - (∇f)z / Hz. For small size problems, the null space method is numerically more reliable, because
in the range space method the inverse of the dual Hessian is a doubly inverted factor (YTH-1Y)-1 in the estimation
of Lagrange multiplier, which may cause significant cancellation errors. For a large size problem, the size of
(YTH-1Y)-1 in Eq. 2•50 of the range space method, and the size of (ZTHZ)-1 in Eq. 2•53 of the null space method
may significantly affect the size of the memory space, consequently, the cost of the inverse computation. For
number of constraints less than half of the variable number the range space method is less demanding, and for
number of constraints greater than half of the variable number null space has the advantage.

Penalty Methods
Penalty method transforms a constrained problem into an unconstrained problem by defining a penalty objec-
tive functional, for example, with a quadratic penalty term of active constraints as

minimize q(x) = f(x) + P(x) = f(x) + (ρ /2) A (x)T A(x) Eq. 2•61

where ρ is the penalty parameter and the second term is designed to penalized the objective functional when the
constraints are violated. The steps of the penalty method are

Workbook of Applications in VectorSpace C++ Library 153


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
Step 1: initial x0 and ρ0 , use x0 which satisfies A(x0) = 0, and starts with an initial small ρ0 to make
f(x0) has more weight compared to the P(x0) in the minimization to obtain a new solution x1.
Step 2: for loop, increase the penalty constant such that ρi+1 > ρi . Therefore, P(xi) has increasing
weight with respect to f(xi); i.e., active constraints are penalized increasingly.

The final solution is the limiting point x, with ρ → ∞ , although when ρ is too big the problem becomes ill-condi-
tioned. The advantage of penalty method lies on the simplicity of Eq. 2•61. No advanced concept needs to be
introduced. The disadvantage is that we are left with a incremental procedure in which the problem needs to be
solved many times with an empirical sequence of ρ. We emphasize that it is necessary to start with a smaller ρ,
and then, increase it subsequently. If we ignore the need for a incremental procedure and compute with only one
big ρ, the solution can be completely different. Starting with too big a penalty parameter the solution is to satisfy
constraints overwhelmingly with no concern of the minimization of the objective functional. However, in many
engineering applications some magic ρ is often recommended for their own application domain. The use of this
magic ρ is an art rather than science.
In view of the shape of a saddle function shown in Figure 2.18, it is obvious that the quadratic form of the
penalty function “P(x) = (ρ/2) || A(x) ||2” is the most popular one, where P,xx is positive semi-definite; i.e., the
penalty term convexifies the primal (x-variables). Comparing Eq. 2•10, “∇f +λT ∇A = 0” (the first-order condi-
tion), with the penalty objective functional

∇q(x) = ∇f(x) + ρ A(x)T ∇A(x) = 0,

we see

λ = ρ A(x) Eq. 2•62

This can be used as the updating formula λi+1 = λi + ρ A(x) for the simplest form of multiplier update method
discussed on page 146. This updating formula can be used for the augmented lagrangian method introduced
later.
Now consider a specific example we have been solving in previous sections
f(x1, x2) = 2x12 + x1x2 + x22 -12x1 -10 x2
subject to x1 + x2 ≤ 4
-x1 ≤ 0
-x2 ≤ 0

For simplicity we should drop the inequality constrained part of the problem and consider only the equality con-
straint

x1 + x2 = 4

Assume that we are at the final constraint set of the active set method. We use x = (2, 2) which is clearly on the
constraint line and use penalty method to search for the final solution (1.5, 2.5). The penalty objective functional
q(x) is defined as

154 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
A(x1, x2) = x1 + x2 - 4
ρ
q(x1, x2) = 2x12 + x1x2 + x22 -12x1 -10 x2 + --- (A (x1, x2) T A(x1, x2) )
2

These two equations are the kernel of the penalty method, and the original constrained problem has been trans-
form completely into an new unconstrained problem. Program Listing 2•20 implemented this simplified prob-
lem. This coding should be embedded into the active set method for a more general inequality constrained
problem.

#include “include/vs.h”
int main() {
const int DOF = 2; const int MAX_NO_OF_ITERATION = 20;
const double EPSILON = 1.e-12; double x[DOF] = {2.0, 2.0};
C2 q, A = TANGENT_OF_TANGENT_BUNDLE("int", DOF), X(DOF,x);
double rho = 1.0, delta_X;
C0 d_x, X_cache = VECTOR("int", DOF);
int k0 = 0;
do {
rho *= 10.0;
int k1 = 0; A(x1, x2) = x1 + x2 - 4
do { q(x1, x2) = 2x12 + x1x2 + x22 -12x1 -10
A = X[0] + X[1] -4; x2 + ρ--- (A(x1, x2)T A(x1, x2) )
q &= 2*X[0].pow(2)+X[0]*X[1]+X[1].pow(2)-12*X[0]-10*X[1] 2
+ (0.5*rho)*A.pow(2);
d_x &= -d(q) / dd(q); dx = - q,x(xi) / q,xx(xi)
(C0)X += d_x; x += dx
} while ((double)norm(d_x) > EPSILON && ++k1 < 10);
cout << "solution(rho=" << rho << ", " << k1 << "): " << ((C0)X) << endl;
delta_X = norm(X_cache - ((C0)X));
X_cache = ((C0)X);
} while(++k0 < MAX_NO_OF_ITERATION && delta_X > 1.e6*EPSILON);
cout << "The Final solution: " << ((C0)X) << endl; x = {1.5, 2.5}T
}

Listing 2•20 Penalty method with a single equality constraint (project: “penalty_one_constraint”).

Since the penalty problem has transformed an equality constrained problem into an unconstrained problem,
various unconstrained optimization methods in Section 2.3.2 are applicable to the penalty method. We apply
classic Newton method, conjugate gradient method, and combined Newton and steepest descent method to the
penalty method in the following. Consider a less trivial problem with 10 variables and 4 equality constraints such
as1

1. from p. 381 in D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley Publishing Company, Inc.,
Reading, MA.

Workbook of Applications in VectorSpace C++ Library 155


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
10
minimize f(x) = ∑ ix i2
i=0

subject to 1.5 x1 + x2 + x3 +0.5 x4 + 0.5 x5 = 5.5


2.0 x6 - 0.5 x7 -0.5 x8 + x9 - x10 = 2.0
x1 + x3 + x5 + x7 + x9 = 10
x2 + x4 + x6 + x8 + x10 = 15
Program Listing 2•21 implemented the classic Newton method version of the penalty method for the above
constrained problem. Definition of the penalty objective functional is q(x) = f(x) + (ρ /2) (A (x)T A(x) ), and the
classic Newton formula is dx = - q,x(xi) / q,xx(xi) with the update x += dx. The solution is x = {-2.00, 2.66, 2.39,
3.62, 3.27, 2.87, 3.87, 3.16, 2.47, 2.69}T with the minimum objective functional value, “f = 502.4”.

#include “include/vs.h”
int main() {
const int DOF = 10; const int MAX_NO_OF_ITERATION = 10;
const double EPSILON = 1.e-12; const double RELAXED = 1.e6;
double rho = 1.0, delta_X, x[DOF] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
C2 f, q, X(DOF,x),
A = VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE("int, int", 4, DOF);
C0 d_x, X_cache = VECTOR("int", DOF);
int k0 = 0;
do {
rho *= 10.0; A0 =1.5 x1+x2+x3 +0.5 x4 +0.5 x5- 5.5
int k1 = 0; A1 =2.0 x6-0.5 x7-0.5 x8+x9-x10-2.0
do { A2 =x1 + x3 + x5 + x7 + x9 - 10
A[0]=1.5*X[0]+X[1]+X[2]+0.5*X[3]+0.5*X[4] -5.5;
A[1]=2.0*X[5]-0.5*X[6]-0.5*X[7]+X[8]-X[9]-2.0; A3 =x2 + x4 + x6 + x8 + x10 - 15
A[2]= X[0] +X[2] + X[4] + X[6] + X[8] -10.0; 10
A[3]= X[1] + X[3] + X[5] + X[7] + X[9]-15.0; f(x) = ∑ ix i2
f &= X[0].pow(2)+2*X[1].pow(2)+3*X[2].pow(2)+4*X[3].pow(2)+5*X[4].pow(2)+ i=0
6*X[5].pow(2)+7*X[6].pow(2)+8*X[7].pow(2)+9*X[8].pow(2)+10*X[9].pow(2);
q &= f + (0.5 * rho) * A.pow(2); q(x) = f(x) + (ρ /2) (A(x) T A(x) )
d_x &= -d(q) / dd(q); dx = - q,x(xi) / q,xx(xi)
((C0)X) += d_x;
} while ((double)norm(d_x) > RELAXED*EPSILON x += dx
&& ++k1 < MAX_NO_OF_ITERATION);
cout << "solution(rho=" << rho << ", " << k1 << "): " << ((C0)X) <<
" f: " << ((C0)f) << " q: " << ((C0)q) << endl;
delta_X = norm(X_cache - ((C0)X));
X_cache = ((C0)X); x = {-2.00, 2.66, 2.39, 3.62, 3.27, 2.87,
} while(++k0 < MAX_NO_OF_ITERATION && delta_X > RELAXED*EPSILON); 3.87, 3.16, 2.47, 2.69}T
cout << "The Final solution: " << ((C0)X) << endl;
} f = 502.4

Listing 2•21 The classic Newton version for penalty method (project: “penalty_newton”).

Program Listing 2•22 implemented conjugate gradient method version of the penalty method for the same
equality constrained problem in the above. Conjugate gradient uses line search along search direction p as xi+1 =
xi + α p, and its objective functional is redefined to be a one-parameter function in α as ψ(xi+1(α)) = ψ(α). The
one-parameter line search uses Newton’s formula dα = -dψ(α)/d 2ψ(α) to find the minimum of ψ(α). Conjugate
gradient is computed using Fletcher-Reeves formula with gi+1=∇q(xi+1)T, and βi= (gi+1)Tgi+1 / [(gi)Tgi] as Eq.

156 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
2•32 on page 131. The next search direction is computed using pi+1 = - gi+1 + βi pi, accordingly. The final result is
essentially the same as the result of classic Newton method.
Combined Newton and steepest descent method version of penalty method is discussed next. When we intro-
duced combined Newton and steepest descent method, Newton method and steepest descent method are applied
simultaneously using modified Cholesky decomposition. The implementation is extremely simple. Here, we go
back to the basics. Denote the tangent plane of the constraints as M = {y | ∇A T y = 0}. First, Newton method is
applied on the orthogonal complement of the tangent plane M⊥. Then, the steepest descent method is applied on
M. These two steps are executed in a repeated sequences1. We first recall the penalty objective functional from
Eq. 2•61

q(x) = f(x) + (ρ /2) (A(x)T A(x) )

Define the gradient of q(x) as

∇q(x) = ∇ f(x) + ρ∇A(x)T A(x)

Denote Q(x) the Hessian of q(x) as

Q(x) = H(x) + ρ∇A(x)T ∇A(x) Eq. 2•63

For the Newton method on M⊥, any movement on this subspace can be expressed as xi+1 = xi + ∇A u. Define
T

such incremental movement on M⊥ for the penalty objective functional


T
q(u) = q(xi + ∇A (xi) u)

The gradient of q(u) is

∇q(u) =∇q(xi + ∇A(xi)T u) ∇A(xi)T

Then, denote Q(x) as the Hessian of q(u) at u = 0, and substitute Eq. 2•63 in place of Q(x), we have

T T
Q(xi) = ∇A(xi)( H(xi) + ρ∇A(xi) ∇A(xi)) ∇A(xi) Eq. 2•64

For ρ → ∞ , we can approximate Eq. 2•64 as

T
Q(xi) ≅ ρ(∇A(xi) ∇A(xi) )2 Eq. 2•65

Therefore, the Newton search direction p projected on M⊥ is

1. p. 282-284, and p. 384-387 in D.G. Luenberger, 1989, “Linear and Nonlinear Programming”, Addison-Wesley Publishing
Company, Inc., Reading, MA.

Workbook of Applications in VectorSpace C++ Library 157


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
#include “include/vs.h”
int main() {
const int DOF = 10; const int MAX_NO_OF_ITERATION = 10; int k0 = 0, k1 = 0;
const double EPSILON = 1.e-12; const double RELAXED = 1.e3;
C1 f, q, X(DOF,x), A = VECTOR_OF_TANGENT_BUNDLE("int, int", 4, DOF);
double c = 1.0, delta_X, x[DOF] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
C0 d_x, sd, delta_q, X_cache( DOF, (double*)0);
do {
rho *= 10.0; A0 =1.5 x1+x2+x3 +0.5 x4 +0.5 x5- 5.5
do {
for(int i = 0; i < 10; i++) {
A1 =2.0 x6-0.5 x7-0.5 x8+x9-x10-2.0
A[0]=1.5*X[0]+X[1]+X[2]+0.5*X[3]+0.5*X[4] -5.5; A2 =x1 + x3 + x5 + x7 + x9 - 10
A[1]=2.0*X[5]-0.5*X[6]-0.5*X[7]+X[8]-X[9]-2.0; A3 =x2 + x4 + x6 + x8 + x10 - 15
A[2]=X[0] +X[2] +X[4] +X[6] +X[8]-10.0;
10
A[3]=X[1] +X[3] +X[5] +X[7] +X[9]-15.0;
f &=X[0].pow(2)+2*X[1].pow(2)+3*X[2].pow(2)+4*X[3].pow(2)+ f(x) = ∑ ixi2
i=0
5*X[4].pow(2)+6*X[5].pow(2)+7*X[6].pow(2)+8*X[7].pow(2)+
9*X[8].pow(2)+10*X[9].pow(2); T
q &= f + (0.5 * rho) * A.pow(2);
q(x) = f(x) + (ρ /2) (A(x) A(x) )
C2 alpha(0.0), x[10], psi[4]; C0 d_alpha, g = d(q); int k2 = 0; p = - g;
if(i == 0) p &= - g; line search along p
do {
for(int j = 0; j < 10; j++) x[j] &= ((C0)X)[j] + alpha * p[j];
xi+1 = xi + α p
psi[0]=1.5*x[0]+x[1]+x[2]+0.5*x[3]+0.5*x[4]-5.5; ψ(xi+1)
psi[1]= 2.0*x[5]-0.5*x[6]-0.5*x[7]+x[8]-x[9]-2.0; dα= -dψ/d2ψ; Newton’s formula
psi[2]= x[0] + x[2] + x[4] + x[6] + x[8] -10.0;
psi[3]= x[1] + x[3] + x[5] + x[7] + x[9] -15.0;
C2 phi = x[0].pow(2)+2*x[1].pow(2)+3*x[2].pow(2)+4*x[3].pow(2)+
5*x[4].pow(2)+6*x[5].pow(2)+7*x[6].pow(2)+8*x[7].pow(2)+
9*x[8].pow(2)+10*x[9].pow(2)+(0.5 * rho) * (psi[0].pow(2)+
psi[1].pow(2)+psi[2].pow(2)+psi[3].pow(2));
if((double)dd(phi) > EPSILON) d_alpha &= -d(phi) / dd(phi); else break;
((C0)alpha) += d_alpha; α += dα
} while(++k2 < MAX_NO_OF_ITERATION &&
(double)norm(d_alpha) > RELAXED*EPSILON);
dx = α p
d_x &= ((C0)alpha)*p; ((C0)X) += d_x; update xi+1 = xi+dx
if(i != 9 && (double)norm(d_x) > EPSILON) { norm = || dx ||
A[0]=1.5*X[0]+X[1]+X[2]+0.5*X[3]+0.5*X[4]-5.5;
A[1]=2.0*X[5]-0.5*X[6]-0.5*X[7]+X[8]-X[9]-2.0;
A[2]=X[0] +X[2] +X[4] +X[6] +X[8]-10.0;
A[3]=X[1] +X[3] +X[5] +X[7] +X[9]-15.0;
C1 f1 = X[0].pow(2)+2*X[1].pow(2)+3*X[2].pow(2)+4*X[3].pow(2) +
5*X[4].pow(2)+6*X[5].pow(2)+7*X[6].pow(2)+8*X[7].pow(2)+
9*X[8].pow(2)+10*X[9].pow(2); conjugate direction
T
C1 q1 = f1 + (0.5 * rho) * A.pow(2); gi+1 = ∇q(xi+1)
T
C0 g1 = d(q1), beta = g1.pow(2) / g.pow(2);
p = -g1 + beta * p; delta_q &= (C0)q - (C0)q1;
βi= (gi+1)Tgi+1 / [ (gi) gi]
if((double)norm(delta_q) < RELAXED*EPSILON || pi+1 = - gi+1 + βi pi
(double)norm(d_x) < RELAXED*EPSILON) break;
}
}
} while (++k1 < MAX_NO_OF_ITERATION && (double)norm(d_x) >
RELAXED*EPSILON && (double)norm(delta_q)> RELAXED*EPSILON); x = {-2.00, 2.66, 2.39, 3.62, 3.27, 2.87,
delta_X = norm(X_cache - ((C0)X)); X_cache = ((C0)X); T
} while(++k0 < MAX_NO_OF_ITERATION && delta_X > RELAXED*EPSILON);
3.87, 3.16, 2.47, 2.69}
cout << "The Final solution: " << ((C0)X) << endl; f = 502.4
}

Listing 2•22 The conjugate gradient method for penalty formulation (project: “penalty_conjugate_gradient”).

158 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
p = -∇A(xi) T ∇q(0) T / Q(xi) ≅ ∇A T(xi) ∇q(xi)T ∇A (xi) / (ρ(∇A(xi) T ∇A(xi))2)
= - --ρ1- ∇A T(xi) (∇A(xi)∇A(xi) T )-2 ∇A (xi) ∇q(xi)T Eq. 2•66

The kernel steps of the combined Newton and steepest descent customized for penalty method are

Step 1: Newton method on M⊥,


search direction: p = - --ρ1- ∇A T(xi) (∇A(xi)∇A(xi) T )-2 ∇A (xi) ∇q(xi)T
line search: minimize q(xi + α p), update ri = xi + α p
Step 2: steepest descent on M,
search direction: p = - ∇q(ri)T
line search: minimize q(ri + α p), update xi+1 = ri + α p

We implemented these two steps with three C++ functions in Program Listing 2•23: (1) the line search is per-
formed in both steps. We factor out this procedure and code it into a “line_search()” function, (2) Newton method
applied to M⊥ is implemented as “newton_on_orthogonal_complement_of_tangent()”, and (3) steepest descent
on M is implemented as “steepest_descent_on_tangent()”. Program Listing 2•24 implemented the main program
of the combined Newton and steepest descent method using the above three functions.

Workbook of Applications in VectorSpace C++ Library 159


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects

#include “include/vs.h”
void line_search(C2& X, C0& p, C2& alpha, double rho) {
const int DOF = 10; const double EPSILON = 1.e-12;
const double RELAXED = 1.e6; const int MAX_NO_OF_ITERATION = 10;
((C0)alpha) = 0.0;
C2 x[DOF], A[4]; C0 d_alpha; int k2 = 0;
do { line search along p
C2 phi; xi+1 = xi + α p
for(int j = 0; j < 10; j++) x[j] &= ((C0)X)[j] + alpha * p[j];
A[0]=1.5*x[0]+x[1]+x[2]+0.5*x[3]+0.5*x[4] -5.5;
A0 =1.5 x1+x2+x3 +0.5 x4 +0.5 x5- 5.5
A[1]= 2.0*x[5]-0.5*x[6]-0.5*x[7]+x[8]-x[9]-2.0; A1 =2.0 x6-0.5 x7-0.5 x8+x9-x10-2.0
A[2]=x[0] +x[2] +x[4] +x[6] +x[8] -10.0; A2 =x1 + x3 + x5 + x7 + x9 - 10
A[3]=x[1] +x[3] +x[5] +x[7] +x[9]-15.0;
phi &= x[0].pow(2)+2*x[1].pow(2)+3*x[2].pow(2)+4*x[3].pow(2)+5*x[4].pow(2)+
A3 =x2 + x4 + x6 + x8 + x10 - 15
6*x[5].pow(2)+7*x[6].pow(2)+8*x[7].pow(2)+9*x[8].pow(2)+10*x[9].pow(2)+ 10
(0.5 * rho) *A.pow(2);
if((double)dd(phi) > EPSILON) d_alpha &= -d(phi) / dd(phi); else break;
f(x) = ∑ ixi2
i=0
((C0)alpha) += d_alpha; T
} while(++k2 < MAX_NO_OF_ITERATION && φ(x) = f(x) + (ρ /2) (A A)
(double)norm((double)d_alpha) > RELAXED*EPSILON); dα= -dφ/d2φ; Newton’s formula
}
void newton_on_orthogonal_complement_of_tangent(
C2& X, C2& f, C2& A, C2& q, C0& p, double rho) { Newton method on M⊥
A[0]=1.5*X[0]+X[1]+X[2]+0.5*X[3]+0.5*X[4] -5.5;
A[1]= 2.0*X[5]-0.5*X[6]-0.5*X[7]+X[8]-X[9]-2.0;
A[2]=X[0] +X[2] +X[4] +X[6] +X[8] -10.0;
A[3]=X[1] +X[3] +X[5] +X[7] +X[9]-15.0;
f &= X[0].pow(2)+2*X[1].pow(2)+3*X[2].pow(2)+4*X[3].pow(2)+5*X[4].pow(2)+
T
6*X[5].pow(2)+7*X[6].pow(2)+8*X[7].pow(2)+9*X[8].pow(2)+10*X[9].pow(2);
q &= f + (0.5 * rho) * A.pow(2);
Q(xi) ≅ --ρ1- (∇A(xi) ∇A(xi) )-2
C0 grad_A = d(A), approx_Q_bar_inv;
T T
approx_Q_bar_inv &= 1.0/rho * (grad_A*(~grad_A)).pow(2).inverse(); p = -∇A (xi) Q(xi) ∇A (xi) ∇q(xi)
p &= (~grad_A) * approx_Q_bar_inv * grad_A * -d(q);
}
void steepest_descent_on_tangent(C2& X, C2& f, C2& A, C2& q, C0& p, double rho) { steepest descent on M
A[0]=1.5*X[0]+X[1]+X[2]+0.5*X[3]+0.5*X[4]-5.5;
A[1]=2.0*X[5]-0.5*X[6]-0.5*X[7]+X[8]-X[9]-2.0;
A[2]=X[0] +X[2] +X[4] +X[6] +X[8]-10.0;
A[3]= X[1] + X[3] + X[5] + X[7] + X[9]-15.0;
f &= X[0].pow(2)+2*X[1].pow(2)+3*X[2].pow(2)+4*X[3].pow(2)+5*X[4].pow(2)+
6*X[5].pow(2)+7*X[6].pow(2)+8*X[7].pow(2)+9*X[8].pow(2)+10*X[9].pow(2);
q &= f + (0.5 * rho) * A.pow(2); T
p &= -d(q); p = - ∇q(ri)
}

Listing 2•23 Three functions of the combined Newton and steepest descent version of penalty method (project:
“penalty_combined_newton_and_steepest_descent”).

160 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization

#include “include/vs.h”
int main() {
const int DOF = 10;
const int MAX_NO_OF_ITERATION = 12; int MAX_NO_OF_INNER_ITERATION = 6;
const double EPSILON = 1.e-12; const double RELAXED = 1.e6;
double rho = 0.1, delta_X, x[DOF] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
C2 f, q, X(DOF,x),
A = VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE("int, int", 4, DOF);
C0 d_x, X_cache = VECTOR("int", DOF);
int k0 = 0;
double q_cache, d_q;
do {
rho *= 10.0;
int k1 = 0;
q_cache = d_q = 0.0;
MAX_NO_OF_INNER_ITERATION = ((rho < 1.e6) ? 1: 6);
do {
C0 p;
newton_on_orthogonal_complement_of_tangent(X, f, A, q, p, rho); call Newton method on M⊥
C2 alpha(0.0); line search
line_search(X, p, alpha, rho);
d_x &= ((C0)alpha)*p;
((C0)X) += d_x;
steepest_descent_on_tangent(X, f, A, q, p, rho); call steepest descent on M
((C0)alpha) = 0.0;
line_search(X, p, alpha, rho);
d_x &= ((C0)alpha)*p; line search
((C0)X) += d_x;
d_q = fabs( ((double)(C0)q)-q_cache );
q_cache = (double)(C0)q;
cout << "(c=" << c << ", k1=" << k1 << ")--" << " f: " << ((C0)f) << " q: " << ((C0)q)
<< " d_q: " << d_q << endl;
if((double)norm(d_x) < RELAXED*EPSILON && d_q < RELAXED*EPSILON)
break;
} while (++k1 < MAX_NO_OF_INNER_ITERATION && (double)norm(d_x) >
RELAXED*EPSILON && d_q > RELAXED*EPSILON);
cout << "solution(c=" << c << ", " << k1 << "): ";
cout << ((C0)X) << " f: " << ((C0)f) << " q: " << ((C0)q) << " d_q: " << d_q << endl;
delta_X = norm(X_cache - ((C0)X));
X_cache = ((C0)X); x = {-2.00, 2.66, 2.39, 3.62, 3.27, 2.87,
} while(++k0 < MAX_NO_OF_ITERATION && delta_X > RELAXED*EPSILON); T
cout << "The Final solution: " << ((C0)X) << endl;
3.87, 3.16, 2.47, 2.69}
return 0; f = 502.4
}

Listing 2•24 The main program of the combined Newton and steepest descent version of penalty method
(project: “penalty_combined_newton_and_steepest_descent”).

Workbook of Applications in VectorSpace C++ Library 161


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
Augmented Lagrangian Method
Augmented Lagrangian method is ready to be tackled, since we have introduced both Lagrange method and
penalty method. For an equality constrained problem the augmented Lagragian functional is

lA(x, λ) = f(x) + λT A(x) + (ρ /2) A(x)T A(x) Eq. 2•67

First, the obvious feature is now we are working on an augmented space of {x, λ} with the dimension “n+m”.
Second, from “dual view point”, comparing Eq. 2•67 with the Lagrangian functional in Lagrange method on
T
page 145, Eq. 2•67 has an extra penalty term (ρ /2) A(x) A(x). In view of the existence and uniqueness (strong)
condition of the saddle function shown in Figure 2.18. This quadratic penalty term in “x” convexifies the primal
(x-variables) of the Lagrangian functional l(x, λ). Third, from “penalty view point”, Eq. 2•67 without the middle
term is the penalty objective functional ( q(x) in Eq. 2•61). The penalty method is always plagued by being not
consistent with the minimum of the Lagrangian functional l(x, λ). We can easily show this by considering that
the first-order conditions of the constraint problem (l,x) and unconstrained problem (∇q) are not equal

l,x = ∇f(x) + λ ∇A(x) ≠ ∇q = ∇f(x) + ρA(x) ∇A(x)


T T

unless a special condition λ = ρA(x), Eq. 2•62, is met. On the other hand, the first derivative of the augmented
Lagrangian functional (lA) is

∇f(x) + λ ∇A(x) + ρA(x) ∇A(x) = 0


T T

This is consistent with the first-order condition of Lagrange method as

∇f(x) + λ ∇A(x) = 0
T

A(x) = 0

Therefore, the middle term λ A(x) in Eq. 2•67 makes the penalty method consistent with the first-order condi-
T

tion of Lagrange method. The algorithm of augmented Lagrangian method can be implemented with a nested
double loop. The outer loop is the penalty method in which we need to increase the penalty parameter ρ from a
smaller number to a considerable greater number until the solution converge. The inner loop is to update the
Lagrange multiplier, in view of λ = ρA(x), as

λi+1 = λi + ρA(xi ) Eq. 2•68

in the hope that A(xi+1) = 0 can be achieved by this update. Program Listing 2•25 implemented augmented
Lagrangian method.

Perturbed Lagrangian Method


Augmented Lagrangian method has a quadratic penalty term that convexifies the primal. In the context of
quadratic programming, the Hessian of the primal is already positive definite. To make the condition for the
existence and uniqueness of a saddle point problem, we really want to convexfies the dual (λ-variables), which

162 Workbook of Applications in VectorSpace C++ Library


Linear Programming and Non-linear Optimization
#include “include/vs.h”
int main() {
const int DOF = 10; const int MAX_NO_OF_ITERATION = 10;
const double EPSILON = 1.e-12; const double RELAXED = 1.e6;
double rho = 1.0, delta_X, x[DOF] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
C2 f, l_A, X(DOF,x),
A = VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE("int, int", 4, DOF);
lambda = VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE("int, int", 4, DOF);
C0 d_x, X_cache(DOF, (double*)0);
int k0 = 0;
do {
rho *= 10.0;
int k1 = 0;
do {
A[0]=1.5*X[0]+X[1]+X[2]+0.5*X[3]+0.5*X[4]-5.5;
A[1]=2.0*X[5]-0.5*X[6]-0.5*X[7]+X[8]-X[9]-2.0;
A[2]=X[0] +X[2] +X[4] +X[6] +X[8]-10.0;
A[3]=X[1] +X[3] +X[5] +X[7] +X[9]-15.0;
f &= X[0].pow(2)+2*X[1].pow(2)+3*X[2].pow(2)+4*X[3].pow(2)+5*X[4].pow(2)+
6*X[5].pow(2)+7*X[6].pow(2)+8*X[7].pow(2)+9*X[8].pow(2)+10*X[9].pow(2); λi+1 = λi + ρA(xi)
lambda += rho * A; lA =f(x) + λT A(x) + (ρ /2) A(x)T A(x)
l_A &= f + lambda*A + (0.5 * rho) * A.pow(2);
d_x &= -d(l_A) / dd(l_A);
dx = - lA,x / lA,xx
((C0)X) += d_x;
} while (++k1 < MAX_NO_OF_ITERATION &&
(double)norm(d_x) > RELAXED*EPSILON);
cout << "solution(rho=" << rho << ", " << k1 << "): " << ((C0)X) <<
" f: " << ((C0)f) << " q: " << ((C0)q) << endl;
delta_X = norm(X_cache - ((C0)X)); x = {-2.00, 2.66, 2.39, 3.62, 3.27, 2.87,
X_cache = ((C0)X);
} while(++k0 < MAX_NO_OF_ITERATION && delta_X > RELAXED*EPSILON);
3.87, 3.16, 2.47, 2.69}T
cout << "The Final solution: " << ((C0)X) << endl; f = 502.4
}

Listing 2•25 Augmented Lagrangian method (project: “augmented_lagrangian”).

does not necessary have negative definite Hessian. Consider the Lagrangian functional l(x, λ), a perturbed
Lagrangian functional lp(x, λ) can be defined as

ε ε
lp(x, λ) = l(x, λ) – --- λ 2 = f(x) + λT A(x) – --- λ 2 Eq. 2•69
2 2

The solution is achieved at the limit ε → 0 . The gradient of the perturbed Lagrangian function (the Euler-
Lagrange equations) is

lp,x = ∇f(x) + λT ∇A(x) = 0


lp,λ = A(x) - ε λ = 0 Eq. 2•70

It is consistent with the first-order condition of Lagrange method (at the limit of ε → 0 ). For a quadratic pro-
gramming problem
1
minimize f(x) = -2 x T H x + g T x
subject to A(x) = Ax - b = 0

Workbook of Applications in VectorSpace C++ Library 163


Chapter 2 Numerical Optimization Using C1 and C2 Type Objects
Eq. 2•70 can be re-written in matrix form with the incremental solution ∆x as

∆ x = – ∇f ( x )
T i
HA Eq. 2•71
A ε λ –A ( x )i

Considering the limit of ε → 0 , ε is in place of a null submatrix in Eq. 2•45

HA
T
∆x – ∇f ( xi )
=
A 0 λ – A ( xi )

Since the left-hand-side matrix is symmetrical we can use modified Cholesky decomposition to solve Eq. 2•71.
Or, recall Eq. 2•45 that λ = (AH-1AT)-1 [A(xi) - AH-1∇f(xi )] Set B-1 = (AH-1AT)-1 and apply modified Cholesky
decomposition to B. Not only the implementation in Program Listing 2•26 is extremely simple, but also the
computing speed is lightning fast.
#include “include/vs.h”
int main() {
const int DOF = 10; const int MAX_NO_OF_ITERATION = 20;
const double EPSILON = 1.e-12; const double RELAXED = 1.e6;
double x[DOF] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
int k = 0;
C2 f, X(DOF,x),
A = VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE("int, int", 4, DOF);
C0 d_x;
do {
A[0]=1.5*X[0]+X[1]+X[2]+0.5*X[3]+0.5*X[4] -5.5;
A[1]= 2.0*X[5]-0.5*X[6]-0.5*X[7]+X[8]-X[9]-2.0;
A[2]= X[0] + X[2] + X[4] + X[6] + X[8] -10.0;
A[3]= X[1] + X[3] + X[5] + X[7] + X[9]-15.0;
f &= X[0].pow(2)+2*X[1].pow(2)+3*X[2].pow(2)+4*X[3].pow(2)+5*X[4].pow(2)+
6*X[5].pow(2)+7*X[6].pow(2)+8*X[7].pow(2)+9*X[8].pow(2)+10*X[9].pow(2);
C0 H_inv = dd(f).inverse(); modified Cholesky on (AH-1AT)-1
λ = (AH-1AT)-1 [A(xi) - AH-1∇f(xi )]
Cholesky AHAt_inv( (d(A)*H_inv*(~d(A)) ), EPSILON);
C0 lambda = AHAt _inv * ( ((C0)A) - d(A)*(H_inv*d(f)) );
d_x &= H_inv*-( (~d(A))*lambda + d(f) ); ∆x = H-1 (−∇f(xi ) - ATλ); i.e., Eq. 2•47
((C0)X) += d_x;
cout << "solution(" << (++k) << "): "<<((C0)X) << ", objective functional: " << ((C0)f)
<< endl;
x = {-2.00, 2.66, 2.39, 3.62, 3.27, 2.87,
} while( k < MAX_NO_OF_ITERATION && (double)norm(d_x) > RELAXED*EPSILON ); 3.87, 3.16, 2.47, 2.69}T
cout << "The final solution: " << ((C0)X) << endl; f = 502.4
}
Listing 2•26 Perturbed Lagrangian method (project: “perturbed_lagrangian”).

164 Workbook of Applications in VectorSpace C++ Library


CHAPTER

Variational Methods
Three Using H0, H1, and H2 Type
Objects

In functional analysis, H0 (= L2 = W0,2), H1 (=W1,2), and H2 (= W2,2) are Sobolev spaces. They are also Hil-
bert spaces that has the inner product defined. For the users of VectorSpace C++ Library, it is sufficient to know
H0, H1, and H2 type objects are integrable objects (among them H1 and H2 type objects are also differentiable
objects). The applications such as those in variational methods can be easily implemented with H0, H1, and H2
type objects. C++ programs using VectorSpace C++ Library in this chapter are projects contained in project
workspace file “Hn.dsw” under directory “vs\ex\Hn”.

3.1 H0 Type Objects


We first look at various integration methods and then inferred from these integration methods the data
abstraction of an integrable object.

3.1.1 Quadrature
Numerical integration is also known as quadrature, which means the process of finding a square equal in area
to a given area. The trapezoidal rule and the Simpson’s rule are the most fundalmental ones that are introduced in
calculus1. For a one dimensional function f(x), the trapezoidal rule and Simpson’s rule evaluate the areas of the

1. For example, p. 602-609, in T.M. Apostol, 1969, “Calculus”, 2nd eds., vol. 2, Blaisdell Publishing Company, Waltham,
Mass.

Workbook of Applications in VectorSpace C++ Library 165


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

Trapezoidal rule Simpson’s rule


f(x)
quadratic interpolation
linear interpolation f(x)

x0 h = x1 - x0 x1 x0 x1 x2

Figure 3•1 Approximation by two linear interpolation functions (left-hand-side), and by one
quadratic interpolation function (right-hand-side), where h = xi+1 - xi is the size of one segment.

approximated linear and quadratic interpolation functions, respectively. Formula of the approximated linear and
quadratic interpolation functions are
x1 1 1
Trapezoidal rule: ∫x f ( x )dx
0
= h --- f ( x 0 ) + --- f ( x 1 ) + O ( h 3 f’’)
2 2
x2 1 4 1
Simpson’s rule: ∫x 0
f ( x )dx = h --- f ( x 0 ) + --- f ( x 1 ) + --- f ( x 2 ) + O ( h 5 f ( 4 ) )
3 3 3
Eq. 3•1

where O( ) indicates order of errors as a function of size “h” and derivatives of f(x). The two methods are illus-
trated in Figure 3•1 . Formula without the necessity of evaluating the function at the end-points (“open-type”
formula) exists; e.g.,

x5 55 5 5 55
∫x f ( x )dx
0
= h ------ f ( x 1 ) + ------ f ( x 2 ) + ------ f ( x 3 ) + ------ f ( x 4 ) + O ( h 5 f ( 4 ) )
24 24 24 24
Eq. 3•2

We notice that f(x0) and f(x5) are not in Eq. 3•2. So, if a singularity of f(x) presents at any such end-points it can
be avoided. Repeatedly using the trapezoidal rule or Simpson’s rule of Eq. 3•1 in many smaller segments yields
the “extended-type” formula

Extended trapezoidal rule:


( x N – x 0 ) 3 f’’
xN 1 1  -----------------------------
-
∫x 0 f ( x )dx = h --
-
2 0
f ( x ) + f ( x 1 ) + f ( x 2 ) + … + f ( x N–1 ) + --
-
2 N
f ( x ) + O
 (N + 1)2 

Extended Simpson’s rule:

f ( x )dx = h --- f ( x 0 ) + --- f ( x 1 ) + --- f ( x 2 ) + --- f ( x 3 ) + … + --- f ( x N – 2 ) + --- f ( x N – 1 ) + --- f ( x N ) + O  --------------------4-


xN 1 4 2 4 2 4 1 1
∫x 0 3 3 3 3 3 3 3 (N + 1) 
Eq. 3•3

166 Workbook of Applications in VectorSpace C++ Library


H0 Type Objects
The above formula are all expressible as

N
∑ hci f ( xi ) Eq. 3•4
i=0

where ci is an array of coefficients in Eq. 3•1, Eq. 3•2, and Eq. 3•3. They all use equally spaced intervals (h = x i+1
- xi), and f(xi) are evaluated at positions xi = x0 + i × h. For example, an integration problem

2
∫ ( x 2 – 2x + 1 ) dx Eq. 3•5
1

using extended Simpson’s rule can be written in C++ with VectorSpace C++ Library as (see project:
“extended_simpson”)

// QUADRATURE: EXTENDED SIMPSON’S RULE


const int N = 11; // number of integration segments
double c[N] = {1.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, // ci
2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0,
2.0/3.0, 4.0/3.0, 1.0/3.0},
X[2] = {1.0, 2.0}, // starting and end points
h = (X[1]-X[0])/N; // size of one segment
Quadrature qp(c, X[0], X[1], N); // domain [a, b], 11 integration points
// THE INTEGRATION
2
H0 x(qp); ∫ ( x 2 – 2x + 1 ) dx = 1/3
C0 area = (x.pow(2) - 2 * x + 1) | J(h); // 1
cout << area << endl; // 0.333334

The class “Quadrature” takes arguments of (1) array of coefficients, (2) starting and end points, and (3) number
of integration points. VectorSpace C++ Library uses integration operator “H0::operator | (const J&)”, where
another new class “J”, the Jacobian, may take the size of an integration segment as its arguments. The reason of
using class “J” is to be compatible with more complicated integration domain and will become more evident
later. For now, in view of Eq. 3•4, we define referential coordinate ξ (with domain [0, 1]) for every integration
segment. Hence, the length of each referential integration segment is |ξ| = 1. The actual integration segement
domain [xi, xi+1] is mapped with a linear coordinate transformation rule x = x(ξ) = (1-ξ) xi + ξ xi+1. The Jacobain
for the segement is J = dx/dξ = (xi+1-xi) / |ξ| = h, the length of the segment per se. For various quadrature rules
discussed in the above only the values of the coefficient array need to be defined differently.
Higher order approximation can be obtained, in general, with greater number of function evaluations, which,
unfortunately, increases the computational cost. The significant computational improvement can be achieved if
we can reduce the number of function evaluations while maintain higher order approximation. The idea of Gaus-
sian quadrature is to choose not only the values of ci but also the locations for the function evaluation (i.e., the
integration point coordinates). Therefore, we have two times the degree of freedom to improve the order of
approximation. For example, we seek a two-point integration rule to exactly integrate a general cubic polynomi-
nal. Let

Workbook of Applications in VectorSpace C++ Library 167


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
f(ξ) = α0 + α1ξ + α2ξ2 + α3ξ3 Eq. 3•6

where αi are the coefficients for the cubic function f(ξ). First we normalize the integration domain to [-1, 1], and
assume that the integration points are spaced symmetrically and weighted equally. Therefore, W 0 = W1 and ξ0 =
-ξ1. Integration of Eq. 3•6 at the interval of [-1, 1] gives

1
∫ f ( ξ ) dξ = 2 α0 + 2--- α2 Eq. 3•7
–1 3

The two-point Gaussian quadrature wieghting coefficients Wi at integration points ξi gives (with i = 0, 1)

1
∫ f ( ξ ) dξ ≅ W0f(ξ0) + W1f(ξ1) =W0(f(−ξ1) + f(ξ1)) = 2W0 ( α0 + α2 ξ12) Eq. 3•8
–1

From the right-hand-sides of both Eq. 3•7 and Eq. 3•6, we have

2
2 α0 + --- α2 = 2W0 ( α0 + α2 ξ12) Eq. 3•9
3

For arbitary values of α0 and α2 Eq. 3•9 must always hold. Therefore, we obtain weightings W0 = W1 = 1, and
integration point coordinates ξ0 = - ξ1 = -1 / 3 . Tabulated values of Wi and ξi with more integration points can
be found in Stroud and Secrest1. Gaussian integration can be applied to integral domains other than the normal-
ized interval [-1, 1]. For example, for an actual integration domain of [1, 2], we can define a linear interpolation
function f(x) = f(ξ), with the natural coordinate ξ (Gaussian integration domain in [-1, 1]), as

1 1
f(ξ) = --- (1-ξ) f(x0) + --- (1+ξ) f(x1) Eq. 3•10
2 2

where we can check f(−1) = f(x0) and f(1) = f(x1). We can also define a similar linear coordinate transformation

1 1
x(ξ) = --- (1-ξ) x0 + --- (1+ξ) x1 Eq. 3•11
2 2

We note that the forms of the interpolation function and the coordinate transformation do not have to be the same
as they are in this example. Again, we can check at the starting point x(−1) = x0, and at the end point x(1) = x1.
Then, without loss of generality for a multi-dimensional case, we write

∫ ∫ f ( x )det  ∂x
------ dξ = ∫ f ( x )J dξ ≅
N


f ( x ) dx ≡

∂ξ ∑ Wi f ( xi )Ji Eq. 3•12
Ω i=0

1. A.H. Stroud and D. Secrest, 1966, “Gaussian quadrature formulas”, Prentice-Hall, Englewood Cliffs, N.J.

168 Workbook of Applications in VectorSpace C++ Library


H0 Type Objects
where Ω is the integration domain, and Ω is the normalized integration domain. Define J = ∂ x/ ∂ ξ as the Jaco-
bian matrix, and J = det J =det ( ∂ x/ ∂ ξ) is the Jacobian. In an one-dimensional case, if the coordinate transforma-
tion rule x(ξ) is chosen to be linear such as Eq. 3•10, ∀ Ji = dx/dξ = (x1-x0)/2 equal the same constant.
Comparing Eq. 3•12 with Eq. 3•4, we find that (1) the constant Ji is in place of segment size “h”, (2) the
wieghting coefficients Wi is in place of the coefficients ci and (3) the integration point coordinates in Eq. 3•12 is
now taking from tabulated numbers instead of simply computed from a equally spaced formula of xi = x0 + i × h.
The integration problem in Eq. 3•5,

∫1( x – 1 ) 2 dx
2

using Gauss quadrature, can be re-written in C++ with VectorSpace C++ Library as (project:
“linear_coordinate_transformation”)

double X[2] = {1.0, 2.0}; // integration domain [1, 2]


Quadrature qp(1, 2); // one dimension, 2 integration points
H0 z(qp), // natural coordinate ξ
x = (1-z) /2*X[0] + (1+z)/2 * X[1]; // x(ξ) = (1/2)(1-ξ) x0 + (1/2)(1+ξ) x1
J omega((X[1]-X[0])/2); // J = dx/dξ = (x1-x0)/2
C0 area = (x-1).pow(2) | omega; // Eq. 3•5; analytical value = 1/3
cout << area << endl; // 0.333334

The Gaussian quadrature is the default integration method in VectorSpace C++ Library, because of its higher
order approximation. Database for both the weighting coefficients and integration points of the Gaussian quadra-
ture are hidden from end-users. The users, however, need to input number of dimensions and number of integra-
tion points in the constructor of class “Quadrature”. In the present example, since the coordinate transformation is
linear the Jacobian is constant everywhere along the integral line; i.e., J = (x1-x0)/2, where “2” is the length of the
integration domain, since the natural coordinate “ξ” is defined in the interval of [-1, 1]. The result of the integra-
tion is 0.333334. A quadratic coordinate tranformation case is presented later. We note that the restriction of con-
stant values for the Jacobian can be relaxed when we introduce H1 type later.
We have seen various integration methods, and we have inferred the basic elements in object-oriented model-
ing of an integrable class. Now we are ready to deduce the data abstraction for constructing classes in H0 type.
H0 type objects are generalization of C0 type objects with integration capability. As in C0 type objects, H0 type
objects are subdivided into primary objects and utility objects. The primary objects include Integrable_Scalar,
Integrable_Vector, and Integrable_Matrix. These objects deserve detailed description. For the utility objects,
Integrable_Subvector and Integrable_Submatrix, it is suffice to say that they are mostly comformable to their C0
counterparts.

3.1.2 Integrable_Scalar
An Integrable_Scalar, “H0::H0(const Quadrature&)”, contains a reference to a Quadrature class instance,
and a pointer array of “C0 *u”. The dual abstraction, discussed on page 97, is also used to model the high-level

Workbook of Applications in VectorSpace C++ Library 169


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
mathematics and to encapsulate the details of low-level memory management. The pointer array of C0, “u”, is
referring to “double *v”.

Integrable_Scalar Object Quadrature Point Values Physical Memory


x C0* of Scalar—u double*—v
TABLE 3.1 Dual abstraction of an Integrable_Scalar.

Constructors
Two examples of using variable dedicated constructor for H0 type Integrable_Scalar have been shown on
page 167 and page 169 for Simpson’s rule and Gaussian quadrature, respectively. We show a few more non-triv-
ial examples, in the followings. Consider a diffusion problem, e.g., heat conduction or chemical diffusion, in the
form of a differential equation1

2
d u
– --------2 = f ( x ) , with 0 < x < 1, u ( 0 ) = α, u ( 1 ) = β Eq. 3•13
dx

where f(x) is the source term. The solution to Eq. 3•13 can be expressed in integral form as

1
u( x) = ∫ g ( x, ξ )f ( ξ )dξ + ( 1 – x )α + xβ Eq. 3•14
0

where g(x, ξ) is Green’s function. The physical interpretation of the Green’s function is that g(x, ξ) is the temper-
ature (or concentration) sampling at x when a unit concentrated point source is located at ξ. Therefore, g(x, ξ)
satisfies Eq. 3•13; i.e., with g(x, ξ) in place of u(x) in the differential equation. We also require, at the location x
= ξ, g(x, ξ) to be continuous. And the net flux of the infinitesimal control line segment at x = ξ equals to the
source intensity; i.e., g’(ξ+, ξ) - g’(ξ-, ξ) = -1, which is also known as the jump condition. From the above con-
ditions, the Green’s function can be solved as

 x ( 1 – ξ ), 0 < x < ξ
g ( x, ξ ) =  Eq. 3•15
 ξ ( 1 – x ), ξ < x < 1

Therefore, Eq. 3•14 can be re-written as

x 1
u ( x ) = ( 1 – x ) ∫ ξf ( ξ )dξ+ x ∫ ( 1 – ξ )f ( ξ )dξ + ( 1 – x )α + xβ Eq. 3•16
0 x

1. p. 42 in I. Stakgold, 1979, “Green’s function and boundary value problems”, John Wiley & Sons, New York.

170 Workbook of Applications in VectorSpace C++ Library


H0 Type Objects
where at interval [0, x] of the integration domain ξ < x, so the second line of Eq. 3•15 is applicable, and at the
interval [x, 1] of the integration domain, x < ξ, the first line of Eq. 3•15 is applicable .

For a specific case with source distribution as f(x) = sin (πx), and homogeneous boundary conditions α = β =
0, we can compute ten point values of the solution u(x) at interval of “h = 0.1”. Program Listing 3•1 implemented
the solution using Eq. 3•16. The analytical solution corresponding to this source distribution is u = - (1/ π2)
sin(πx), which is used to compare with the computed result of the integral equation. They only differs after the
sixth digit after the decimal point ( see TABLE 3.2.)

#include “include/vs.h”
int main() {
double const PI = 3.141592654;
double const alpha = 0.0; double const beta = 0.0; α=β=0
double x = 0.0, extended Simpson’s rule
w[11] = {1.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0,
4.0/3.0, 1.0/3.0};
for(int i = 0; i < 11; i++) {
Quadrature q1(w, 0.0, x, 11), q2(w, x, 1.0, 11);
H0 z1(q1), z2(q2),
f_1 = sin(PI*z1), f_2 = sin(PI*z2); f(x) = sin πx
C0 integ_1, integ_2; x 1
if(i !=0) integ_1 &= (1-x)*( (z1*f_1) | J(x/10.0); else integ_1 &= C0(0.0); ( 1 – x ) ∫ ξf ( ξ )dξ + x ∫ ( 1 – ξ )f ( ξ )dξ
if(i !=10) integ_2 &= x*( ((1-z2)*f_2) | J((1-x)/10.0); else integ_2 &= C0(0.0); 0 x
double u = (double)(integ_1+integ_2) + (1-x)*alpha+x*beta;
cout << “u(“ << x << “): “ << u << endl; with boundary terms (1-x) α + x β
if(i != 10) x += 0.1;
}
return 0;
}

Listing 3•1 Solving diffusion problem using integral expression (project: “green_diffusion_equation”).

x - (1/ π2) sin(πx) Program Listing 3•1


0.0 0.000000 0.000000
0.1 0.031310 0.031311
0.2 0.059555 0.059555
0.3 0.081971 0.081970
0.4 0.096362 0.096361
0.5 0.101321 0.101320
0.6 0.096362 0.096361
0.7 0.081971 0.081970
0.8 0.059555 0.059555
TABLE 3.2. Comparison of analytical solution and the
result computed from integral expression Eq. 3•16.

Workbook of Applications in VectorSpace C++ Library 171


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

x - (1/ π2) sin(πx) Program Listing 3•1


0.9 0.031310 0.031311
1.0 0.000000 0.000000
TABLE 3.2. Comparison of analytical solution and the
result computed from integral expression Eq. 3•16.
For heat conduction or chemical diffusion problem as shown in Eq. 3•13, the thermal diffusivity (or the dif-
fusion coefficient) can be a function of positions (x). We may have a slightly different problem as

– ------  k ( x ) ------ = f ( x ), 0 < x < 1 ;u ( 0 ) = 0, u’( 1 ) = 0


d du
Eq. 3•17
dx  dx

The solution can be expressed in integral form as

1
u( x ) = ∫ g ( x, ξ )f ( ξ )dξ Eq. 3•18
0

The Green’s function should satisfy Eq. 3•17 and should be continuous at x = ξ. The jump condition is
k(ξ)[g’(ξ+,ξ)-g’(ξ-, ξ)] = -1. These conditions lead to the Green’s function for this problem as

 x

∫ ---------
1
 - dy, 0<x<ξ
k(y)
 0

g ( x, ξ ) =  Eq. 3•19

 ξ

∫ ---------
 1
- dy, ξ<x<1
 k(y)
 0

Substituting Eq. 3•19 into Eq. 3•18, with the case of k(x) = (1+x), and f(x) = x, gives

x 1
u(x) = ∫ ln ( 1 + ξ )f ( ξ )dξ + ln ( 1 + x ) ∫ f ( ξ )dξ Eq. 3•20
0 x

The exact solution for this case is

uexact = -x2/4 +x/2 Eq. 3•21

Program Listing 3•2 implements Eq. 3•20, which are codes from the Program Listing 3•1 with only very slight
modifications. The results of Program Listing 3•2 are listed in TABLE 3.2. for comparison with the analytical
solution. Only last three points in the interval of [0.8, 1.0] has 1.e-6 in error. We emphasize that Eq. 3•16 and Eq.
3•20 are much more complicated than that of the corresponding analytical solutions. However, analytical solu-
tions are only possible when the given f(x) and k(x) happen to give an analytical solvable differential equations,
while the Green’s function method is quite general for less restricted forms of f(x) and k(x). This concludes the
example for the Integrable_Scalar object of H0 type.

172 Workbook of Applications in VectorSpace C++ Library


H0 Type Objects

#include “include/vs.h”
int main() {
double x = 0.0,
w[11] = {1.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, extended Simpson’s rule
4.0/3.0, 1.0/3.0};
for(int i = 0; i < 11; i++) {
Quadrature q1(w, 0.0, x, 11), q2(w, x, 1.0, 11);
H0 z1(q1), z2(q2);
C0 integ_1, integ_2; f(x) = x, k(x) = 1+x
if(i !=0) integ_1 &= ( (z1*log(1+z1)) | J(x/10.0); else integ_1 &= C0(0.0); x 1
if(i !=10) integ_2 &= log(1+x)*( z2 | J((1-x)/10.0); else integ_2 &= C0(0.0);
double u = (double)(integ_1+integ_2) ;
cout << “u(“ << x << “): “ << u << endl;
∫ ln ( 1 + ξ )f ( ξ )dξ + ln ( 1 + x ) ∫ f ( ξ )dξ
0 x
if(i != 10) x += 0.1;
}
return 0;
}

Listing 3•2 Solving diffusion problem with k(x) = 1+x and f(x) = x using integral expression (traget:
“green_diffusion_variable_conductivity”).

x -x2/4 +x/2 Program Listing 3•2


0.0 0.000000 0.000000
0.1 0.047500 0.047500
0.2 0.090000 0.090000
0.3 0.127500 0.127500
0.4 0.160000 0.160000
0.5 0.187500 0.187500
0.6 0.210000 0.210000
0.7 0.227500 0.227500
0.8 0.240000 0.240001
0.9 0.247500 0.247501
1.0 0.250000 0.250001
TABLE 3.3. Comparison of analytical solution and the
result computed from integral expression Eq. 3•20.

Workbook of Applications in VectorSpace C++ Library 173


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
The constant strings used for the virtual constructors (for example, using macro definition “H0 x =
INTEGRABLE_SCALAR(const char*, ...)”) and the autonomous virtual constructor (for example, using “H0 x
= H0(const char*, ...)”) are shown in the following box.

virtual constructor string VectorSpace C++ library definition priority

by reference
“H0&” H0 type Integrable_Scalar object 1
“H0*” a pointer to H0 type Integrable_Scalar object 2
“double*, const Quadrature&, int, int” 3
double pointer, Quadrature, m_row_size, m_col_size

by value
“const Quadrature&” Quadrature 4
“const H0&” H0 type Integrable_Scalar object 5
“const H0*” pointer to H0 type Integrable_Scalar object 6
Strings in H0 virtual constructor for Integrable_Scalar object.

Operators and Functions


The most important operator for the Integrable_Scalar object is the integration operator. The integration
operator is “H0::operator | (const J&)” that takes a Jacobian class “J(const H0&) or J(const double&)” as its
argument. We notice that unless we have H1 type object capability we can not do the differentiation of the coor-
dinate transformation x(ξ) to construct the Jacobian class, such as “J(d(x))”, where x is an H1 object, and d(x)
returns an H0 object that contains the derivative information of the x. For the time being, as long as the coordi-
nate transformation x(ξ) is chosen to be linear we can use “J(const double& J)” to define the corresponding con-
stant Jacobian easily, where the single argument is a reference to a const double, and its value is J = (xend point -
xstarting point)/2, as shown in the above example.

The rest of the operators and functions are listed in the following box. They are mostly comformable to the
operators and functions of the Scalar object. Promotion of C0 type to H0 type object by binary operators is a
common practice just as in standard C++ language. For example,

H0 x;
C0 y;
H0 z1 = x+y; // invoke H0::operator +(const C0&);
H0 z2 = y+z; // invoke H0’s friend operator +(const C0&, const H0&)

The second operator invoked, “operator +(const C0&, const H0&)” is a binary operator which is declared a
friend function (operator) to H0 class. These operators, needed for the promotion operation, are not listed in the
box for simplicity.

174 Workbook of Applications in VectorSpace C++ Library


H0 Type Objects

operator or function VectorSpace C++ library definition remark

symbolic operators
H0& operator &= ( ) assignment by reference
H0& operator = ( ) assignment by value
H0 operator & ( ) const column concatenation
H0 operator && () const one-by-one column concatenation
H0 operator | ( ) const row concatenation
H0 operator || () const one-by-one row concatenation

arithmatic operators
H0 operator + ( ) const positive unary
H0 operator - ( ) const negative unary
H0 operator + (const H0&) const addition
H0 operator - (const H0&) const subtraction
H0 operator * (const H0&) const multiplication
H0 operator / (const H0&) const multiplication
H0& operator += (const H0&) replacement addition
H0& operator -= (const H0&) replacement subtraction
H0& operator *= (const H0&) replacement multiplication
H0& operator /= (const H0&) replacement division

logic operators
int operator == (const H0&) const equal TRUE == 1
int operator != (const H0&) const not equal FALSE == 0
int operator >= (const H0&) const greater or equal
int operator <= (const H0&) const less or equal
int operator > (const H0&) const greater
int operator < (const H0&) const less

functions
H0 pow(int) const power
H0 sqrt(const C0&) const square root
H0 exp(const C0&) const exponent
H0 log(const C0&) const log
H0 sin(const C0&) const sin
H0 cos(const C0&) const cos
Partial listing of H0 type Integrable_Scalar class arithmetic operators, logic operators and functions.

Workbook of Applications in VectorSpace C++ Library 175


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
3.1.3 Integrable_Vector

Constructors
A dedicated constructor of an Integrable_Vector, “H0::H0(int, double*, const Quadrature&)”, contains a ref-
erence to a Quadrature instance, and a pointer array of “C0 *u”. The dual abstraction is used. The pointer array
of C0, “u”, is referring to a “double *v”.

Integrable_Vector Object Quadrature Point Values Physical Memory


x C0* of Vector—u double*—v
TABLE 3.4. Dual abstraction of an Integrable_Vector.

Following is an example of an array of quadratic interpolation functions “N” constructed as an H0 type


Integrable_Vector object. The access of each element of the Integrable_Vector is done by the selector,
“H0::operator [](int)” (see project: “quadratic_coordinate_transformation”).

double f(double x) { return (x-1)*(x-1); } //


Quadrature qp(1, 2); // 1-dimension, 2-points Gaussian
H0 z(qp), // natural coordinates ξ
⇒ N(3, (double*)0, qp); // Integrable_Vector object of H0 type
N[0] = (z-1)*z/2.0; // N0 = (ξ-1)ξ/2
N[1] = (1-z.pow(2)); // N1= 1-ξ2
N[2] = (z+1)*z/2.0; // N2 = (ξ+1)ξ/2
H0 y = N[0]*f(1.0)+N[1]*f(1.5)+N[2]*f(2.0); // y = Σ Nifi
C0 area = y | J(0.5); // “H0::operator | (const J&)”
cout << area << endl; // 1/3

In this case the quadratic coordinate transformation rule is implicitly chosen as

1 1
x(ξ) = --- (ξ-1)ξ x0 + (1-ξ2) x1 + --- (ξ+1)ξ x2 Eq. 3•22
2 2

We mentioned in previous section on the subject of Integrable_Scalar that since we have not yet introduced H1
type objects, we need a linear coordinate transformation to have constant values of the Jacobian throughout the
integration domain. We can make Eq. 3•22 consistent with a linear coordinate transformation rule by enforcing

x1 = (x0+x2)/2

i.e., x1 has to be the middle point of the segment. Substituting this relation into Eq. 3•22 to eliminate x1 yields
1 1
x(ξ) = --- (1-ξ) x0 + --- (1+ξ) x2
2 2

176 Workbook of Applications in VectorSpace C++ Library


H0 Type Objects

f(x2) = 3

f(x3) = 2

f(x1) = 2

f(x0) = 1 x2 = (1,1)
x3 = (0,1)

Ω x1 = (1,0)
x0 = (0,0)
Figure 3•2 Integration of a plane surface with a square integration domain.

which is exactly the linear coordinate transformation rule of Eq. 3•11. Therefore, we have J = (2-1)/2 = 0.5 as
used in the above example.
We show an example of a 2-D problem using Gaussian quadrature. Again, without H1 type objects for differ-
entiation operation, we restrict the integration domain to be a square or a rhombic region in order to have con-
stant Jacobian everywhere. In this example, a set of bilinear interpolation functions are used

1 1 1 1
f(ξ, η) = --- (1−ξ)(1−η) f(x0) + --- (1+ξ)(1−η) f(x1) + --- (1+ξ)(1+η) f(x2) + --- (1−ξ)(1+η) f(x3) Eq. 3•23
4 4 4 4

Assuming a plane as shown in , f(x0) = 1, f(x1) = 2, f(x2) = 2, and f(x3) = 3, where x0 = (0, 0), x1 = (1, 0),x2 = (1,
1), and x3 = (0, 1). The constant Jacobian of the problem is J = 1/4, where the area of the referential domain
– 1 ≤ ξ ≤ 1 and – 1 ≤ η ≤ 1 , is “4”. (in project: “integration_2d”).

double f[4] = {1.0, 2.0, 2.0, 3.0}; // f(xi)


Quadrature qp(2, 4); // 2-D, and 2x2 (= “4”) integration points
H0 z(2, (double*)0, qp), zai, eta; // natural coordinates
zai &= z[0], eta &= z[1]; // alias of elements in the Integrable_Vector
C0 volume = ((1-zai)*(1-eta)/4 *f[0] // ∫ f ( ξ ,η ) dΩ
+ (1+zai)*(1-eta)/4*f[1] Ω
+(1+zai)*(1+eta)/4*f[2]
+(1-zai)*(1+eta)/4*f[3]) | J(0.25);
cout << volume << endl; // 2.0

Workbook of Applications in VectorSpace C++ Library 177


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
The constant strings used for the virtual constructors (for example, using macro definition in “H0 x =
INTEGRABLE_VECTOR(const char*, ...)”) and the autonomous virtual constructor (for example, using “H0 x
= H0(const char*, ...)”) are shown in the following box.

virtual constructor string VectorSpace C++ library definition priority

by reference
“H0&” H0 type Integrable_Vector —
“H0*” a pointer to H0 type Integrable_Vector —
“int, double*, const Quadrature&, length, double* != 0, Quadrature 7
int, int” m_row_size, m_col_size

by value
“int, const Quadrature&” length, Quadrature 8
“const H0*” H0* —
“int, H0&, int, const Quadrature” length, H0, starting index, Quadrature 9
(the only one for reference Integrable_Vector)
Strings in H0 virtual constructor for H0 type Integrable_Vector class.

Operators and Functions


In the above example the integration operator is applied on Integrable_Scalar objects of H0 type, which are
components of an Integrable_Vector object of H0 type. The integration operator can be applied to an H0 type
Integrable_Vector object directly. However, the return value of the integration operator in this case is a Vector of
C0 type instead of a Scalar of C0 type. The rest of the operators and functions are listed in the following box.
They are mostly conformable to the operators and functions of the Vector objects of C0 type.

178 Workbook of Applications in VectorSpace C++ Library


H0 Type Objects

operator or function VectorSpace C++ remark

symbolic operators
H0& operator &= ( ) assignment by reference
H0& operator = ( ) assignment by value
H0& operator [] (int) selector return scalar
H0 operator & ( ) const column concatenation
H0 operator && () const one-by-one column concatenation
H0 operator | ( ) const row concatenation return matrix
H0 operator || ( ) const one-by-one row concatenation return matrix

arithmetic operators
H0 operator ~ ( ) const transposed (into a “row vector”) return matrix
H0 operator + ( ) const positive (primary casting) unary
H0 operator - ( ) const negative unary
H0 operator + (const H0&) const addition
H0 operator - (const H0&) const subtraction
H0 operator * (const H0&) const multiplication by a scalar; scalar productof two vectors
H0 operator %(const H0&) const tensor product of two vectors
H0 operator / (const H0&) const division (by a scalar or a matrix only)
H0& operator += (const H0&) replacement addition
H0& operator -= (const H0&) replacement subtraction
H0& operator *= (const H0&) replacement multiplication (by a scalar only)
H0& operator /= (const H0&) replacement division (by a scalar only)

logic operators
int operator == (const H0&) const equal TRUE == 1
int operator != (const H0&) const not equal FALSE == 0
int operator >= (const H0&) const greater or equal
int operator <= (const H0&) const less or equal
int operator > (const H0&) const greater
int operator < (const H0&) const less

functions
int length() const length of the Integrable_Vector
double norm(int = 2) const 1-norm or 2-norm
double norm(const char*) const infinite-norm takes strings “infinity”, or “maximum”
H0 pow(int) const power (applied to each element of the Integrable_Vector)
H0 sqrt(const H0&) const square root(applied to each element of the Integrable_Vector)
H0 exp(const H0&) const exponent (applied to each element of the Integrable_Vector)
H0 log(const H0&) const log (applied to each element of the Integrable_Vector)
H0 sin(const H0&) const sin (applied to each element of the Integrable_Vector)
H0 cos(const H0&) const cos (applied to each element of the Integrable_Vector)
Partial listing of Integrable_Vector object arithmatic operators, logic operators and functions.

Workbook of Applications in VectorSpace C++ Library 179


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
3.1.4 Integrable_Matrix

Constructors
A dedicated constructor of an Integrable_Matrix, “H0::H0(int, int, double*, const Quadrature&)”, contains a
reference to a Quadrature instance, and a pointer array of “C0 *u” (pointer to C0 type Matrix object). The dual
abstraction is also used. The pointer array of C0, “u”, is referring to a “double *v”.

Integrable_Matrix Object Quadrature Point Values Physical Memory


x C0* of Matrix—u double*—v
TABLE 3.5. Dual abstraction of an Integrable_Matrix.
A simple example of an Integrable_Matrix is the “consistent mass matrix”; e.g., “M” in Eq. 3•217 on
page 258, generated from the tensor product operation ⊗ (using “%” in VectorSpace C++ Library ) of two
Integrable_Vector objects of H0 type. Integrable_Matrix is often encountered in variational calculus to be intro-
duced later in this Chapter.
The constant strings used for the virtual constructors (for example, using macro definition in “H0 x =
INTEGRABLE_MATRIX(const char*, ...)”) and the autonomous virtual constructor (for example, using “H0 x
= H0(const char*, ...)”) are shown in the following box.

virtual constructor string VectorSpace C++ library definition priority

by reference
“H0&” H0 type Matrix —
“H0*” a pointer to H0 type Matrix —
“int, int, double*, row-length, column-length, double* != 0,
const Quadrature&, int, int” memory-row-length, memory-column-length 10

by value
“int, int, double*, row-length, column-length, double* = 0,
const Quadrature&, int, int” memory-row-length, memory-column-length 11
“int, int, const Quadrature&” row-length, column-length 12
“int, int, const double*, row-length, column-length, double*, 13
const Quadrature&, int, int” memory-row-length, memory-column-length
“int, int, H0&, int, int, row-length, column-length, H0&, 14
const Quadrature&” starting row-index, starting column-index
(the only one for reference Integrable_Matrix)
Strings in H0 virtual constructor for H0 type Integrable_Matrix class.

180 Workbook of Applications in VectorSpace C++ Library


H0 Type Objects
operator or function VectorSpace C++ library definition remark

functions
H0 pow(int) const power (applied to each element of the Matrix)
H0 sqrt(const H0&) const square root (applied to each element of the Matrix)
H0 exp(const H0&) const exponent (applied to each element of the Matrix)
H0 log(const H0&) const log (applied to each element of the Matrix))
H0 sin(const H0&) const sin (applied to each element of the Matrix)
H0 cos(const H0&) const cos (applied to each element of the Matrix

matrix algebra fucntions


int rank() const rank of a Matrix
H0 identity() const identity Matrix
H0 cond() const condition number of a Matrix
H0 inverse() const inverse of a Matrix
H0 det() const determinant of a Matrix
Partial listing of H0 type Integrable_Matrix class arithmetic operators, logic operators and functions.

Operators and Functions


The operators and functions are listed in the above box. They are mostly conformable to the operators and
functions of the Matrix object of C0 type.

3.1.5 Utility Integrable Objects—Integrable_Subvector and Integrable_Submatrix


The Integrable_Subvector and the Integrable_Submatrix objects of H0 type are straight-forward extensions
from the Subvector and Submatrix objects of C0 type. However, it is not necessary to have Integrable_Basis
object of H0 type. We can simply use primary integrable objects in Sections 3.1.2 to 3.1.4 with the Basis object
of C0 type to make Integrable_Subvector or Integrable_Submatrix objects. In “Fourth-order Differential Equa-
tion” on page 205, many examples of alternative but equivalent approaches of using different utility integrable
objects are shown. With these flexible utility integrable objects, the C++ code of the mathematical implementa-
tions can be made to look much closer to the mathematical expressions we find in the mathematics literature.

Workbook of Applications in VectorSpace C++ Library 181


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
operator or function VectorSpace C++ library definition remark

symbolic operators
H0& operator &= ( ) assignment by reference
H0& operator = ( ) assignment by value
H0& operator [ ] (int) row selector
H0& operator( )(int) column selector
H0& operator ( )(int, int) element selector
H0 operator & ( ) const column concatenation
H0 operator && () const one-by-one column concatenation
H0 operator | ( ) const row concatenation
H0 operator || ( ) const one-by-one row concatenation

arithmetic operators
H0 operator + ( ) const positive (primary casting) unary
H0 operator - ( ) const negative unary
H0 operator + (const C0&) const addition
H0 operator - (const C0&) const subtraction
H0 operator * (const C0&) const multiplication
H0 operator / (const C0&) const division (by Integrable_Scalar or Integrable_Matrix only)
H0& operator += (const C0&) replacement addition
H0& operator -= (const C0&) replacement subtraction
H0& operator *= (const C0&) replacement multiplication (by an Integrable_Scalar only)
H0& operator /= (const C0&) replacement division (by an Integrable_Scalar only)

matrix algebra operators


H0 operator ~ ( ) const transpose
H0 operator !( ) const matrix decomposition

logic operators
int operator == (const H0&) const equal TRUE == 1
int operator != (const H0&) const not equal FALSE == 0
int operator >= (const H0&) const greater or equal
int operator <= (const H0&) const less or equal
int operator > (const H0&) const greater
int operator < (const H0&) const less

functions
int row_length() const row-length of the Integrable_Matrix
int col_length() const column-length of the Integrable_Matrix
H0 norm(in) const 1 (maximum column-sum)-norm or 2 (spectral)- norm
H0 norm(const char*) const “infinity”(max row-sum),“Forbenisu”(Forbenius-norm)

Partial listing of H0 type Integrable_Matrix class arithmetic operators, logic operators and functions.

182 Workbook of Applications in VectorSpace C++ Library


H1 Type and H2 Type Objects
3.2 H1 Type and H2 Type Objects
We can consider H1 and H2 types as natural extensions of C1 and C2 types. That is the high-level data
abstraction in the dual abstraction of H1 and H2 type object is a pointer array to C1 and C2 type objects (i.e.,
C1*, or C2*), and the length of the pointer array equals the number of quadrature points. The low-level data
abstraction is pointers to double (double*) to represent the memory space of the base point, tangent, and tangent
of tangent, in which the values of all quadrature points are connected together in a continuous memory space for
efficient memory management. A cross-relation of the dual abstraction is shown in Figure 3•3. The high-level
entities, in this case, three conceptually separate C1 type objects have their sub-entities (base points and tan-
gents), share the continuous memory spaces.
mathematical abstraction physical memory space
C1( tangent bundle)—*u double *v, *dv
quadrature
point # 1
quadrature
base point # 2 double* dv
point tangent quadrature
quadrature point # 1
point # 3
C1 *u;
double * v
quadrature
u[0] point # 1
quadrature
point # 2

quadrature
u[1] point # 2
quadrature
point # 3

u[2] quadrature (assume spatial dimension = 3)


point # 3
Figure 3•3 Cross relation by dual representation of an H1 type object.

3.2.1 Integrable_Tangent_Bundle / Integrable_Vector_of_Tangent_Bundle


Integrable_Tangent_Bundle and Integrable_Vector_of_Tangent_Bundle are two H1 type objects that are the
integrable extension of Tangent_Bundle and Vector_of_Tangent_Bundle objects of C1 type.

Constructors
The dedicated constructors for an Integrable_Tangent_Bundle are

Workbook of Applications in VectorSpace C++ Library 183


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
H1::H1(const Quadrature&)
H1::H1(double* v, const Quadrature&)
H1::H1(double* v, int spatial_dimension, const Quadrature&)

The dedicated constructor for an Integrable_Vector_of_Tangent_Bundle is

H1::H1(int vector_size, double* v, const Quadrature&)

For the dedicated constructor, the size of the vector equals the number of the spatial dimension.
The constant strings used for the virtual constructors and autonomous virtual constructor are listed in the
following two boxes. The macro defintions used for the virtual constructors of the Integrable_Tangent_Bundle
and the Integrable_Vector_of_Tangent_Bundle are

H1 x = INTEGRABLE_TANGENT_BUNDLE(const char*, ...);


H1 x = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(const char*, ...);

respectively. And, H1 x = H1(const char*, ...) is used for autonomous virtual constructors.

virtual constructor string VectorSpace C++ library definition priority

by reference
“H1&” H1 type Matrix 1
“H1*” a pointer to H1 type Matrix 2
“double*, double*, Quadrature” v, dv, 7
“double*, double*, int, Quadrature”v, dv, spatial dimension, 8
by value
“Quadrature”, 3
“int, Quadrature” memory-row-length, memory-column-length 4
“const double&, const double&, v, dv, (for spatial dimension = 1 only) 5
const Quadrature&”
“const double*, const double*, v, dv, 6
int, Quadrature&” spatial dimension
“const H0&, const H0&” base point, tangent 9
“const H0*, const H0*” base point, tangent 10
“const H1&” Integrable_Tangent_Bundle 11
“const H1*” Integrable_Tangent_Bundle* 12
Strings in H1 virtual constructor for H1 type Integrable_Tangent_Bundle class.

184 Workbook of Applications in VectorSpace C++ Library


H1 Type and H2 Type Objects

virtual constructor string VectorSpace C++ library definition priority

by reference
“H1&” H1 type Matrix —
“H1*” a pointer to H1 type Matrix —
“int, int, double*, double*, vector size, spatial dimension, v, dv, 14
Quadrature, int, int” quadrature, memory row size, and column size
by value
“int, int, Quadrature”, vector size, spatial dimension 13
“const H0&, const H0&” base point , tangent —
“const H0*, const H0*” base point, tangent —
“int, const H1*” vector size, Integrable_Tangent_Bundle* 15
“const H1&” Integrable_Vector_of_Tangent_Bundle —
“const H1*” Integrable_Vector_of_Tangent_Bundle* —
Strings in H1 virtual constructor for H1 type Integrable_Vector_of_Tangent_Bundle class.

1-D Integration: We seek the integration of the example shown on page 176. That is
x2 x2
x3
∫ ( x – 1 ) 2 dx =  x – x 2 + ----3- Eq. 3•24
x0
x0

The same quadratic coordinate tranformation rule


1 1
x(ξ) = --- (ξ-1)ξ x0 + (1-ξ2) x1 + --- (ξ+1)ξ x2
2 2

is used, and recall from Eq. 3•12 the 1-D version is


N
∫ f ( x ) dx ≡ ∫ f ( x ) ------ dξ = ∫ f ( x )J dξ ≅ ∑ W i f ( x i )J i
∂x
Ω ∂ξ
Ω Ω i=0
On page 176 we restrict ourselves to have equal-spaced points of x0, x1, and x2, in order to have a constant Jaco-
bian throughout the integration domain. Now Jacobian of the coordinate tranformation rule in this case is an
Integrable_Scalar of H0 type

dx ( ξ )
J = -------------- Eq. 3•25

This numerical integration problem can be coded with VectorSpace C++ Library as (project: “integration_1d”)

double x[3] = {1.0, 1.5, 3.0}; // x=[1, 3], analytical value form Eq. 3•24 is 8/3
Quadrature qp(1,3);

Workbook of Applications in VectorSpace C++ Library 185


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
H1 zai(qp), // Integrable_Tangent_Bundle
X = -(1-zai)*zai/2.0 * x[0]
+(1-zai)*(1+zai) * x[1]
+zai*(1+zai)/2.0 * x[2]; 3
2
C0 v = (((H0)X)-1).pow(2) | J(d(X)); // ∫ ( x – 1 ) 2 dx = 2 ---
3
1
cout << v << endl; // 2.66667,

Now the restriction of requring the three nodes to be equally spaced is lifted. The Jacobian is simply J(d(X)) as
in the above program.

Line Integration in 2-D: Consider an example of line integration on a two-dimensional space. We first look at
the arc length method in calculus. We seek the length of a circle in the first quadrant (see the right-hand-side of
Figure 3•4)

x2 + y 2 = 1

The value of the ordinate “y” can be considered as a function of “x” as

f( x) ≡ y = 1 – x2 Eq. 3•26

The arc length, “s”, of a function f(x) is (see the middle of Figure 3•4)

s ≅ ∑ ( ∆x i ) 2 + f ( x i + 1 ) – f ( x i ) 2 Eq. 3•27
i

by applying pythagorean law. For differentiable function f(x) at the limit of ∆x → 0 we have the arc-length
method formula

s = ∫ 1 + f ’( x )2 dx Eq. 3•28

y = f(x) y
arc length = s
r
x 2 + y 2 = r2

f(xi+1)-f(xi)

∆x
x r x
Figure 3•4 Use arcs to approximate the length of a curve.

186 Workbook of Applications in VectorSpace C++ Library


H1 Type and H2 Type Objects
An alternative approach is found in finite element method, which is also adopted by boundary element
method. A point in a curved element is represented by a position vector r ={x, y}T as shown in Figure 3•5. We
use quadratic interpolation functions for coordinate transformation of the position vector, r, with three nodal
coordinates (x0, y0), (x1, y1), and (x2, y2).

1 1
x(ξ) = --- (ξ-1)ξ x0 + (1-ξ2) x1 + --- (ξ+1)ξ x2
2 2
1 1
y(ξ) = --- (ξ-1)ξ y0 + (1-ξ2) y1 + --- (ξ+1)ξ y2 Eq. 3•29
2 2

where ξ is the parameter for the coordinate transformation of x and y coordinates. An infinitesimal length of the
curve dr can be obtained as (see Figure 3•5)

2 2
dr =  dx  +  dy dξ Eq. 3•30
 d ξ  d ξ

again, by applying pythagorean law. The integrand in Eq. 3•30 is

2 2
 dx +  dy  = dr ≡ J Eq. 3•31
 d ξ  d ξ dξ 2
2

That is the Euclidean norm of Jacobian of the coordinate transformation rule. Therefore, we have

dr = J 2 dξ Eq. 3•32

This integration formula is simply written consistent with the coordinate transformation method.
Program Listing 3•3 implements the arc length method with Eq. 3•26 and Eq. 3•28, and the coordinate trans-
formation method with Eq. 3•29 and Eq. 3•30. The program computes the line integration using arc length
method, if the macro definition “__ARC_LENGTH” is defined at the compile time. Otherwise, the default

dr ∂r
dy
∂y

∂r
dx
r ∂x

Figure 3•5 Derivatives of the position vector r = {x, y}T .

Workbook of Applications in VectorSpace C++ Library 187


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
method is the coordinate transformation method.
The function “sqrt()” is only defined for C0 and H0 types but can be extended easily for H1 type by an ad
hoc implementation of a free function “sqrt()”, which takes an Integrable_of_Tangent_Bundle of H1 type argu-
ment. The virtual constructor of the Integrable_Tangent_Bundle uses macro definition as in

H1 sqrt(const H1& a) {
return INTEGRABLE_TANGENT_BUNDLE(
“const H0&, const H0&”, sqrt((H0)a), d(a)/2.0/sqrt((H0)a) );
}

#include "include\vs.h"
#define NODE_NO 5
#if defined(__ARC_LENGTH)
H1 sqrt(const H1& a) {
return INTEGRABLE_TANGENT_BUNDLE("const H0&, const H0&",
sqrt((H0)a), d(a)/2.0/sqrt((H0)a));
}
#endif
int main() { nodal coordinates (xi, yi)
const double r = 1.0; const double PI = 3.141592654;
double X[NODE_NO][2];
for(int i = 0; i < NODE_NO; i++) {
X[i][0] = r*cos(((double)(i+1))*PI/(2.0*(NODE_NO-1)));
#ifndef __ARC_LENGTH
X[i][1] = r*sin(((double)(i+1))*PI/(2.0*(NODE_NO-1)));
#endif
}
Quadrature qp(1, 4);
H1 zai(qp), 1
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( N0(ξ) = --- (ξ-1)ξ, N1(ξ)= (1-ξ2)
2
"int, int, Quadrature", 3, 1, qp),
x, y; 1
N2(ξ)= --- (ξ+1)ξ
N[0] = -zai*(1-zai)/2; N[1] = (1+zai)*(1-zai); N[2] = zai*(1+zai)/2; 2
C0 length = 0.0;
for(int i = 0; i < (NODE_NO-1)/2; i++) { coordinate transformation rule
x = N[0]*X[i+2][0]+N[1]*X[i+1][0]+N[2]*X[i][0];
#if defined(__ARC_LENGTH)
x(ξ)=Ni(ξ)xi , y(ξ)=Ni(ξ)yi
y = sqrt(pow(r,2)-x.pow(2)); arc length method
#else
length += (sqrt(1.0+(d(y)/d(x)).pow(2)) | J(d(x)));
s = ∫ 1 +f ’( x )2 dx
y = N[0]*X[i+2][1]+N[1]*X[i+1][1]+N[2]*X[i][1];
length += (sqrt(d(x).pow(2)+d(y).pow(2))) | J(1.0); integration of Jacobian of transforma-
#endif tion rule
}
2 2
cout << length << endl;
dr =  dx +  dy dξ = J dξ
return 0;  d ξ  d ξ 2
}

Listing 3•3 Line integration using the arc length method or the coordinate transformation method (project:
“line_integration_2d”).

188 Workbook of Applications in VectorSpace C++ Library


H1 Type and H2 Type Objects
The results of the computation are listed in TABLE 3.6. The analytical result for the length of a quadrant of a
circle with unit radius is 2π(1)2/4 ≅ 1.570796327. The accuracy of the arc length method seems to be far supe-
rior than that of the integration of the coordinate transformation method. For both methods, the smaller the line
segments for integration the better the accuracy.

Number Coordinate
of Nodes Arc Length Transformation
3 1.57077 1.56245
5 1.57079 1.57020
7 1.57079 1.57068
9 1.57079 1.57076
TABLE 3.6. Line integration using 3 to 9 nodes.

Surface and Volume Integration in 3-D: Next we consider an example of integrating the volume and surface
area of a sphere with unit radius, numerically. The analytical value of the volume of a sphere is -34- πr3 ≅
4.188790203 (with r = 1), and surface area of a unit sphere is 4πr2 ≅ 12.56637061 (with r=1). The nodal coordi-
nates of any point on a unit sphere can be computed using spherical (polar) coordinates as

x0 = r sin φ cos θ, x1 = r sin φ sin θ, and z = r cos φ

where r is the radius of the sphere, φ (latitude) is the angle between the points to the Z-axis, and θ (colatitude) is
the angle obtained by first projecting the point on the x0-x1 plane, then, measuring the angle of the projected
point and the polar axis on the x0-x1 plane.
We use a 9-node Lagrangian element in Figure 3•7, which is common in finite element method. The case pre-
sented here, with the interpolation functions the same as the functions used for coordinate transformation, is
known as isoparametric element. The algorithm presented here, however, is applicable to 4 to 9 node element.
With this algorithm, any number of nodes from the fifth to the ninth can be added or omitted.

V = ∫ z ( ξ̂ )dx
x

φ
∂z 2 ∂z 2
r
A = ∫ 1 +  -------- +  -------- dx
 ∂x 0  ∂x 1
x
θ

x1
x0
Figure 3•6 Spherical (polar) coordinates and formula for computing the volume and
surface area of one-eighth of a sphere with unit radius.

Workbook of Applications in VectorSpace C++ Library 189


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

η
η 3 6
x(ξ)
3 6 2 7
8 2

5 5
7 0
8 ξ
4 ξ
0 4 1
1
Figure 3•7 Coordinate transformation rule x(ξ, η) of a 9-node Lagrangian element.

Step 1: Define interpolation functions Ni for four corner nodes (i = 0, 1, 2, 3) with

N0(ξ, η) = 1--4- (1-ξ)(1-η), N1(ξ, η) = 1--4- (1+ξ)(1-η)


N2(ξ, η) = 1--4- (1+ξ)(1+η), N3(ξ, η)= 1--4- (1-ξ)(1+η) Eq. 3•33

Step 2: If no center node, skip Step 2.

(1) add the center node by

N8(ξ, η) = (1-ξ2) (1-η2) Eq. 3•34

Then, (2) modify four corner nodes due to the presence of the center node

N0 -= N8/4, N1 -= N8/4, N2 -= N8/4, N3 -= N8/4, Eq. 3•35

Step 3: For each of the four edge nodes (i = 4, 5, 6, 7), do the following. If the edge node is present,
(1) add the edge node according to the corresponding interpolation function

N4(ξ, η) = (1-ξ2)(1-η) - N8/2, N5(ξ, η) = (1-η2)(1+ξ) - N8/2


N6(ξ, η) = (1-ξ2)(1+η) - N8/2, N7(ξ, η) = (1-η2)(1-ξ) - N8/2 Eq. 3•36

Then, (2) modify the four corner nodes according to the presence of the four edge nodes

N0 -= (N4+N7)/2, N1 -= (N4+N5)/2, N2 -= (N5+N6)/2, N3 -= (N6+N7)/2, Eq. 3•37

190 Workbook of Applications in VectorSpace C++ Library


H1 Type and H2 Type Objects

3 6

2
3
7 1
8 5
1
2

0 4 1
Figure 3•8 Finite element discretization of a quarter of a circle.

The order of the node number (left-hand-side of Figure 3•7) and the interpolation functions defined in the above
are both accepted standard in finite element method. A quarter of a circle is taken as a whole element or subdi-
vided into three quadrilateral elements as in Figure 3•8.
The coordinate tranformation rule for a nodal point x = (x0, x1) is now

x ( ξ, η ) = N • x Eq. 3•38

where the components of vector N are the shape functions defined in Eq. 3•33 to Eq. 3•37, and x = (x0, x1) are
nodal coordinates. The interpolation function for the z-coordinates of a point on the surface of a sphere can be
expressed similarly as

z ( ξ, η ) = N • z Eq. 3•39

where z is the height of the spherical surface above a node. For geometrical entities that are simple enough, as in
this case with the unit sphere, their algebraic expression can be found in analytical geometry. Instead of Eq. 3•39,
the height of the unit sphere can be written as

z ( ξ, η ) = 1 – ( x0 ) 2 – ( x 1 ) 2 Eq. 3•40
The volume of one-eighth of a sphere can be obtained by

volume = ∫ z ( ξ̂ )dx Eq. 3•41


x

where ξ̂ is a vector = {ξ, η}T. Either, the isoparametric interpolation of Eq. 3•39 or analytical expression of Eq.
3•40 can be used in Eq. 3•41. A three dimensional surface area of a two dimensional integration domain has its
formula as a multiple dimensional generalization to that of the arc length method shown in Eq. 3•28.

∂z 2 ∂z 2
surface area = ∫ 1 +  -------- +  -------- dx
 ∂x 0  ∂x 1
Eq. 3•42
x

Workbook of Applications in VectorSpace C++ Library 191


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
where

∂z
--------
∂x 0 dz dx –1
= ------  ------
dz
------ ≡ Eq. 3•43
dx ∂z dξ̂ dξ̂
--------
∂x 1

The Program Listing 3•5 defines the element geometry as shown in Figure 3•8. The Program Listing 3•5 is
the C++ main() porgram implements integration formula for computing volume and surface area using Eq. 3•39
or Eq. 3•39. The macro definition “__THREE_ELEMENTS”, if defined, the program discretizes one-quarter of
a circular domain into three nine-nodes quadrilateral elements, otherwise, only one element is used. The macro
definition “__ANALYTICAL_GEOMETRY”, if defined, Eq. 3•40 is used in place of Eq. 3•39. The macro defi-
nition “__SURFACE_AREA”, if defined, compute the surface area instead of the volume. (Program Listing 3•5
and Program Listing 3•5 are in project: “volume_and_surface_area”)
The results of this computation are shown in TABLE 3.7. Three elements discretization does improve the
accuracy of the integration as compared to that of the one element discretization.

Volume Surface Area


1 Element 3.99991 12.3264
3 Elements 4.17283 12.5465
Analytical 4.18879 12.5664
TABLE 3.7. Integration for Volume and Surface Area

Automatic Mesh Generation: We use, in the above, the example of evaluating the volume and the surface area
of an unit sphere because it has the advantage that the analytical solutions are available for checking the accu-
racy of the numerical integration. For simplicity, we stick to use this example in the following In a pratical appli-
cation we might face problem with more complicated geometrical domain. Therefore, we need a more
sophisticated tool to deal with the geometrical complexity. We have used the coordinate transformation method
in finite element method to evaluate the integration. The finite element method is a powerful tool in part that it
deals with complicated geometry. Therefore, we use some of the most primitive finite element objects in Chapter
4, which provides us a systematic treatment to acomplish the refinement of the meshes. We use a function
“block” to automatically generate mesh with the interface as

void block(Omega_h* Ωh, int ξ-node-no, int η-node-no,


int control-node-no, int* control-node-flag,
double* control-node-coordinates,
int first-node-no, int first-element-no)

The first argument Ωh refers to the discretized global domain, where Ω in mathematical convention is domain of
integration, and the superscript “h” denotes the discretization of the actual domain Ω with a characteristic mesh
size of “h”. The “block” function generates nodes and elements for a set of control points (4 to 9 nodes). The

192 Workbook of Applications in VectorSpace C++ Library


H1 Type and H2 Type Objects

#include "include\vs.h"
double PI = 3.141592654, deg = PI/180.0, theta = 90.0, phi = 90.0;
void sphere(double* X, double& Z, double phi, double theta) {
X[0] = sin(phi*deg)*cos(theta*deg); X[1] = sin(phi*deg)*sin(theta*deg); Z = cos(phi*deg); }
void define_element() { Three elements discretization
#if defined(__THREE_ELEMENTS)
const int ELEMENT_NO = 3;
double X[ELEMENT_NO][9][2], Z[ELEMENT_NO][9];
sphere(X[0][0], Z[0][0], 0.0, 0.0); frist element
sphere(X[0][1], Z[0][1], 1.0/2.0*phi, 0.0);
sphere(X[0][2], Z[0][2], 1.0/2.0*phi, 1.0/2.0*theta);
sphere(X[0][3], Z[0][3], 1.0/2.0*phi, theta);
sphere(X[0][4], Z[0][4], 1.0/4.0*phi, 0.0);
sphere(X[0][5], Z[0][5], 1.0/2.0*phi, 1.0/4.0*theta);
sphere(X[0][6], Z[0][6], 1.0/2.0*phi, 3.0/4.0*theta);
sphere(X[0][7], Z[0][7], 1.0/4.0*phi, theta);
sphere(X[0][8], Z[0][8], 1.0/4.0*phi, 1.0/2.0*theta);
sphere(X[1][0], Z[1][0], 1.0/2.0*phi, 0.0);
sphere(X[1][1], Z[1][1], phi, 0.0);
second element
sphere(X[1][2], Z[1][2], phi, 1.0/2.0*theta);
sphere(X[1][3], Z[1][3], 1.0/2.0*phi, 1.0/2.0*theta);
sphere(X[1][4], Z[1][4], 3.0/4.0*phi, 0.0);
sphere(X[1][5], Z[1][5], phi, 1.0/4.0*theta);
sphere(X[1][6],Z[1][6],3.0/4.0*phi,1.0/2.0*theta);
sphere(X[1][7],Z[1][7],1.0/2.0*phi,1.0/4.0*theta);
sphere(X[1][8],Z[1][8],3.0/4.0*phi,1.0/4.0*theta);
sphere(X[2][0], Z[2][0], 1.0/2.0*phi, theta);
sphere(X[2][1], Z[2][1], 1.0/2.0*phi, 1.0/2.0*theta); third element
sphere(X[2][2], Z[2][2], phi, 1.0/2.0*theta);
sphere(X[2][3], Z[2][3], phi, theta);
sphere(X[2][4], Z[2][4], 1.0/2.0*phi, 3.0/4.0*theta);
sphere(X[2][5], Z[2][5], 3.0/4.0*phi, 1.0/2.0*theta);
sphere(X[2][6], Z[2][6], phi, 3.0/4.0*theta);
sphere(X[2][7], Z[2][7], 3.0/4.0*phi, theta);
sphere(X[2][8], Z[2][8], 3.0/4.0*phi, 3.0/4.0*theta);
#else
const int ELEMENT_NO = 1; One element discretization
double X[ELEMENT_NO][9][2], Z[ELEMENT_NO][9];
sphere(X[0][0], Z[0][0], 0.0, 0.0); sphere(X[0][1], Z[0][1], phi, 0.0);
sphere(X[0][2], Z[0][2], phi, 1.0/2.0*theta); sphere(X[0][3], Z[0][3], phi, theta);
sphere(X[0][4], Z[0][4], 1.0/2.0*phi, 0.0); sphere(X[0][5], Z[0][5], phi, 1.0/4.0*theta);
sphere(X[0][6], Z[0][6], phi, 3.0/4.0*theta); sphere(X[0][7], Z[0][7], 1.0/2.0*phi, theta);
sphere(X[0][8], Z[0][8], 1.0/2.0*phi, 1.0/2.0*theta);
#endif
}

Listing 3•4 Element discretization of two dimensional integration domain for a sphere with unit radius.

control points use linear of quadratic interpolation functions to map a referential domain to a physical domain
(see Figure 3•7). The second and third argument “ξ-node-no” and “η-node-no” indicate the number of nodes
generated in each direction. The fourth argument “control-node-no” has values of 4 to 9. The following “control-
node-flag” uses either “TRUE” (=1) or “FALSE” (=0) to indicate if a node is to be used as a control node. The
pointer to double array is the nodal coordinates of the control nodes. This is followed by two integers to indicate
the first node number and the first element number to be generated. For example (see also Figure 3•9),

Workbook of Applications in VectorSpace C++ Library 193


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
1 void sphere(double* X, double phi, double theta);
2 EP::element_pattern EP::ep = EP::LAGRANGIAN_9_NODES;
3 Omega_h::Omega_h() { // constructor for Ωh
4 double X[9][2];
5 const double theta = 90.0; const double phi = 90.0;
6 int control_node_flag[9] = {TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE};
7 sphere(X[0], 0.0, 0.0); sphere(X[1], 1.0/2.0*phi, 0.0);
8 sphere(X[2], 1.0/2.0*phi, 1.0/2.0*theta); sphere(X[3], 1.0/2.0*phi, theta);
9 block(this, 3, 3, 4, control_node_flag, X[0], 0, 0);
...
10 }

int main() {
define_element();
Quadrature qp(2, 9);
H1 ZAI(2, (double*)0, qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 9, 2, qp), Zai, Eta;
Zai &= ZAI[0]; Eta &= ZAI[1];
4 to 9 nodes algorithm
N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4; Step 1: four corner nodes
N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4;

N[8] = (1-Zai.pow(2))*(1-Eta.pow(2));
Step 2: center nodes and modification
N[0] -= N[8]/4; N[1] -= N[8]/4; N[2] -= N[8]/4; N[3] -= N[8]/4; to four corner nodes
Step 3: four edge nodes and modifcation
N[4] = ((1-Zai.pow(2))*(1-Eta)-N[8])/2; N[5] = ((1-Eta.pow(2))*(1+Zai)-N[8])/2;
N[6] = ((1-Zai.pow(2))*(1+Eta)-N[8])/2; N[7] = ((1-Eta.pow(2))*(1-Zai)-N[8])/2;
to other nodes
N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2;
N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;

C0 vol(0.0), area(0.0);
for(int i = 0; i < ELEMENT_NO; i++) {
C0 X_(9, 2, X[i][0]); coordinate transformation
H1 x = N*X_;
x ( ξ, η ) = N • x
C0 Z_(9, Z[i]);
#if defined(__SURFACE_AREA) 1: Surface Area
H1 z = N*Z_; interpolation z ( ξ, η ) = N • z
H0 dz_dx = d(z) * d(x).inverse(),
∂z 2 ∂z 2
∫ 1 +  -------- +  -------- dx
da = sqrt((dz_dx[0]).pow(2)+(dz_dx[1]).pow(2)+1.0);
area =
area += da | J(d(x).det());  ∂x 0  ∂x 1
} x
cout << (8.0*90.0/theta*area) << endl;
#else
2: Volume
#if defined(__ANALYTICAL_GEOMETRY)
H0 z = sqrt(1-((H0)x[0]).pow(2)-((H0)x[1]).pow(2)); z ( ξ, η ) = 1 – ( x 0 ) 2 – ( x1 ) 2
#else
H0 z = ((H0)N)*Z_;
#endif interpolation z ( ξ, η ) = N • z
vol += z | J(d(x).det());
}
cout << (8.0*90.0/theta*vol) << endl;
volume = ∫ z ( ξ̂ )dx
x
#endif
return 0;
}

Listing 3•5 Two dimensional integration domain for volume and surface area of a sphere with unit radius.

194 Workbook of Applications in VectorSpace C++ Library


H1 Type and H2 Type Objects
3 6

2
2
2
0 5
4 1 3
3 5
2

0 1

0 4 1
0 1
Figure 3•9 Use of “block()” function to generate elements and nodes
automatically. The control nodes are shwon as open squares.

The function “sphere()” is to generate Catesian coordinates on a circular domain (on x0-x1 plane, see Figure 3•6)
given the latitude φ and colatitude θ of a unit sphere. This generates a 9-nodes Lagrangian element with four cor-
ner nodes as control nodes (see element number “0” in Figure 3•9). The other two 9-nodes Lagrangian elements
labeled as element number “1” and element number “2” and can be constructed as .

1 sphere(X[0], 1.0/2.0*phi, 0.0); sphere(X[1], phi, 0.0);


2 sphere(X[2], phi, 1.0/2.0*theta); sphere(X[3], 1.0/2.0*phi, 1.0/2.0*theta);
3 sphere(X[4], 3.0/4.0*phi, 0.0); sphere(X[5], phi, 1.0/4.0*theta);
4 control_node_flag[4] = FALSE;
5 block(this, 3, 3, 6, control_node_flag, X[0], 9, 1);
6 sphere(X[0], 1.0/2.0*phi, theta); sphere(X[1], 1.0/2.0*phi, 1.0/2.0*theta);
7 sphere(X[2], phi, 1.0/2.0*theta); sphere(X[3], phi, theta);
8 sphere(X[4], 1.0/2.0*phi, 3.0/4.0*theta); sphere(X[5], 3.0/4.0*phi, 1.0/2.0*theta);
9 sphere(X[6], phi, 3.0/4.0*theta);
10 control_node_flag[4] = control_node_flag[5] = FALSE;
11 block(this, 3, 3, 7, control_node_flag, X[0], 18, 2);

The control nodes ordering is the same as that shown in Figure 3•7. For the element number “1”, the total number
of control nodes is 6, and the node number “4” is skipped by setting “control_node_flag[4] = FALSE;”. For the
element number “2”, the total number of control nodes is “7”, and both node number “4” and “5” are skipped by
setting “control_node_flag[4] = control_node_flag[5] = FALSE;”. In the main() program, the discretized domain
is declared as

Omega_h oh;

the geometrical information of nodes and elements can be accessed as

Workbook of Applications in VectorSpace C++ Library 195


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
Omega_eh elem = oh(j); // j-th element Ωhe with “operator()(int)”
Node nd = oh[i]; // i-th node with “operator[](int)”
doube x = nd[0], // x-coordinates of i-th node using “operator[](int)”
y = nd[1]; // y-coordinates of i-th node using “operator[](int)”

Program Listing 3•6 is the program to generate three 9-nodes Lagragian elements. The geometrical information
on the nodes and elements are coded in the segment for the constructor of the discretized global domain
“Omega_h::Omega_h()”. The actual instance of the discretized global domain is declared inside the main() pro-
gram. The project “block_volume_integral” can be used to generate either 9-nodes Lagragian elements or 4-
nodes quadrilateral elements. The 9 nodes Lagragian elements are generated when the macro definition
“__LAGRANGAIN_9_NODES” is defined at the compile time. 2, 4, and 8 subdivision for each side of the
block can be defined using macro definition “__TWO_SEGMENTS” and “__FOUR_SEGMENTS” for the first
two kinds of subdivision. The nodes and elements for these options are shown in Figure 3•10. The results of
refined computation are shown in TABLE 3.8.

12 elements 3 elements

48 elements
12 elements

192 elements
48 elements

4-nodes Quadrilateral 9-nodes Lagrangian element


Figure 3•10 Discretization of global domain into nodes and elements.

196 Workbook of Applications in VectorSpace C++ Library


H1 Type and H2 Type Objects

#include "include\vs.h"
#include "include\dynamic_array.h"
#include "include\omega_h.h"
#include "include\block.h"
static const double PI = 3.14159265359; static const double deg = PI/180.0;
void sphere(double* X, double phi, double theta) {
X[0] = sin(phi*deg)*cos(theta*deg); X[1] = sin(phi*deg)*sin(theta*deg); }
EP::element_pattern EP::ep = EP::LAGRANGIAN_9_NODES; 9-nodes Lagrangian element
Omega_h::Omega_h() {
double X[9][2]; const double theta = 90.0; const double phi = 90.0;
int control_node_flag[9] = {1, 1, 1, 1, 1, 1, 1, 1, 1};
sphere(X[0], 0.0, 0.0); sphere(X[1], 1.0/2.0*phi, 0.0); block # 0; 4 control nodes
sphere(X[2], 1.0/2.0*phi, 1.0/2.0*theta); sphere(X[3], 1.0/2.0*phi, theta);
block(this, 3, 3, 4, control_node_flag, X[0], 0, 0);
sphere(X[0], 1.0/2.0*phi, 0.0); sphere(X[1], phi, 0.0); sphere(X[2], phi, 1.0/2.0*theta);
block # 1; 5 control nodes
sphere(X[3], 1.0/2.0*phi, 1.0/2.0*theta); sphere(X[4], 3.0/4.0*phi, 0.0);
sphere(X[5], phi, 1.0/4.0*theta); control_node_flag[4] = 0;
block(this, 3, 3, 6, control_node_flag, X[0], 9, 1);
sphere(X[0], 1.0/2.0*phi, theta); sphere(X[1], 1.0/2.0*phi, 1.0/2.0*theta);
block # 2; 5 control nodes
sphere(X[2], phi, 1.0/2.0*theta); sphere(X[3], phi, theta);
sphere(X[4], 1.0/2.0*phi, 3.0/4.0*theta); sphere(X[5], 3.0/4.0*phi, 1.0/2.0*theta);
sphere(X[6], phi, 3.0/4.0*theta); control_node_flag[4] = control_node_flag[5] = 0;
block(this, 3, 3, 7, control_node_flag, X[0], 18, 2);
}
int main() {
Quadrature qp(2, 9);
H1 ZAI(2, (double*)0, qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 9/*nen*/, 2/*nsd*/, qp), Zai, Eta;
Zai &= ZAI[0]; Eta &= ZAI[1];
N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4;
4 to 9 nodes shape function
N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4; Step 1: four corner nodes
N[8] = (1-Zai.pow(2))*(1-Eta.pow(2)); Step 2: center nodes and modification
N[0] -= N[8]/4; N[1] -= N[8]/4; N[2] -= N[8]/4; N[3] -= N[8]/4;
N[4] = ((1-Zai.pow(2))*(1-Eta)-N[8])/2; N[5] = ((1-Eta.pow(2))*(1+Zai)-N[8])/2;
to four corner nodes
N[6] = ((1-Zai.pow(2))*(1+Eta)-N[8])/2; N[7] = ((1-Eta.pow(2))*(1-Zai)-N[8])/2; Step 3: four edge nodes and modifcation
N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2; to other nodes
N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;
Omega_h oh; C0 vol(0.0);
Ωh
for(int i = 0; i < oh.total_element_no(); i++) {
Omega_eh& elem = oh(i); element Ωhe
double X[9][2];
for(int j = 0; j < 9; j++) {
int nn = elem[j];
Node& nd = oh[nn]; x- and y- coordinates
for(int k = 0; k < 2; k++) X[j][k] = nd[k];
x ( ξ, η ) = N • x
}
C0 X_(9, 2, X[0]); H1 x = N*X_; Volume
H0 z = sqrt(1-((H0)x[0]).pow(2)-((H0)x[1]).pow(2));
vol += z | J(d(x).det());
z ( ξ, η ) =
1 – ( x0 ) 2 – ( x1 )2
}
cout << (8.0*vol) << endl; interpolation z ( ξ, η ) = N • z
return 0; volume = ∫ z ( ξ̂ )dx
}
x

Listing 3•6 Volume integration using “block()” function to generate nodes and elements automatically.

Workbook of Applications in VectorSpace C++ Library 197


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
.

No. of Elements 4-nodes Quadrilateral 9-nodes Lagragian


3 — 4.19694
12 4.17459 4.19144
48 4.18803 4.18958
192 4.18092 —
TABLE 3.8. Volume integral of an unit sphere (analytical =
4π/3 = 4.18879).

3.2.2 Integrable_Tangent_of_Tangent_Bundle /Integrable_Vector_of_Tangent_of_Tangent_Bundle


Integrable_Tangent_of_Tangent_Bundle and Integrable_Vector_of_Tangent_of_Tangent_Bundle are two
objects of H2 type that are the integrable extension of Tangent_of_Tangent_Bundle and
Vector_of_Tangent_of_Tangent_Bundle of C2 type objects.

Constructors
The dedicated constructors for a Integrable_Tangent_Bundle are

H2::H2(const Quadrature&)
H2::H2(double* v, const Quadrature&)
H2::H2(double* v, int spatial_dimension, const Quadrature&)

The dedicated constructors for a Integrable_Vector_of_Tangent_of_Tangent_Bundle is

H2::H2(int vector_size, double* v, const Quadrature&)

For the dedicated constructor, the size of vector is equal to the number of spatial dimension.
The constant strings used for the virtual constructors and autonomous virtual constructor are listed in the
following two boxes. The macro defintions use for the virtual constructors of the
Integrable_Tangent_of_Tangent_Bundle and Integrable_Vector_of_Tangent_of_Tangent_Bundle are

H2 x = INTEGRABLE_TANGENT_OF_TANGENT_BUNDLE(const char*, ...);


H2 x = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(const char*, ...);

respectively. “H2 x = H2(const char*, ...)” is used for autonomous virtual constructors. There are not much dif-
ferent in the use of H2 type objects compared to that of the H1 type objects, The major difference is that the H2
type objects allow twice differentiable operation needed in some applications. In the next section on the varia-
tional methods, a lot of examples with H2 type are shown.

198 Workbook of Applications in VectorSpace C++ Library


H1 Type and H2 Type Objects

virtual constructor string VectorSpace C++ library definition priority

by reference
“H2&” H2 type Matrix 1
“H2*” a pointer to H2 type Matrix 2
“double*, double*, double*, v, dv, ddv 7
Quadrature”
“double*, double*, double*, v, dv, ddv, 8
int, Quadrature” spatial dimension
by value
“Quadrature”, 3
“int, Quadrature” memory-row-length, memory-column-length 4
“const double&, const double&, v, dv, (for spatial dimension = 1 only) 5
const double&, Quadrature” ddv
“const double*, const double*, v, dv, 6
const double*, int, Quadrature&” ddv, spatial dimension
“const H0&, const H0&, base point, tangent, 9
const H0&” tangent of tangent
“const H0*, const H0*, base point, tangent, 10
const H0*” tangent of tangent
“const H2&” Integrable_Tangent_of_Tangent_Bundle 11
“const H2*” Integrable_Tangent_of_Tangent_Bundle* 12
Strings in H2 virtual constructor for Integrable_Tangent _of_Tangent_Bundle object.

virtual constructor string VectorSpace C++ library definition priority

by reference
“H2&” H2 type Matrix —
“H2*” a pointer to H2 type Matrix —
“int, int, double*, double*, double*,vector size, spatial dimension, v, dv, ddv 14
Quadrature, int, int” quadrature, memory row size, and column size
by value
“int, int, Quadrature”, vector size, spatial dimension 13
“const H0&, const H0&, base point , tangent —
const H0&” tangent of tangent
“const H0*, const H0*, base point, tangent —
const H0*” tangent of tangent
“int, const H2*” vector size, Integrable_Tangent_of_Tangent_Bundle* 15
“const H2&” Integrable_Vector_of_Tangent_of_Tangent_Bundle —
“const H2*” Integrable_Vector_of_Tangent_of_Tangent_Bundle* —

Strings in H2 virtual constructor for Integrable_Vector_of_Tangent_of_Tangent_Bundle object.

Workbook of Applications in VectorSpace C++ Library 199


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
3.3 Variational Methods
For a linear differential equation with homogeneous essential boundary condition,

Au = f Eq. 3•44

where A is a self-adjoint linear operator. We can define a functional J(u) as

1
J(u) = --- a(u, u) - (u, f) Eq. 3•45
2

where (u, f) is a linear functional and a(u, u) is a bilinear functional with a(u, u) ≡ (u, Au). For u0, a solution
corresponding to the minimum value of J; i.e., J(u0) ≤ J(u). We define u = u0 + εη, where ε is a small real num-
ber and η is called the variation. The variation of J denoted as δJ can be defined with the directional derivatives
as

 d 
δJ(u)= ε   ------ J ( u 0 + εη )  Eq. 3•46
 dε ε = 0

Set δJ(u) = 0 for the minimization of J. Considering Eq. 3•46 equals zero is always true for the arbitary small
real number ε, the term in bracket must equal zero for u0 to give a stationary value of J. So, we have

ε2
------  --- a ( u 0, u 0 ) + εa ( η, u 0 ) + ----- a ( η, η ) – ( u 0, f ) – ε ( η, f )
d 1
= a ( η, u 0 ) – ( η, f ) = 0 Eq. 3•47
dε  2 2 
ε=0

That is,

a(η, u0) = (η, f) Eq. 3•48

which is just the Eq. 3•44 multiplies by the variation η. So it is equivalent to the Eq. 3•44. Therefore, the solu-
tion of the differential equation (strong form) of Eq. 3•44, is the solution of the minimization problem of Eq.
3•45, and is also the solution of the variational formulation (weak form) of Eq. 3•48.
Re-defining “v” = u0 + εη, and “u” = u0 in Eq. 3•48, we have (v, Au) = (v, f), And, the adjoint homogeneous
equation A*v = 0, therefore, (A*v, u) = (0, u).

(v, Au) - (A*v, u) = (v, f) - (0, u) = (v, f) Eq. 3•49

The left-hand-side of Eq. 3•49 is (Au, v) - (u, A*v) = 0 for self-adjoint operator A. So, we have

(v, f) = 0 Eq. 3•50

Eq. 3•50 is the solvability condition. From linear algebra, orthogonal complement of the range space of A is the
null space of its adjoint A*, usually denoted as R(A)⊥ = N(A*). Since f ∈ R(A) and v ∈ N(A*), this leads directly
to the orthogonal relation (v, f) = 0.

200 Workbook of Applications in VectorSpace C++ Library


Variational Methods
3.3.1 Rayleigh-Ritz Method
For the Rayleigh-Ritz method we write the approximation of the variational formulation in Eq. 3•48 as

a(vN, uN) = (vN, f) Eq. 3•51

where vN and uN is taken as

N–1
u N = vN = ∑ ci φi Eq. 3•52
i=0

The approximation basis functions φi should give finite energy with respect to the bilinear form a(. , .), such that
derivatives corresponding to the operator A is integrable. Substituting Eq. 3•52 into Eq. 3•51 gives a system of
simultaneous equations

N–1
ci ∑ a ( φi, φj ) cj = c i ( f, φ i ), where i = 0, 1, …, N – 1 Eq. 3•53
j=0

coefficients ci are present in both sides of the equation, so it can be dropped. Eq. 3•53 can be re-written in matrix
form as

Mc=b Eq. 3•54

where Mij = a(φi, φj), and bi = (φi, f). The solution vector c of Eq. 3•54 is the vector consists of coefficients ci in
Eq. 3•52. If φi are chosen to be orthogonal basis functions, M is diagonal; i.e., Mij = 0 for i ≠ j.

Second-order Differential Equation


The first set of example problems is a second-order differential equation1

2
d u
– 2
= cos πx, where 0 < x < 1 Eq. 3•55
dx

with different kinds of boundary conditions for each of the two problems:

1. Dirichlet boundary conditions—u(0) = u(1) = 0


2. Mixed boundary conditions—u(0) = 0, and u’(1) = 0

1. p. 265-270 in J.N. Reddy, 1986, “Applied functional analysis and variational methods in engineering”, McGraw-Hill, Inc.

Workbook of Applications in VectorSpace C++ Library 201


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
Dirichlet Boundary Conditions: For the first one with the Dirichlet boundary conditions u(0) = u(1) = 0 the
exact solution is

1
uexact(x) = -----2 ( cos πx + 2x – 1 ) Eq. 3•56
π

From Eq. 3•51 we have the weak form as

a(vN, uN) - (vN, f) =


1 2 1 1 1
 d uN  dv N du N du N dv N du N
∫  vN d x2 + vN cos πx dx = ∫  d x d x + v N cos πx dx + v N d x 0
= ∫  d x dx
+ v N cos πx dx = 0

Eq. 3•57
0 0 0

We have applied the integration by part on the term with the second order derivative and requring homogeneous
conditions, vN(0) = vN(1) = 0, on the boundary integral term

1
du N
vN = 0 Eq. 3•58
dx 0

Therefore, we choose φi in Eq. 3•52 as the set of orthogonal functions {sin [(i+1)πx] | 0 ≤ i ≤ N – 1 }. Notice
that this set of functions satisfies the requirement of vN(0) = vN(1) =0. Since the set is orthogonal, all off-diago-
nals of M matrix are zeros. We only consider the diagonal elements of M as

1
dφ i dφ i
diag(M)i = a(φi, φi) = ∫ dx dx
dx , and bi = φi cos πx Eq. 3•59
0

The solution of the coefficient vector is simply ci = bi / diag(M)i , component by component without having to
solve a system of simultaneous equation. The Program Listing 3•7 implements the Eq. 3•59. After the ci are
solved for, the solution of the Dirichlet boundary value problem is obtained by plug in ci in Eq. 3•52.
In Figure 3•11, the exact solution in Eq. 3•56 is compared to the solution constructed using ci computed from
Program Listing 3•7. The approximated vairiational solution, in the form of Eq. 3•52 uN = ciφi, with N = 2 to N =
16 (even numbers only) are ploted in the left-hand-side. Only the case with N=2 has clear differences from the
exact solution, other solutions (N= 4-16) converge rapidly towards the exact solution. Figure 3•12 shows the
alternative approach discussed in page 170 with an integral equation using Green function as kernel for solution.
The results are computed by Program Listing 3•7, which is almost with a simple modification that “f(x) = cos
πx” instead of “f(x) = sin(πx)”.

202 Workbook of Applications in VectorSpace C++ Library


Variational Methods

#include "include\vs.h"
#define PI 3.141592654
int main() {
const int n = 16;
double w[35] = {1.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, extended Simpson’s rule
4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0,
4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0,
4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 1.0/3.0};
Quadrature qp(w, 0.0, 1.0, 35);
H1 x(qp),
phi = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", n, 1, qp);
for(int i = 0; i < n; i++) phi[i] = sin(((double)i+1.0)*PI*x); φi = sin[(i+1)πx], i = 0, N-1
H0 d_phi_2(n, (double*)0, qp); 1
dφ i dφ i
for(int i = 0; i < n; i++) d_phi_2[i] = d(phi[i]).pow(2);
C0 M = d_phi_2 | J(1.0/34.0);
diag(M)i = a(φi, φi) = ∫ dx dx
dx
C0 b = ( ((H0)phi) * cos(PI*((H0)x)) ) | J(1.0/34.0); 0
C0 c(n, (double*)0); bi = φi cos πx
for(int i = 0; i < n; i++) c[i] = b[i] / M[i];
ci = bi / diag(M)i
for(int i = 0; i < n; i++) cout << c[i] << ", "; cout << endl;
return 0;
}

Listing 3•7 Dirichlet boundary condition u(0) = u(1) = 0, for the differential equation - u” = f (project:
“dirichlet”).
Exact = (cos πx + 2x -1) /π2 uN = ciφi , i = 0, Ν( = 2−16)
0.02 0.02 Ν=2

0.01 0.01
0.20.40.60.8 1 0.20.40.60.8 1
-0.01 -0.01
-0.02 -0.02
Figure 3•11 The left-hand-side is the exact solution, and the right-hand-side is the solution
with N=2 to N = 16 Rayleigh-Ritz method.

Mixed Boundary Conditions: For the Mixed boundary conditions u(0) = u’(1) = 0, the exact solution is

1
uexact(x) = -----2 ( cos πx – 1 ) Eq. 3•60
π

With this different set of boundary conditions the Eq. 3•57 to Eq. 3•59 still hold. However, φi = {sin(iπx) |
0 ≤ i ≤ N – 1 } now is not complete with respect to x = 1, where u(1) = 0 always. We can add a new algebraic
function φ0 = x (the simplest one) to fix this situation. That is the new set of φ becomes {x, sin(πx), sin(2πx),
sin(3πx), ... }. Therefore, the Program Listing 3•7 only needs the slightest modification. The Program Listing 3•8
is a modified version for the mixed boundary conditions problem. Aternatively, since the solution of ci in Pro-
gram Listing 3•7 is working out component by component for this problem (since we select a orthogonal set of
φi), we can just compute one additional term c0 with φ0 = x and add to the solution of the problem 1. Figure 3•13

Workbook of Applications in VectorSpace C++ Library 203


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

0.02

0.01
uexact = (cos πx + 2x -1) /π2

0.2 0.4 0.6 0.8 1

-0.01

-0.02

Figure 3•12 Point-values are solution of integral equation using Green function computed by Program
Listing 3•1 with a simple modification that f(x) = cos πx.
shows that the solution up to N= 2 is already a good enough approximation to the exact solution for this
problem.
#include "include\vs.h"
#define PI 3.141592654
int main() {
const int n = 16;
double w[35] = {1.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, extended Simpson’s rule
4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0,
4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0,
4.0/3.0, 2.0/3.0, 4.0/3.0, 2.0/3.0, 4.0/3.0, 1.0/3.0};
Quadrature qp(w, 0.0, 1.0, 35);
H1 x(qp),
phi = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", n, 1, qp);
phi[0] = x; φ0 = x, φi = sin[iπx], i = 1, N-1
for(int i = 1; i < n; i++) phi[i] = sin(((double)i+1)*PI*x);
H0 d_phi_2(n, (double*)0, qp);
for(int i = 0; i < n; i++) d_phi_2[i] = d(phi[i]).pow(2);
C0 M = d_phi_2 | J(1.0/34.0);
C0 b = ( ((H0)phi) * cos(PI*((H0)x)) ) | J(1.0/34.0);
C0 c(n, (double*)0);
for(int i = 0; i < n; i++) c[i] = b[i] / M[i];
for(int i = 0; i < n; i++) cout << c[i] << ", "; cout << endl;
return 0;
}

Listing 3•8 Mixed boundary condition u(0) = u’(1) = 0, for the differential equation - u” = f (project: “mixed”).

Dirichlet Boundary Conditions Revisited: We can use polynominals instead of trigonometric functions as the
basis functions, such as

φi = x(i+1) (1-x). Eq. 3•61

Notice that this choice of φi satisfies the Dirichlet boundary conditions u(0) = u(1) = 0. However the φis are not
orthogonal with each other and therefore Mij will not be a diagonal matrix. The solution of a system of simulta-
neous equations is required. We choose 0 ≤ i ≤ 3 ; i.e., N=4. Hence the highest order polynomial is fifth-order.
One modification to the Program Listing 3•7 is that we use Bode’s integration rule which is a fifth-order approx-
imation. The weights for the Bode’s rule is

204 Workbook of Applications in VectorSpace C++ Library


Variational Methods

0.20.40.60.8 1 0.20.40.60.8 1
-0.05 Exact = (cos πx -1) /π2 -0.05 uN = ciφi , i = 0, Ν( = 2−16)
-0.1 -0.1
-0.15 -0.15
-0.2 -0.2
Figure 3•13 Exact solution (cos πx -1) /π2 comparing to solutions of N= 2-16 Rayleigh-Ritz method.
14 24 64 28 64 24 64 28 64 24 64 14
------, ------ , ------, ------, ------ , ------, ------, ------, …, ------, ------, ------, ------
45 45 45 45 45 45 45 45 45 45 45 45

Redefine φi simply as “phi[0] = x*(1-x); for(int i = 1; i < 4; i++) phi[i] = phi[i-1]*x;” Program Listing 3•9 imple-
ments the polynomial approximation. Figure 3•14 shows the solutions of the polynomial approximations which
is almost the same as the eact solution visually.
#include "include\vs.h"
#define PI 3.141592654
int main() {
double w[17] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0,
64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, extended Bode’s rule
64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0,
64.0/45.0, 24.0/45.0, 64.0/45.0, 14.0/45.0};
Quadrature qp(w, 0.0, 1.0, 17);
H1 x(qp), φi = x(i+1) (1-x), i = 0, N-1
phi = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 1, qp); 1
phi[0] = x*(1-x); for(int i = 1; i < n; i++) phi[i] = phi[i-1]*x; dφ i dφ j
C0 M = ( d(phi)*(~d(phi)) ) | J(1.0/16.0); Mij = a(φi, φj) = ∫ dx dx
dx
C0 b = ( ((H0)phi) * cos(PI*((H0)x)) ) | J(1.0/16.0); 0
C0 c = b / M;
for(int i = 0; i < 4; i++) cout << c[i] << ", ";
bi = φi cos πx
return 0; ci = bi /Mij
}

Listing 3•9 Polynomial basis φi = x(i+1) (1-x), i = 0, N-1 for the Dirichlet boundary condition u(0) = u(1) = 0,
for the differential equation - u” = f (project: “polynomial”).

Fourth-order Differential Equation


Now we consider a second example problem which is a fourth-order differentail equation which is apllicable
for transverse deflection of a beam in the subject of mehanics of materials1. Denote the transverse deflection of

1. p.188-190, and p. 351-353 in J.M.Gere, and S.P. Timoshenko, 1984, “Mechanics of materials”, 2nd ed., Wadsworth, Inc.,
Belmont, California.

Workbook of Applications in VectorSpace C++ Library 205


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

0.02

0.01

0.2 0.4 0.6 0.8 1

-0.01

-0.02

Figure 3•14 Solution of Dirichlet boundary problem using polynomial basis functions.
the beam as “w” (see Figure 3•15). From balance of force, the transverse loading (f) is equal to the derivative of
shear force (V) as

dV/dx =- f Eq. 3•62

and the shear force is equal to the derivative of bending moment (M) as

dM/dx =- V Eq. 3•63

Therefore,

2
dM
= f Eq. 3•64
dx2

The curvature (d2w/dx2) of the beam is related to the bending moment and the flexure rigidity of the beam as

w V
f(x)

x
M

Figure 3•15 Transverse deflection of a beam.

206 Workbook of Applications in VectorSpace C++ Library


Variational Methods
2
dw M
= --------- Eq. 3•65
dx2 2EI

where EI is the flexural rigidity. Substituting M in Eq. 3•65 into Eq. 3•64 gives

2
d  d w
2
 EI  = f, where 0 < x < L Eq. 3•66
d x  d x2 

For the current problem, Eq. 3•66 is subject to boundary conditions

2 2
dw dw d  d w
w( 0 ) = ( 0 ) = 0, EI 2 ( L ) = M, V ( L ) =  EI  (L) = 0 Eq. 3•67
dx dx d x d x2 

The exact solution for this problem is

2M + fL 2
w ( x ) =  ---------------------- x 2 – --------- x 3 + ------------ x 4
fL f
 4EI 
Eq. 3•68
6EI 24EI

Four weak formulations can be used for this problem1

1. Irreducible formulation,
2. Lagrange multiplier formulation,
3. Penalty function formulation, and
4. Mixed formulation

1. Irreducible Formulation: The irreducible formulation has the highest order of differential equation. We first
need to transform the end bending moment boundary condition into a homogeneous one by change of variable as

u = w - w0, where w0 = Mx2 / 2EI

The problem posed by Eq. 3•66 and Eq. 3•67 becomes

2
d  d u
2
 EI  = f, where 0 < x < L Eq. 3•69
dx  dx2 

with the homogeneous boundary conditions of

1. From examples in p. 156-158, and p. 275-280 in J.N. Reddy, 1986, “Applied functional analysis and variational methods
in engineering”, McGraw-Hill, Inc.

Workbook of Applications in VectorSpace C++ Library 207


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
2 2
du du d  d u
u(0) = ( 0 ) = EI 2 ( L ) =  EI  (L) = 0 Eq. 3•70
dx dx d x dx2 

The Lagrangian functional corresponding to this is

L 2 2
EI  d u 
J(u) = ∫ ------   – fu dx
2  d x2 
Eq. 3•71
0

Using δu = ε v, where ε is a small real number, and setting δJ(u) =0 gives

L 2 L 2 2
2 d u  d v  d u 

d
δJ ( u ) = EI ( δu )  2  – δuf dx = ε ∫ EI  2   2  – vf dx = 0 Eq. 3•72
dx 2
 d x  dx  dx 
0 0

The second identity is obtained from applying integration by parts twice and considering that all boundary con-
ditions are homogeneous. Dropping ε , for which is arbitary, we can define an approximated system of equations
with left-hand-side and right-hand-side as

L 2 2 L
 d v N  d u N
a(vN, uN) = ∫ EI  2   2  dx , and (vN, f) =
dx  dx 
∫ [ vN f ] dx Eq. 3•73
0 0

respectively. The approximation basis functions are taken as φi = {x i+2}, i = 0,1, ..., N-1. Program Listing 3•10
implements Eq. 3•73, with N= 2, 3. The case with N= 3 actually produces the coefficients of the exact solution
in Eq. 3•68. Figure 3•16 shows N=2 is almost identical to the exact solution visually.
0.6

0.5

0.4
Exact = (N=3) ~ (N=2)
0.3

0.2

0.1

0.2 0.4 0.6 0.8 1

Figure 3•16 Irreducible formulation for transeverse deflection of a beam with N=2, 3.

2. Lagrange Multiplier Formulation: We refer to Chapter 2, Eq. 2•11 in page 118 for an introduction on the
Lagrangian functional in the Lagrange multiplier method for the constrained optimization problem. We now
define the constraint equation as the variable of the negative slope ψ

dw
ψ = – Eq. 3•74
dx

208 Workbook of Applications in VectorSpace C++ Library


Variational Methods
#include "include\vs.h"
int main() {
double L_ = 1.0, E_ = 1.0, I_ = 1.0, f_ = 1.0, M_ = 1.0;
for(int N = 2; N < 4; N++) {
double w[17] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, extended Bode’s rule
64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0,
64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0,
64.0/45.0, 24.0/45.0, 64.0/45.0, 14.0/45.0};
Quadrature qp(w, 0.0, L_, 17); φi = x(i+2), i = 0, N-1
H2 x((double*)0, qp), L 2 2
phi = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(  d φ i  d φ j
"int, int, Quadrature", N/*vector size*/, 1/*spatial dim.*/, qp); Mij = ∫ EI  2   2  dx
dx  dx 
for(int i = 0; i < N; i++) phi[i] = x.pow(i+2); 0
H0 d2_phi = INTEGRABLE_VECTOR("int, Quadrature", N, qp); L
for(int i = 0; i < N; i++) d2_phi[i] = dd(phi)(i)[0][0];
C0 M = ( E_* I_* (d2_phi%d2_phi) ) | J(L_/16.0); bi = ∫ [ φi f ] dx
C0 b = ( ( ((H0)phi) * f_ ) | J(L_/16.0) )+ M_bc; 0
C0 c = b / M;
for(int i = 0; i < N; i++) cout << c[i] << endl;
ci = bi /Mij
} N = 2: ci={0.20833,-0.0833333}T,
return 0; N = 3: ci={0.25, -0.166667, 0.041667}T
}

Listing 3•10 Transverse deflection of a beam using irreducible formulation. φi = x(i+2) , i = 0, N-1 (project:
irreducible_formulation”).

Substituting Eq. 3•74 into Eq. 3•71 and considering the boundary term yields

L
EI  dψ 2
J ( ψ, w ) = ∫ ------
2 dx 
– fw dx + ( ψM ) Eq. 3•75
0 L

Using the Lagrangian multiplier λ with the constraint equation (Eq. 3•74), we can define the Lagrangian func-
tional l(ψ, w, λ) as

L
EI  dψ 2
– f w + λ  ψ +  dx + ( ψM )
dw
l ( ψ, w, λ ) = ∫ ------
2 dx  dx
Eq. 3•76
0 L

subject to boundary condition that ψ(0) = w(0) = 0. The Euler-Lagrange equations can be obtained by setting
δl(ψ, w, λ) = 0 as

Workbook of Applications in VectorSpace C++ Library 209


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
L
dδψ dψ
δl ( ψ, w, λ ) = ∫ EI ----------
dx d x
+ ( δψ )λ dx + ( δψM ) +
0 L
L

∫  ---------- f dx +
dδw
- λ – δw
dx 
0
L

∫ δλ  ψ + d x  dx
dw
Eq. 3•77
0

=0

The Lagrange multiplier in this case has physical interpretation of the shear force.

2
d  d w
λ =  EI  Eq. 3•78
dx d x2 

Therefore, the exact solution for ψ and λ can be obtained by differentiating the exact solution for w(x) in Eq.
3•68 as

– ( 2M + fL 2 ) f
fL 2 --------
ψ ( x ) = ------------------------------
2EI 2EI x – 6EI x ,
x + --------
- - 3 λ = f(L – x ) Eq. 3•79

The approximation basis functions for each approximated variable are taken as

ψ N wN λ N = c iψ φ iψ c iw φiw c iλ φ iλ

Eq. 3•77 can be re-written in matrix form as

L L
dφ iψ dφ jψ
– ( φiψ M )
∫ ---------
EI
dx dx
- ---------- dx 0 ∫ φiψ φjλ dx c jψ L
0 0
L L
dφ iw
0 0 ∫ ---------
dx j
- φ λ dx c jw = ∫ φiw f dx Eq. 3•80
0 0

c jλ
L L
dφjw
∫ φiλ φjψ dx ∫
0
φ iλ ---------- dx 0
dx
0 0

Therefore, the matrix and vectors in Eq. 3•80 can be labeled to be comformed with “M c = b” (Eq. 3•54). We
first consider a one-parameter approximation with

210 Workbook of Applications in VectorSpace C++ Library


Variational Methods

φ 0ψ φ0w φ 0λ = x x 1 Eq. 3•81

then, a two-parameters approximation with

φ 0ψ φ 1ψ φ 0w φ 1w φ 0λ φ1λ = x x 2 x x 2 1 x Eq. 3•82

Eq. 3•80 with Eq. 3•81 or Eq. 3•82 offers a first non-trivial matrix formulation in this workbook and it deserves
us to look into the various ways, that VectorSpace C++ Library supports, to implment these equations. Program
Listing 3•11 implements the one-parameter approixmation.

#include "include\vs.h" Trapezoidal rule weightings {0.5, 0.5}T


int main() {
double L_ = 1.0, E_ = 1.0, I_ = 1.0, f_ = 1.0, M_ = 1.0, weight[2] = {0.5, 0.5};
Quadrature qp(weight, 0.0, L_, 2);
φ 0ψ φ 0w φ 0λ = x x 1
H1 x(qp), psi = x, w = x;
double lambda = 1.0;
H0 m = INTEGRABLE_MATRIX("int, int, Quadrature", 3, 3, qp); L dφ 0ψ dφ 0ψ
m[0][0] = (E_*I_)*d(psi)*d(psi); M00= ∫ EI ---------- ---------- dx
dx dx
m[0][2] = m[2][0] = ((H0)psi)*lambda;
0
m[1][2] = m[2][1] = d(w)*lambda;
m[0][1] = m[1][0] = m[1][1] = m[2][2] = 0.0; L
C0 M = m | J(L_), M02 =M20 = ∫ φ0ψ φ0λ dx
b(3, (double*)0);
0
b[0] = -L_*M_; b[1] = ( ((H0)w)*f_ ) | J(L_); b[2] = 0.0;
C0 c = b / M; L dφ w


0 λ
for(int i = 0; i < 3; i++) cout << c[i] << ", "; cout << endl; M12 =M21 = - φ dx
---------
dx 0
return 0; 0
} T
L 
b= – ML  ∫ φ 0w fdx 0
 
0 
Listing 3•11 On-parameter approximation in Lagrange multiplier method for beam bending problem
(project: “lagrange_multiplier”).

For one-parameter approximation matrix M is only 3 × 3, which is not too complicated. It can be accessed
with plain C or Fortran semantics using the selector “[]”. Two alternative approaches are available in Vector-
Space C++ Library. First we can build M just as it is written in matrix form using concatenation operator “|” and
“&” such as

C0 M = ( ( (E_*I_*(d(psi)*d(psi)))| null | (((H0)psi)*lambda) )&


( null | null | (d(w)*lambda) )&
( (((H0)psi)*lambda) | (d(w)*lambda) | null ) ) | J(L_),
b = ( C0(-L_*M_) &
( (((H0)w)*f_) | J(L_) ) &
C0(0.0) );

Workbook of Applications in VectorSpace C++ Library 211


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
The expression is to mimic directly the image of the right-hand-side matrix and the left-hand-side vector of Eq.
3•80. Or, we can use basis expression closer to linear algebra as

C0 e(3),
M = ( (E_*I_*(d(psi)*d(psi))) *(e[0]%e[0])+
0.0 *(e[0]%e[1])+
(((H0)psi)*lambda) *(e[0]%e[2])+
0.0*(e[1]%e[0])+ 0.0 *(e[1]%e[1])+
(d(w)*lambda) *(e[1]%e[2])+
(((H0)psi)*lambda) *(e[2]%e[0])+
(d(w)*lambda) *(e[2]%e[1])+
0.0 *(e[2]%e[2])
) | J(L_),
b = (-L_*M_) *e[0] +
((((H0)w)*f_) | J(L_)) *e[1] +
0.0 *e[2];

This code simulates the image of

dφ 0ψ dφ 0ψ
L L
M= ∫ ---------
EI
dx dx
- ---------- dx ( e 0 ⊗ e 0 ) + 0 ( e 0 ⊗ e 1 ) + ∫ φ 0ψ φ 0λ dx ( e 0 ⊗ e 2 ) +
0 0
L
dφ iw
0 ( e1 ⊗ e0 ) + 0 ( e 1 ⊗ e 1 ) + ∫ ---------- φ jλ dx ( e 1 ⊗ e 2 ) +
dx
0
L L
dφ 0w
∫ φ0λ φ0ψ dx ( e 2 ⊗ e0 ) + ∫ φ 0λ ---------- dx ( e 2
dx
⊗ e1 ) + 0 ( e2 ⊗ e2 )
0 0
and,
L

b= – ( φ 0ψ M ) e 0 + ∫ φ 0w f dx e 1 + 0 e 2
L
0
The results of one-parameter approximation are shown in Figure 3•17. There is room for improvements obvi-
ously.
Program Listing 3•12 implements the two-parameter approximation.
ψ w λ
0.6 1
-0.2
0.2 0.4 0.6 0.8 1 0.5 0.8 exact
-0.4 0.4 0.6
-0.6 exact 0.3
0.4
-0.8 0.2 exact
-1 0.1 0.2
-1.2
0.2 0.4 0.6 0.8 1 0.2 0.4 0.6 0.8 1
Figure 3•17 Comparison of one-parameter solution to the exact solution.

212 Workbook of Applications in VectorSpace C++ Library


Variational Methods
T
φ = φ ψ φw φ λ , φψ = x x2 , φ w = x x2 , φ λ = 1 x
T T T
Eq. 3•83

For more than one-parameter approximation, the complexities for the formulation increase dramatically. Subma-
trix/Subvector become an important tool to deal with the complexities. We used in Program Listing 3•12, the ref-
erence Integrable_Matrix “mi j”, where i, j = 0, 1, 2. Each “mi j” is a matrix of size 2x2.
#include "include\vs.h" Simpson’s rule {1/3,4/3,1/3}T
int main() {
double L_ = 1.0, E_ = 1.0, I_ = 1.0, f_ = 1.0, M_ = 1.0, weight[3] = {1.0/3.0, 4.0/3.0, 1.0/3.0};
Quadrature qp(weight, 0.0, L_, 3); φ0ψ φ 1ψ φ0w φ 1w φ 0λ φ 1λ = x x 2 x x 2 1 x
H1 x(qp),
psi = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 2, 1, qp), L
w = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 2, 1, qp), dφ ψ dφ ψ
lambda=INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature",2,1,qp);
psi[0] = x; psi[1] = x.pow(2);
M00= ∫ EI --------- ⊗ --------- dx
dx dx
0
w[0] = x; w[1] = x.pow(2); L
lambda[0] = 1.0; lambda[1] = x; ψ λ
H0 m(6, 6, (double*)0, qp), ML02
λ
=
ψ ∫φ ⊗ φ dx , M20
m00(2, 2, m, 0, 0, qp), m01(2, 2, m, 0, 2, qp), m02 (2, 2, m, 0, 4, qp), = ∫ φ ⊗ φ dx 0
m10(2, 2, m, 2, 0, qp), m11(2, 2, m, 2, 2, qp), m12(2, 2, m, 2, 4, qp),
m20(2, 2, m, 4, 0, qp), m21(2, 2, m, 4, 2, qp), m22(2, 2, m, 4, 4, qp); 0
m00 = (E_*I_)*d(psi)*(~d(psi)); m01 = 0.0; m02 = ((H0)psi)%((H0)lambda); L L
w
λ λ dφ w
M12= ∫ ---------- ⊗φ dx , M21= ∫ φ ⊗---------
m10 = 0.0; m11 = 0.0; m12 = d(w)(0)%((H0)lambda); dφ -dx
m20 = ((H0)lambda)%((H0)psi); m21 = ((H0)lambda)%d(w)(0); m22 = 0.0; dx dx
C0 M = m | J(L_/2); 0 0
T
H0 f(6, (double*)0, qp), f0(2, f, 0, qp), f1(2, f, 2, qp), f2(2, f, 4, qp);
f1 = (((H0)w)*f_); f0 = f2 = 0.0; L 
( – Mφ ψ ( L ) )  ∫ φ w fdx 0
b=
C0 b = f | J(L_/2);
 
b[0] = -M_*L_;b[1] = -M_*pow(L_, 2); 0 
C0 c = b / M;
for(int i = 0; i < 6; i++) cout << c[i] << ", "; cout << endl;
return 0;
}

Listing 3•12 Two-parameters approximation in Lagrange multiplier method for beam bending problem
(project: “lagrange_multiplier”).

Three alternative implementions of the two-parameter approximation in VectorSpace C++ Library are possi-
ble. First, concatenation operators can be used to patch smaller matrices and vectors into a larger one, such as

H0 null = INTEGRABLE_MATRIX("int, int, Quadrature", 2, 2, qp); null = 0.0;


C0 M =(( (E_*I_*(d(psi)*(~d(psi)))) | null | (((H0)psi)%((H0)lambda)) )&
( null | null | (d(w)(0)%((H0)lambda)) )&
( (((H0)lambda)%((H0)psi)) | (((H0)lambda)%d(w)(0)) | null )
) | J(L_/2);
C0 M_delta_psi(2, (double*)0), zero(2, (double*)0);
M_delta_psi[0] = -M_*L_; M_delta_psi[1] = -M_*L_*L_; zero = 0.0;
C0 b = ( M_delta_psi & ( (((H0)w)*f_) | J(L_/2)) & zero);

Workbook of Applications in VectorSpace C++ Library 213


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
This code is hardly different from the one-parameter version, although each “term” is now an Integrable_Matrix
instead of an Integrable_Scalar. Secondly, the two-parameter approximation for the linear algebra version can be
written as

C0 e(2), E(3),
M =+( ((E_*I_)*(d(psi)*(~d(psi)))) *((e%e)*(E[0]%E[0]))+
0.0 *((e%e)*(E[0]%E[1]))+
(((H0)psi)%((H0)lambda)) *((e%e)*(E[0]%E[2]))+
0.0 *((e%e)*(E[1]%E[0]))+
0.0 *((e%e)*(E[1]%E[1]))+
(d(w)(0)%((H0)lambda)) *((e%e)*(E[1]%E[2]))+
(((H0)lambda)%((H0)psi)) *((e%e)*(E[2]%E[0]))+
(((H00)lambda)%d(w)(0)) *((e%e)*(E[2]%E[1]))+
0.0 *((e%e)*(E[2]%E[2]))
) | J(L_/2);
C0 M_delta_psi(2, (double*)0); M_delta_psi[0] = -M_*L_; M_delta_psi[1] = -M_*L_*L_;
C0 b = +( M_delta_psi *(e*E[0]) +
( (((H0)w)*f_) | J(L_/2) ) *(e*E[1]) +
0.0 *(e*E[2]) );

The expressions for using Integrable_Submatrix or Integrable_Subvector in two-parameter case are very close to
those of the one-parameter approximation case. The fact that they are now matrix of size 2x2 and vector of size
2x1 instead of a scalar component (comparing to one-parameter case discussed on page 212) is handled by pro-
jecting these matrices and vectors to “((e%e)*(E[i]%E[j]))” or “(e*E[i]) )” according to basis expression intro-
duced in Section 1.1.6. We call your attention to the last operations in the construction of both “M” and “b”.
They use unary “+” operator. This is the primary casting which down casts Subvectors and Submatrices into
Vectors and Matrices, respectively.
Basis expression generates Integrable_Subvector and Integrable_Submatrix. Third, a complementary expres-
sion using the objects of Integrable_Subvector and Integrable_Submatrix directly is

H0 m(6, 6, (double*)0, qp),


ms(2, 2, m);
ms(0,0) = E_*I_*(d(psi)*(~d(psi))); ms(0,1) = 0.0; ms(0,2) = ((H0)psi)%((H0)lambda);
ms(1,0) = 0.0; ms(1,1) = 0.0; ms(1,2) = d(w)(0)%((H0)lambda);
ms(2,0) = ((H0)lambda)%((H0)psi); ms(2,1) = ((H0)lambda)%d(w)(0); ms(2,2) = 0.0;
H0 f(6, (double*)0, qp), fs(2, f);
fs(1) = (((H0)w)*f_); fs(0) = fs(2) = 0.0;
C0 M = m | J(L_/2),
b = f | J(L_/2);
b[0] = -M_*L_; b[1] = -M_*pow(L_, 2);

The results of two-parameter approximation are shown in Figure 3•18. Compared with Figure 3•17 the approxi-
mations for ψ and w are almost equal to the exact solution, and the approximation for λ is identical to the exact
solution.

214 Workbook of Applications in VectorSpace C++ Library


Variational Methods

ψ w
λ
0.6 1
-0.2 0.20.40.60.8 1 0.5 exact ~
~ approx.
0.8 exact = approx.
-0.4 0.4 0.6
-0.6 0.3
0.2 0.4
-0.8 exact ~~ approx. 0.2
-1 0.1
0.20.40.60.8 1 0.20.40.60.8 1
Figure 3•18 Two-parameter approximation using Lagrange multiplier formulation for the beam
bending problem.

3. Penalty Function Formulation: We refer to Chapter 2, Eq. 2•61 on page 153 for the basics of the penalty
method. Taking J(ψ, w) in Eq. 3•75 with a quadratic penalty term, we have

L L
EI  dψ 2 ρ dw 2
J p ( ψ, w ) = ∫ ------ – fw dx + ( ψM ) + --- ∫  ψ +  dx Eq. 3•84
2 dx  2  dx 
0 L 0

where ρ is the penalty parameter. Setting δJp = 0,

L L

∫  EI ---------
- ------- – δwf dx + ( Mδψ ) + ρ ∫  δψ + -----------  ψ + ------- dx = 0
dδψ dψ dδw dw
 
Eq. 3•85
dx dx dx   dx 
0 L 0

Using approximation basis functions as in the Rayleigh-Ritz method this equation can be written in matrix form
as

L L
dφ iψ dφ jψ dφ jw
 EI --------- ψ φ ψ dx ρ φ ψ --------- – M φiψ
∫  dx dx i j - ---------
- + φ ∫ i dx- dx c jψ L
0 0 = L Eq. 3•86
∫ φiw fdx
L L
dφ iw dφ iw dφ jw c jw
ρ∫ ---------- φ jψ dx ρ ∫ ---------- ---------- dx
dx dx dx 0
0 0

Workbook of Applications in VectorSpace C++ Library 215


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
We label Eq. 3•86 as “Mc = b”. We may choose approximation basis functions as

ψ w T T
φ = φ i φ i , where φ iψ = x, and φ iw = x x
2
Eq. 3•87

Program Listing 3•13 implements the penalty function formulation as in Eq. 3•86 and approximation basis
functions as in Eq. 3•87.
2 T
φ ψ = x, and φ w = x x
#include "include\vs.h"
int main() {
double L_ = 1.0, E_ = 1.0, I_ = 1.0, f_ = 1.0, M_ = 1.0,
L
rho = 1.0, weight[3] = {1.0/3.0, 4.0/3.0, 1.0/3.0}; dφ 0ψ dφ 0ψ
Quadrature qp(weight, 0.0, L_, 3);
C0 c, delta_c, c_cache(3, (double*)0);
M00= ∫ EI ---------- ---------- dx
dx dx
do { 0
H1 x(qp), psi = x, L
w w
w = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", [M01, M02] = ρ ∫ φ 0ψ dφ 0 dφ 1 dx ,
---------
- ----------
2, 1, qp); dx dx
w[0] = x; w[1] = x.pow(2); 0
H0 m = INTEGRABLE_MATRIX("int, int, Quadrature", 3, 3, qp);
m[0][0] = E_*I_*d(psi)*d(psi)+rho*((H0)psi)*((H0)psi);
m[0][1] = m[1][0] = rho*((H0)psi)*d(w[0]);  dφ w 
L 0 
m[0][2] = m[2][0] = rho*((H0)psi)*d(w[1]); M 10 ---------
 dx ψ -
m[1][1] = rho*d(w[0])*d(w[0]); m[1][2] = rho*d(w[0])*d(w[1]); = ρ∫  φ 0  dx
m[2][1] = rho*d(w[1])*d(w[0]); m[2][2] = rho*d(w[1])*d(w[1]); M 20  dφ 1w 
C0 M = m | J(L_/2.0); 0  ---------
- 
C0b(3, (double*)0);  dx 
b[0] = -M_*L_; b[1] = (((H0)w[0])*f_) | J(L_/2.0); b[2] = (((H0)w[1])*f_) | J(L_/2.0);
c &= b / M; L
M11 M 12 dφ w dφ w
= ρ ∫ ---------- ⊗ ---------- dx
delta_c &= c_cache - c;
c_cache = c; dx dx
rho *= 5.0;
M21 M 22
0
} while((double)norm(delta_c) > 1.e-6); T
cout << c << endl; L 
( – Mφ 0 ( L ) )  ∫ φ 0 φ 1 fdx
return 0; b= ψ w w
}  
0 

Listing 3•13 Penalty function formulation for beam bending problem (project: “penalty_function”).

Notice that the penalty functional Jp can be written in quadratic form Jp(c) = cTMc-cT b where M is a sym-
metrical positive definitive. δJp = 0 gives “Mc = b”. Therefore, the solution is c = b / M. Alternatively, we con-
sider that Newton’s method for the minimization of Jp(x) should be achieved in just one iteration for a quadratic
functional

c = - Jp,c / Jp,c c = b / M Eq. 3•88

Applied Newton’s formula gives the same result. Therefore, Eq. 3•86 can be used to replace the inner loop of the
Newton’s iteration in Program Listing 2•20 for the penalty method in Chapter 2.

216 Workbook of Applications in VectorSpace C++ Library


Variational Methods
The results of the penalty function formulation are shown in Figure 3•19. Both the ψ and w approximation
are significantly different from the exact solution, considering the exact solution for y is spanned by [x, x2, x3] (in
Eq. 3•79), and the exact solution for w is spanned by [x2, x3, x4] (in Eq. 3•68).
ψ w

0.6
0.2 0.4 0.6 0.8 1
-0.2 0.5
0.4 exact
-0.4
-0.6 0.3
-0.8 exact 0.2
-1 0.1

0.2 0.4 0.6 0.8 1


Figure 3•19 Penalty formulation for beam bending problem.

4. Mixed Formulation: The irreducible formulation is based on a fourth-order differential equation (Eq. 3•66),
which is obtained from substituting M in Eq. 3•65 into Eq. 3•64. The mixed formulation is based on the two sep-
arated equations, Eq. 3•64 and Eq. 3•65, directly. That is

2 2
dw M dM
= ---------, and = f Eq. 3•89
dx2 2EI d x2

These equations are subject to the boundary conditions of w(0) = 0, and M(L) = M. The Lagrangian functional
corresponding to the above equations is

L
M2
J M ( w, M ) = ∫  d x d x 2EI- + fw dx
 dw dM + -------- Eq. 3•90
0

Setting δJM = 0, we can obtain Euler-Lagrange equations as

L
∫  ----------
- -------- + δwf dx
dδw dM
= 0
dx dx 
0
L
( δM )M
∫  ----------
- ------- + ------------------ dx
dδM dw
= 0 Eq. 3•91
dx dx EI 
0

Workbook of Applications in VectorSpace C++ Library 217


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
Using Eq. 3•65, the exact solution for the bending moment M can be obtained by differentiating Eq. 3•68 as

Mexact(x) = (f/2) (x-L)2 +M

The approximated solutions for w and M, which satisfies the boundary condtions w(0) = 0 and M(L) = M, are
taken as

w ≈ c 0w x + c 1w x 2, and M ≈ M + c 0M ( L – x ) + c 1M ( L – x ) 2

Substituting w and M in Eq. 3•91, we can write Eq. 3•91 in matrix form as

L L
dφ w dφ M
0 ∫ ---------
dx
- ⊗ ---------- dx
dx cw
– ∫ φ w fdx
0 = 0 Eq. 3•92
L L L
cM φMM
dφ M dφ w φ ⊗ φ dx
∫ ---------
- ⊗ ---------- dx ∫ --------------------- – ∫ ------------ dx
M M
dx dx - EI
EI
0 0 0

where φw = [x, x2], and φM = [(L-x), (L-x)2]. The second term in the right-hand-side vector has the bending
moment boundary condition M(L) = M, which is shifted to the right-hand-side of Eq. 3•92 from the second term
in the second equation of the left-hand-side of Eq. 3•91. Program Listing 3•14 implements Eq. 3•92. The results
of the mixed formulation are shown in Figure 3•20.

#include "include\vs.h" L
dφ w dφ M
∫ ---------
int main() {
double L_ = 1.0, E_ = 1.0, I_ = 1.0, f_ = 1.0, M_ = 1.0, M01 = - ⊗ ---------- dx
dx dx
weight[5] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 14.0/45.0}; 0
Quadrature qp(weight, 0.0, L_, 5.0); L
dφ M dφ w
∫ ---------
H1 x(qp),
w = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 2, 1, qp), M10 = - ⊗ ---------- dx
dx dx
M = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 2, 1, qp); 0
w[0] = x; w[1] = x.pow(2); L
φ ⊗ φ dx
∫ ---------------------
M[0] = x-L_; M[1] = (x-L_).pow(2); M M
C0 e(2), E(2), M11 = -
m =+( 0.0 *((e%e)*(E[0]%E[0])) +
EI
0
(d(w)*(~d(M))) *((e%e)*(E[0]%E[1]))+
(d(M)*(~d(w))) *((e%e)*(E[1]%E[0])) + L
( ((H0)M)*(~((H0)M))/(E_*I_) )*((e%e)*(E[1]%E[1])) ) | J(L_/4), – ∫ φ w fdx
b = +( ( (-(H0)w)*f_ ) *(e*E[0])+
( (-(H0)M)*(M_/(E_*I_)) ) *(e*E[1]) ) | J(L_/4); b= 0
c = b / m;
φ M
L M
– ∫ ------------ dx
cout << c << endl;
return 0; EI
} 0

Listing 3•14 Mixed formulation for beam bending problem (project: “mixed_formulation”).

218 Workbook of Applications in VectorSpace C++ Library


Variational Methods

w M

0.6 exact ~~ approx.


1.5
exact = approx.
0.5 1.4
0.4 1.3
0.3
1.2
0.2
0.1 1.1
0.2 0.4 0.6 0.8 1 0.2 0.4 0.6 0.8 1
Figure 3•20 Approximated transeverse deflection “w” and bending moment “M” using mixed
formulation for the beam bending problem.

Green Function Solution: We now re-visit the solution by integral equation using Green function to solve the
same fourth-order differential equation and boundary conditions posed in Eq. 3•65 and Eq. 3•66. For simplicity
we set all material constant as 1 and this simplifies the problem to have a Green function that satisfies

4
dg
= δ ( x – ξ ), 0 < x, ξ < 1 ;g ( 0, ξ ) = g’( 0, ξ ) = g’’’( 1, ξ ) = 0, g’’( 1, ξ ) = 1 Eq. 3•93
dx4

where δ( . ) is the Dirac delta function. The jump condition of the shear force (V) due to the concentrated load at
ξ is

V(ξ+)-V(ξ-) = -1

That results in the jump condition g’’’( ξ + , ξ ) – g’’’( ξ– , ξ ) = 1 . Therefore, the equation and conditions for
solving the Green function is
4
dg
1. = 0, 0 < x < ξ, ξ < x < 1
dx4
2. ( 0, ξ ) = g’( 0, ξ ) = g’’’( 1, ξ ) = 0, g’’( 1, ξ ) = 1
3. g, g’, g’’ continuous at x = ξ ; g’’’( ξ + , ξ ) – g’’’( ξ– , ξ ) = 1

Workbook of Applications in VectorSpace C++ Library 219


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
From conditions 1. and 2. we can determine that the Green function is of the form

For x < ξ, g1= Ax2 + B x3 (from g4 = 0 and g ( 0, ξ ) = g’( 0, ξ ) = 0 )


For x > ξ , g2 =a + b (1-x) + c (1-x)2 (from g4 = 0 and g’’’( 1, ξ ) = 0 )

We can determine c = 1/2 by using g’’( 1, ξ ) = 1 . Applying the jump condition g’’’( ξ + , ξ ) – g’’’( ξ– , ξ ) = 1 on x
= ξ we have

g1= Ax2 - (1/6) x3 , and g2 =a + b (1-x) + (1/2)(1-x)2

That is B = -1/6. Consequently, the continuity of g’’ at ξ gives A = (1+ξ)/2, and the continuity of g’ gives b = - 1
− ξ2/2. Then, the continuity of g gives a = 1/2 +(1/2) ξ2 −(1/6) ξ3. Therefore,

g1= ((1+ξ)/2) x2 - (1/6) x3; for x < ξ


g2 = (1/2 +(1/2) ξ2 −(1/6) ξ3) - (1+ξ2/2) (1-x) + (1/2) (1-x)2; for x > ξ Eq. 3•94

Program Listing 3•15 implements the Green function defined in Eq. 3•94, the transverse loading f(x) is taken as
a constant “1.0”. The results of eleven-points values are shown in Figure 3•21 for compared with the exact solu-
tion.

#include "include/vs.h"
int main() {
double x = 0.0,
wt[17] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0 64.0/45.0, 24.0/45.0,
64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0,
24.0/45.0, 64.0/45.0, 14.0/45.0};
for(int i = 0; i < 11; i++) { f(x) = 1, a constant
Quadrature q1(wt, 0.0, x, 17), q2(wt, x, 1.0, 17);
double c = 1.0/2.0, B = -1.0/6.0; c = 1/2, B = - 1/6
H0 z1(q1), z2(q2);
H0 A = (1.0 + z2) / 2.0, A = (1+ξ)/2
a = 1.0/2.0+1.0/2.0*z1.pow(2)-1.0/6.0*z1.pow(3), a = 1/2 +(1/2)ξ2 −(1/6)ξ3
b = -1.0 - 1.0/2.0*z1.pow(2), b = - 1 − ξ2/2
integrand1 = a + b*(1.0-x)+ c*pow((1.0-x), 2),
integrand2 = A*pow(x, 2) + B*pow(x, 3); g2 =a + b (1-x) + c (1-x)2 (where ξ<x)
C0 integal_1, integal_2; g1= Ax2 + B x3 (where ξ>x)
if(i != 0) integal_1 &= integrand1 | J(x / 16.0); else integal_1 &= C0(0.0); x 1

∫ g2 dξ + ∫
if(i != 10) integal_2 &= integrand2 | J((1.0-x) / 16.0); else integal_2 &= C0(0.0);
double w = (double) (integal_1 + integal_2); w(x) = g 1 dξ
cout << "w(" << x << "): " << w << endl; 0 x
if(i != 10) x += 0.1;
}
return 0;
}

Listing 3•15 Integral equation solution using Green function for beam bending problem (project:
“green_function”).

220 Workbook of Applications in VectorSpace C++ Library


Variational Methods

0.6

0.5

0.4

w 0.3

0.2

0.1

0.2 0.4 0.6 0.8 1

Figure 3•21 Point-values are integral equation solution using the Green function compared
with the curve of the exact solution.

Workbook of Applications in VectorSpace C++ Library 221


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
Poisson Equation
The two sub-sections on second-order differential equation and the fourth-order differential equation dis-
cussed above are ordinary differential equations with only one dimension. We now deal with the Poisson equa-
tion which is a partial differetial equation with dimensions greater than one.
Consider the Poisson equation defined in an unit square region with homogenous essential or natural bound-
ary conditions

–∇ 2 u = f Eq. 3•95

Denote the square region as Ω and its boundary as Γ. Multiply the left-hand-side with the variation “v” and inte-
grate over the square region Ω.

– ∫ v∇ 2 udV = ∫ –[ ∇ • ( v∇u ) – ∇v • ∇u ]dV = ∫ ∇v • ∇udV – °∫ v∇u • nds = ∫ ∇v • ∇udV Eq. 3•96


Ω Ω Ω Γ Ω

The first identity uses the integration by parts and the second identity uses divergence theorem of Gauss, where
n is the surface unit normal vector. For homogeneous boundary conditions the boundary integral term vanishes.
We investigate three sets of boundary conditions

1. Dirichlet boundary conditions: u = 0 on all sides,


2. Neumann boundary conditions: ∂u ⁄ ∂n = 0 on all sides,
3. Mixed boundary conditions: u = 0 on x0 = 1 and x1 = 1; ∂u ⁄ ∂n = 0 on x0 = 0 and x1 = 0.

The matrix form for the Eq. 3•96 in this particular 2-D settings can be re-written as
11 11
∂φ i ∂φ j ∂φ i ∂φ j
Mij = ∫ ∫  -------- -------- + -------- -------- dx 0 dx 1, b i = ∫ ∫ φi fdx 0 dx1 Eq. 3•97
 ∂x 0 ∂x 0 ∂x 1 ∂x 1
00 00

where bi is the right-hand-side vector.

Dirichlet Boundary Conditions : the basis functions to satisfy these conditions are

φi = φmn = sin((m+1)πx0) sin((n+1)πx1) ; m, n = 0, 2, ..., N-1, and i = m × N + n. Eq. 3•98

Program Listing 3•16 implements Eq. 3•97 with basis functions in Eq. 3•98, where N = 3 and f = 1. Notice that
the 2-D integration are obtained by forming a 2-D array of weightings. The Jacobian of this problem is constant
throughout the whole integration domain. Figure 3•22 shows the results obtained from Program Listing 3•16.

222 Workbook of Applications in VectorSpace C++ Library


Variational Methods

#include "include\vs.h"
#define PI 3.141597
int main() {
double f_0 = 1.0, weight[5][5],
bode[5] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 14.0/45.0};
for(int i = 0; i < 5; i++)
for(int j = 0; j < 5; j++) weight[i][j] = bode[i] * bode[j]; 2-D weightings
Quadrature qp(weight[0], 0.0, 1.0, 5, 0.0, 1.0, 5);
J d_a(pow( (1.0/4.0), 2.0)); 2-D Jacobian
H1 x(2, (double*)0, qp), phi = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature",9, 2, qp);
for(int m = 0; m < 3; m++) φi = φmn = sin(mπx0) sin(nπx1)
for(int n = 0; n < 3; n++) phi[m*3+n] = sin((m+1.0)*PI*x[0])*sin((n+1.0)*PI*x[1]); m, n = 0, 2, ..., N-1, and i = m × N + n
H0 M_diag(9, (double*)0, qp); 11
for(int i = 0; i < 9; i++) ∂φ i ∂φ i ∂φ i ∂φ i
M_diag[i] = d(phi[i]).pow(2);
C0 M = M_diag | d_a,
M ii = ∫ ∫  -------
- -------- + -------- -------- dx dx
∂x 0 ∂x 0 ∂x 1 ∂x 1 0 1
00
b = ( ((H0)phi) * f_0 ) | d_a, 11
c(9, (double*)0);
for(int i = 0; i < 9; i++) c[i] = b[i] / M[i];
cout << c << endl;
bi = ∫ ∫ φi fdx0 dx1
00
return 0;
}

Listing 3•16 The Poisson equation with the Dirichlet boundary conditions (project: “poisson_dirichlet”).

0.06
6
1
0.04
04
0.02
02 0.8
0 0.6
0
0.2 0.4
0.4
0.6 0.2
0.8
10
Figure 3•22 Solution of Poisson equation with homogeneous Dirichlet boundary conditions.

Neumann Boundary Conditions : the basis functions are

φi = φmn = cos(mπx0) cos(nπx1) ; m, n = 0, 1, 2, ..., N-1, and i = m × N + n Eq. 3•99

For this Neumann boundary condition to be solvable, f can not be a non-zero constant. On physical ground if we
consider the Poisson equation to be for the heat conduction problem, the homogeneous Neumann boundary con-
ditions mean that the square region is to be insulated from its surroundings. The temperature in the region will

Workbook of Applications in VectorSpace C++ Library 223


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
increase with time, since the internal source f is added to the region. For a steady state solution to be possible
with totally insulated condition, we conclude that the N(A*) of the Laplace operator consists of constant func-
tions (with the homogeneous Neuman boundary conditions.) Therefore, the solvability condition, from Eq. 3•50,
requires the orthogonal relation which has the internal source to equal the internal sink; i.e.,

∫ f dV = 0 Eq. 3•100

In this case, we choose f = cos πx which satisfies Eq. 3•100. For the computation we only need to replace the
definition for φi (N = 2) and the source term f = cos πx.

Mixed Boundary Conditions : We choose approximation basis functions to be algebraic polynomials of the
form

φi = (1-x02+i) (1-x12+i); i = 0, 1, 2, ..., N-1 Eq. 3•101

We choose N = 2 and f = 1. The results of the Neumann boundary conditions and the mixed boundary conditions
are shown in Figure 3•23.
Neumann boundary conditions, with f = cos πx Mixed boundary conditions, with f = 1.0

0.1
1 0.3
3
0.05
5 1 0.2
2 1
0 0.8
0.8 0.1
.1
-0.05
05
-0.1
.1 0.6 0 0.6
0 0
0.2 0.4 0.2 0.4
0.4 0.4
0.6 0.2 0.6 0.2
0.8 0.8
10 10
Figure 3•23 Solutions of the Poisson equation with the Neumann and mixed boundary conditions.

Green Function Method : The Green function for Eq. 3•95 with the Dirichlet boundary condtions is1

1
g ( x, y ;ξ, η ) = ∑ --------------------------------
nπ sinh ( nπ )
sin ( nπξ ) sin ( nπx ) { cosh ( nπ ( 1 – ( y + η ) ) ) – cosh ( nπ ( 1 – y – η ))} Eq. 3•102
n=1

1. Problem 9.3.9 in p. 147 of G.F. Carrier, and C.E. Pearson, 1988, “ Partial Differential Equations: Theory and technique”
2nd eds., Academic Press Inc., San Diego, CA.

224 Workbook of Applications in VectorSpace C++ Library


Variational Methods
Program Listing 3•17 (project: “green_poisson”) is the code for Poisson equations with the Dirichlet boundary
conditions using the Green function (Eq. 3•102). The computation approximates the infinite series only up to n =
8. The results of this approximation are shown in Figure 3•24.
#include "include\vs.h"
#define PI 3.141597
H0 cosh(const H0& a) { return (exp(a)+exp(-a))/2.0; }
int main() {
const double f_0 = -1.0,
bode[17] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0,
64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0,
64.0/45.0, 14.0/45.0}, weight[17][17];
for(int i = 0; i < 17; i++) 2-D weightings
for(int j = 0; j < 17; j++) weight[i][j] = bode[i] * bode[j];
Quadrature qp(weight[0], 0.0, 1.0, 17, 0.0, 1.0, 17);
J d_a(pow( (1.0/16.0), 2.0)); 2-D Jacobian
double x0, x1;
H0 z(2, (double*)0, qp), integrand(qp), zai, eta; zai &= z[0]; eta &= z[1];
for(int i = 0; i < 11; i++) {
for(int j = 0; j < 11; j++) { ∞
sin ( nπξ ) sin ( nπx )
x0 = 0.1 * j; x1 = 0.1 * i;
integrand = 0.0;
g ( x, y ;ξ, η ) = ∑ ---------------------------------------------
nπ sinh ( nπ )
for(int n = 1; n <= 8; n++) { n=1
integrand += (f_0/(n*PI*sinh(n*PI))*sin(n*PI*x0)*sin(n*PI*zai)* { cosh ( nπ ( 1 – ( y + η ) ) ) -
(cosh(n*PI*(1.0-x1-eta))-cosh(n*PI*(1.0-sqrt((x1-eta)*(x1-eta))))));
cosh ( nπ ( 1 – y – η ) ) }
}
C0 integal = integrand | d_a;
cout << "u(" << x0 << ", " << x1 << ") = " << ((double)integal) << ", ";
}
cout << endl;
}
return 0;
}

Listing 3•17 The Green function method on the Poisson equation with the Dirichlet boundary conditions.

0.06
6
1
1.0
1.
u 0.04
04
0.02
02
0
0.0
-1 0.5
0
0
0.5 y
1 -1
x 0 0.0
1.0
1

Figure 3•24 Eight-term (n=8) Green function method for the Poisson equation with the Dirichlet
boundary conditions.

Workbook of Applications in VectorSpace C++ Library 225


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
3.3.2 Weighted-Residual Method
For a linear differential equation Au = f, with a self-adjoint operator A, the approximated solution uN is

u ≈ u N = φ̂ + ∑ c i φ i Eq. 3•103
N

where φ̂ is set to the essential boundary conditions of uΓ, and φ i is homogenous on the boundaries. Define the
residual RN of the approximated solution as

R N = Au N – f Eq. 3•104

The residual can be distributed in an over-all manner through-out the whole domain, and then set the integrated
value to be zero, such as

( w, R N ) = ∫ wRN dΩ = 0 Eq. 3•105


where w is the weighting function. Different ways of defining the weighting function lead to different types of
approximation methods. The general class of methods in the form of Eq. 3•105 is known as the weighted-resid-
ual methods.

Point-Collocation Method
The weighting function of the point-collocation method can be expressed using the Dirac delta function that
is

w = δ(x-ξ) Eq. 3•106

For a function f(x), we have

∫ δ ( x – ξ )f ( x ) dx = f(ξ ) Eq. 3•107


Therefore, substituting Eq. 3•106 into Eq. 3•105 gives RN(ξ) = 0. What we have to do is simply pick a number of
collocation points ξi , evaluate their corresponding residuals, and obtain a system of equations by setting these
residual equations to zero.
Considering the example1

2
du
+ u + x = 0, 0<x<1 Eq. 3•108
dx2

1. p. 14 and on in C.A. Brebbia, J.C.F. Telles, and L.C. Wrobel, 1984, “Boundary element techniques: Theory and applica-
tions in engineering”, Springer-Verlag, Berlin, Germany.

226 Workbook of Applications in VectorSpace C++ Library


Variational Methods
subject to boundary conditions u(0) = u(1) = 0. The exact solution to this problem is

sin ( x )
u exact = --------------- – x Eq. 3•109
sin ( 1 )

Two term approixmation basis functions which satisfy this homogenous boundary condition can be taken from
Eq. 3•61 in page 204,

φ0 = x (1-x), and φ1 = x2 (1-x)

That is u2 = c0 φ0 + c1 φ1. Two collocation points are necessary for solving the two unkown coefficients, and they
are taken at ξ0 = 1/4 and ξ1 = 3/4. These two points generate two residual equations in matrix form as

d 2 φ0 ( ξ 0 ) d2 φ1 ( ξ0 )
---------------------- + φ 0 ( ξ 0 ) ---------------------
- + φ1 ( ξ0 )
dx 2 dx 2 c0 –ξ0
= Eq. 3•110
d φ0 ( ξ 1 )
2 d φ1 ( ξ1 )
2 c1 –ξ1
---------------------- + φ 0 ( ξ 1 ) ---------------------
- + φ1 ( ξ1 )
dx 2 dx 2

We can also write Eq. 3•110 as “M c = - b”. This two-point collocation problem is simple enough to be solved by
hand, or you can code it with VectorSpace C++ Library, which has the advantage that it can be extended to higher
number of basis functions and collocation points, and the matrix solution procedure becomes inevitable (see
project: “point_collocation”)

double x[2] = {0.25, 0.75}; // ξ0 = 1/4, ξ1 = 3/4


C0 M(2, 2, (double*)0), b(2, x);
for(int i = 0; i < 2; i++) {
C2 z(x[i]),
phi = VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(“int, int”, 2, 1);
phi[0] = z*(1-z); phi[1] = z.pow(2)*(1-z); // φ0 = x (1-x), φ1 = x2 (1-x)
M[i] = (+dd(phi))(0)+((C0)phi); // d2φ/dx2 + φ
}
C0 c = -b / M;
cout << c << endl; // c = {0.189841, 0.172043}T

For a Vector_of_Tangent_of_Tangent_Bundle object with spatial dimension = 1, we need a forced degeneration


operation for its Hessain, “(+dd(phi))(0)”. The first operation has the primary casting operator “+” casting a
Nominal_Submatrix to a Matrix, and then a column selector “( )” makes this Matrix into a Vector. The approxi-
mated two-point-collocation solution is u2 = 0.189841 x (1-x) + 0.172043 x2 (1-x). This result is plotted against
the exact solution “uexact = sin(x)/sin(1) -x” in Figure 3•25. There are no significant differences among the two
from the left-hand-side of Figure 3•25.

Workbook of Applications in VectorSpace C++ Library 227


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

Solution - Exact
0.07

0.06
0.2 0.4 0.6 0.8 1
0.05
-0.0002
0.04

0.03 -0.0004
0.02
-0.0006
0.01

-0.0008
0.2 0.4 0.6 0.8 1

Figure 3•25 Two-points collocation solution and error comparing to the exact solution.

We can investigate further on the relationships of the point-collocation method and the finite difference
method. Considering the domain (“cell”) has three equally spaced points xi-1, xi, and xi+1, the approximating
function u correspoinding to these three points are given as ui-1, ui, and ui+1. The value of u in between two con-
sequtive points can be interpolated with a set of quadratic interpolation functions as

u = ui-1 φ1 + ui φ2 + ui+1 φ3 Eq. 3•111

where

φ1 = ξ (ξ-1)/2, φ2 = (1-ξ) (1+ξ), and φ3 = ξ (1+ξ)/2, where -1 < ξ < 1 Eq. 3•112

Considering the cell length = 2h for the three equally spaced points, we have a constant Jacobian for the coordi-
nate transformation rule through-out the whole cell as dξ/dx = 1/h. With a point-collocation on ξ = 0, the first
derivative of u with respect to x is

1
du/ dx = (du/ dξ) (dξ/dx) = [(ξ-1/2)ui-1 + 2ξui + (ξ+1/2)ui+1] 1--- = ------ (ui+1-ui-1) Eq. 3•113
h 2h
ξ=0

Notice that the point-collocation is taken at ξ = 0. Eq. 3•113 is the central difference formula. We can check that
point-collocation at ξ = -1/2 and ξ = 1/2 will yield the backward difference and the forward difference formula,
respectively. The second derivative of u can be derived accordingly as

1
d2u/ dx2 = (d2u/ dξ2) (dξ/dx)2 =----2- (ui-1-2 ui + ui+1) Eq. 3•114
h

This finite difference formula for the second derivative is independent of ξ; i.e., the position of collocation point.

228 Workbook of Applications in VectorSpace C++ Library


Variational Methods
Subdomain-Collocation Method
The weighting function for the subdomain-collocation method is taken as a step function defined, for exam-
ple, as


 1, ξ1 < x < ξ 2
w =  Eq. 3•115
 0, – 1 < x < ξ1 or ξ 2 < x < 1

where x, ξ1, and ξ2 can be all defined in the interval of (-1, 1). For a function f(x), we have

1 ξ2

∫ wf ( x )dx = ∫ f ( x )dx Eq. 3•116


–1 ξ1

That is the domain of integration is now restricted to the subdomain bounded by [ξ1, ξ2]. Substituting the weight-
ing function of Eq. 3•115 in the weighted-residual statement of Eq. 3•105 gives

ξ2

∫ RN ( x )dx = 0 Eq. 3•117


ξ1

The subdomain-collocation method evaluates a number of subdomains bounded by different sets of [ξ1, ξ2] in
Eq. 3•117, for solving the coefficient vector c.
Considering the same example in the point-collocation case with the same basis functions and two subdo-
mains Ω1 = (-1, 0), and Ω2 = (0, 1), we then have

d2 φ0 d 2 φ1
∫ ----------- + φ 0 dx
dx 2 ∫ ----------
dx 2
- + φ 1 dx
c0
– ∫ xdx
Ω1 Ω1 Ω1
= Eq. 3•118
d2 φ0 d 2 φ1 c1
– ∫ xdx
∫ ----------- + φ 0 dx
dx 2 ∫ ----------- + φ 1 dx
dx 2
Ω2
Ω2 Ω2

The code which implements Eq. 3•118 with VectorSpace C++ Library is (project: “subdomain_collocation”)

double x[2][2] = {{-1.0, 0.0}, {0.0, 1.0}}, // Ω1 =(-1,0), Ω2 = (0, 1)


w[5] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 14.0/45.0}; // Bode’s integration rule
C0 M(2, 2, (double*)0), b(2, (double*)0);
for(int i = 0; i < 2; i++) {
Quadrature qp(w, x[i][0], x[i][1], 5); // Quadrautre in subdomain Ωi
J d_l((x[i][1]-x[i][0])/4.0);

Workbook of Applications in VectorSpace C++ Library 229


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
H2 z(qp),
phi = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(
“int, int, Quadrature”, 2, 1, qp);
phi[0] = z*(1-z); phi[1] = z.pow(2)*(1-z); // φ0 = x (1-x), φ1 = x2 (1-x)
M[i] = (+dd(phi))(0)+((H0)phi) | d_l; // d2φ/dx2 + φ
b[i] = - ((H0)z) | d_l;
}
C0 c = b / M;
cout << c << endl; // c = {0.181818, 0.181818}T

The coefficient vector is not substantially different from that of the point-collocation case. The results are shown
in Figure 3•26.
Error
0.07
0.00025
0.06
0.2 0.4 0.6 0.8 1
0.05 -0.00025
0.04 -0.0005

0.03 -0.00075
-0.001
0.02
-0.00125
0.01
-0.0015
0.2 0.4 0.6 0.8 1

Figure 3•26 Solution of subdomain collocation and error compared with the exact
solution.

We note that the first derivative of u with respect to x collocated in the interval of (-1/2, 1/2) is

1 1 1
--- --- ---
2 2 2

∫  d ξ  d x dx
du du dξ 1 1 1 1
∫ d x dx = = --- --- ξ ( ξ – 1 )u i – 1 + ( 1 – ξ ) ( 1 + ξ )u i + --- ξ ( 1 + ξ )u i + 1
h 2 2
= ------ (ui+1-ui-1) Eq. 3•119
2h
1 1 1
– --- – --- – ---
2 2 2

This gives the central difference formula. Similarly, subdomains [-1, 0] and [0, -1] give backward difference and
forward difference formula, respectively.

Method of Moment
In statistics, the idea of using moments to descirbe a distribution is taken from mechanics. The expectation of
a distribution can be considered as the center of the gravity. The variance is the second power of distances of the
data with respect to this “center of gravity”. Skewness and kurtosis are defined as the third and fourth power of
the distances, respectively.
For the weighted-residual method, we can choose the weighting function in Eq. 3•105 to be the various
power of coordinate variable x, such as

230 Workbook of Applications in VectorSpace C++ Library


Variational Methods
wi = xi, i = 0, 1, 2, .... Eq. 3•120

That is the residuals (or errors) are distributed through-out the domain with various moments of x. We notice that
the weighting function for the weighted residual method in general is not necessarily required to satisfy the
homogeneous boundary conditions.
Consider the example in the point-collocation and subdomain collocation with the approximation basis func-
tion φ0 = x (1-x), and φ1 = x2 (1-x). We use first two weighting moments w 0 = 1 and w1 = x. This gives

d2φ d2 φ
 ----------0- + φ  dx  ----------1- + φ  dx
∫  dx 2 0 ∫  dx 2 1 c0
– ∫ xdx
Ω Ω = Ω Eq. 3•121
d2φ d2 φ c
 ----------0- + φ  dx  ----------1- + φ  dx 1 – ∫ x 2 dx
∫  dx 2 0
x ∫  dx 2 1
x

Ω Ω

The code which implements Eq. 3•121 with VectorSpace C++ Library is (project: “method_of_moment”)

double w[5] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 14.0/45.0}; // Bode’s integration rule
C0 M(2, 2, (double*)0), b(2, (double*)0);
Quadrature qp(w, 0.0, 1.0, 5);
J d_l(1.0/4.0);
for(int i = 0; i < 2; i++) {
H2 x(qp),
phi = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(
“int, int, Quadrature”, 2, 1, qp);
phi[0] = x*(1-x); phi[1] = x.pow(2)*(1-x); // φ0 = x (1-x), φ1 = x2 (1-x)
M[i] = (((H0)x).pow(i)*(+dd(phi))(0)+((H0)phi)) | d_l; // d2φ/dx2 + φ
b[i] = - ((H0)x).pow(i+1) | d_l;
}
C0 c = b / M;
cout << c << endl; // c = {0.166667, 0.212121}T

The results of this implementation are shown in Figure 3•27.

0.07 Error
0.06 0.001

0.05

0.04 0.2 0.4 0.6 0.8 1

0.03 -0.001

0.02
-0.002
0.01

0.2 0.4 0.6 0.8 1


Figure 3•27 Method of moment solution and its error.

Workbook of Applications in VectorSpace C++ Library 231


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
Galerkin Method: Bubnov-Galerkin / Petrov-Galerkin Methods and Weak Formulation
We have chosen the weighting functions to be a Dirac delta function in point-collocation, a step function in
subdomain collocation, and a set of power of coordinate x for the method of moment. In Galerkin method the
weighting functions are simply the same as the approximation basis functions.That is

wi = φi = x(i+1) (1-x), i = 0, 1, 2, ... Eq. 3•122

Consider the same example we solved starting from the point-collocation method in the above. We have

d2 φj
∫ φi  ---------
dx 2
- + φ j dx

c j = – ∫ φ i xdx Eq. 3•123
Ω Ω

For two term approximation (i = 0, 1), the matrix form can be written as

d2 φ0 d 2 φ1
∫ φ0  ----------
dx 2
- + φ0 dx ∫ φ0  ----------- + φ 1 dx
  dx 2  c0
– ∫ φ 0 xdx
Ω Ω = Ω Eq. 3•124
d2φ d2φ c1
φ1  ---------- + φ0 dx φ1  ---------- + φ 1 dx – ∫ φ 1 xdx
∫ ∫
0 1
- -
 dx 2   dx 2  Ω
Ω Ω

The code implements Eq. 3•124 with VectorSpace C++ Library as (project: “galerkin_method”)

double w[5] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 14.0/45.0}; // Bode’s integration rule
C0 M(2, 2, (double*)0), b(2, (double*)0);
Quadrature qp(w, 0.0, 1.0, 5);
J d_l(1.0/4.0);
for(int i = 0; i < 2; i++) {
H2 x(qp),
phi = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(
“int, int, Quadrature”, 2, 1, qp);
phi[0] = x*(1-x); phi[1] = x.pow(2)*(1-x); // φ0 = x (1-x), φ1 = x2 (1-x)
M[i] = (((H0)phi)[i]*(+dd(phi))(0)+((H0)phi)) | d_l; // d2φ/dx2 + φ
b[i] = - (((H0)phi)[i]*((H0)x)) | d_l;
}
C0 c = b / M;
cout << c << endl; // c = {0.166667, 0.212121}T

The results of the above implementation are shown in Figure 3•27. Notice that although we choose the weight-
ing functions equal to the approximation basis functions; i.e., wi = φi , the left-hand-side matrix M is still not
symmetrical. This is because the operator A(u) = (d2 u/ dx2 + u) is not self-adjoint. .

232 Workbook of Applications in VectorSpace C++ Library


Variational Methods

0.08
Error
0.14

0.12
0.06
0.1

0.08 0.04
0.06
0.02
0.04

0.02
0.2 0.4 0.6 0.8 1
0.2 0.4 0.6 0.8 1

Figure 3•28 Galerkin method solution and its error.

For a positive definite operator A = (T*) T, where T* is the self-adjoint of T (i.e., T*=T), the Galerkin method
with homogeneous boundary conditions gives

(Aφi, φj) = (Tφi, Tφj) = a(φi, φj) Eq. 3•125

= ( φ i, A φj ) Eq. 3•126

where a( . , . ) is a bilinear form which is symmetrical. Therefore, A is self-adjoint ( Eq. 3•126), and the resultant
weak formulation with the Galerkin weighting is symmetrical (Eq. 3•125). In the case of the self-adjoint opera-
tor, the Galerkin method with wi = φi is also known as the Bubnov-Galerkin method. When wi ≠ φi , it is known
as the Petrov-Galerkin method
A second example is the eigenvalue problem of a circular membrance of radius a given by1

– ∇2u = λu Eq. 3•127

with homogeneous essential boundary conditions. This axisymmetric problem can be reduced to a 1-D problem
in polar coordinate r as

– ---  r
1d du
= λu Eq. 3•128
r d r  d r

The approximation basis functions which satisfy the homogeneous boundary conditions are

πr
φ i = cos ( 2 i + 1 ) ------, i = 0, 1, 2, …, N – 1 Eq. 3•129
2a

For the Galerkin method with wi = φi, we have

1. p. 292 in J.N. Reddy, 1986, “Applied functional analysis and variational methods in engineering”, McGraw-Hill, Inc.

Workbook of Applications in VectorSpace C++ Library 233


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
a a
 1 d dφj 
∫ φi  – --r- d r  r d r   ( 2πrdr )cj = ∫ λφi φj ( 2πrdr )cj Eq. 3•130
0 0

Therefore,

2
 a  dφ d φ j  a 
  – φ j – rφ   φ φ rdr c
∫  idr ∫ i j  j
 dr c = λ Eq. 3•131
dr   j
i 2
0  0 

We have a generalized eigenvalue problem of the form

Ax = λ B x Eq. 3•132

We solve Eq. 3•131 for N = 2 and a = 1. First, we notice that the off-diagonals of A is analytically identical since
the Laplace operator is self-adjoint. However, numerically, the off-diagonals of A can be slightly different due to
errors from numerical integration. We can either ignore such differences or symmetrize A numerically by defin-
ing As = (A+AT)/2. Now we can solve the generalized eigenvalue problem by using the inverse of B as

B -1Asx = λ x Eq. 3•133

However, (B -1As) will not be symmetrical. We can not use a symmetrical eigenvalue solver for this problem.
Therefore, we first use Cholesky decomposition for the symmetrical matrix B = LLT. The symmetry of the left-
hand-side can now be preserved by pre-multipling L -1 and post-multipling (L-1)T on Eq. 3•132 to give

(L -1) As ((L-1)T) x = λ x Eq. 3•134

Program Listing 3•18 implements the Galerkin method with Laplace operator in Eq. 3•131, and using Eq. 3•134
to solve the symmetric eigenvalue problem.
The results are shown in Figure 3•29. The surface graphs represent the modes from eigenvectors as a func-
tion of “vi j φ j”, where vi is the i-th eigenvector. The frequencies ωi are computed from eigenvalues λi as

ωi = λi

234 Workbook of Applications in VectorSpace C++ Library


Variational Methods

#include "include\vs.h"
#define PI 3.141592654
int main() {
double a_= 1.0, radius a = 1
weight[25] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0, extended Bode’s integration rule
64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0,
24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0,
64.0/45.0, 24.0/45.0, 64.0/45.0, 14.0/45.0};
Quadrature qp(weight, 0.0, a_, 25);
J d_r(a_/24.0); πr
H2 r((double*)0, qp), φ i = cos ( 2 i + 1 ) ------, i = 0, 1, 2
2a
phi = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 3, 1, qp); a 2
 dφ j d φ j
A= ∫  – φi – rφ i 2  dr
phi[0] = cos((PI/2.0/a_)*r); phi[1] = cos((3.0*PI/2.0/a_)*r); phi[2] = cos(5.0*PI/2.0/a_*r);
H0 d2_phi = INTEGRABLE_VECTOR("int, Quadrature", 3, qp); d r
for(int i = 0; i < 3; i++) d2_phi[i] = dd(phi)(i)[0][0];
 dr 
0
C0 A = -( ( d(phi)(0)%((H0)phi) + d2_phi%((H0)phi)*((H0)r) ) | d_r ), a

∫ φi φj rdr
B = ( ((H0)phi)%((H0)phi) * ((H0)r) ) | d_r;
C0 L = MATRIX("int, int", 3, 3); B=
Cholesky Ch(B); 0
for(int i = 0; i < 3; i++) Cholesky decomposition
for(int j = 0; j < 3; j++)
if(i >= j) L[i][j] = Ch.rep_ptr()[0][i*(1+i)/2+j]; Lower triangular matrix
else L[i][j] = 0.0;
C0 L_inv = L.inverse(), (L -1) As ((L-1)T) x = λ x
C = L_inv*((A+~A)/2.0*(~L_inv)),
lambda = Eigen(C).Eigenvalues(), λ = {5.78529, 30.4889, 75.0492}T
v = Eigen(C).Eigenvectors(); v0 ={0.999174, 0.398374, -0.008019}T
cout << lambda << endl; v1 ={0.0396508,-0.998966,-0.022225}T
cout << v << endl;
return 0; v2={0.00889577,-0.0218887,0.99972}T
}

Listing 3•18 Galerkin method for the eigenvalue problem of a circular membrance (project:
“circular_membrance”).

ω0 =2.40526 ω1 = 5.52168 ω2 = 8.66309

2 2 2

1 1 1 1
1 1
0 0
0
0.5 -1 0.5
-1 0.5 -1
-2 -2
-2 -1 -1 0
-1 0 0
-0.5 -0.5
-0.5
0 -0.5 0 -0.5
0 -0.5
0.5 0.5
0.5
1 -1 1 -1
1 -1

Figure 3•29 Three frequency and mode pairs of the circular membrance eigenvalue problem.

Workbook of Applications in VectorSpace C++ Library 235


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
A third example is a non-linear differential equation 1

2
du 2
u 2 +   = 1,
du
0 < x < 1, with u' ( 0 ) = 0, and u ( 1 ) = 2 Eq. 3•135
dx  d x

The exact solution to this problem is

u exact ( x ) = 1 + x2 Eq. 3•136

This problem can be transformed to one that has homogeneous boundary conditions by setting u = v+ 2 such
that

2
d v  dv  2
(v + 2) + = 1, 0 < x < 1, with v' ( 0 ) = 0, v ( 1 ) = 0 Eq. 3•137
d x 2  d x

This is equivalent to

d dv
(v + 2) = 1, 0 < x < 1, with v' ( 0 ) = 0, v ( 1 ) = 0 Eq. 3•138
dx dx

Therefore, the residuals of the problem is

d dv N
R ( vN ) = ( vN + 2) –1 Eq. 3•139
dx dx

The weighted residuals method with Galerkin weightings, wN = ci ψi , and approximation basis functions, vN = cj
φj , gives

1 1
d dv N 
I ( c ) = c i ∫ ψ i R ( v N ) dx = c i ∫ ψ i  ( vN + 2) – 1 dx = 0 Eq. 3•140
 dx dx 
0 0

where ci are arbitary constants and will eventually be dropped from Eq. 3•140, and cj is the only unknown vector
of the non-linear problem. Integrating by part on the first term gives the weak formulation

1
dψ i dv N
I ( c ) = ci ∫ ( vN + 2 ) – ψ i dx = 0 Eq. 3•141
dx dx
0

An iterative algorithm needs to be employed for this non-linear problem

1. p. 294 in J.N. Reddy, 1986, “Applied functional analysis and variational methods in engineering”, McGraw-Hill, Inc.

236 Workbook of Applications in VectorSpace C++ Library


Variational Methods
∂I
I ( c jk + 1 ) = I ( c jk + δ cjk ) ≅ I ( cjk ) + δ c jk = 0 Eq. 3•142
∂c
cjk

where cjk+1 = cjk + δ cjk. The approximation in this equation is the Taylor expansion to the first-order derivatives.
That is the increment of the solution δ cjk can be solved by

–1
∂I
δ c jk = I ( c jk ) = – [ IT ] –1 I ( c jk ) Eq. 3•143
∂c k
c

where IT the “tangent” of I(c) can be defined as


1
∂I dψ i dv N dφ j
φ
IT = = ci ∫ + ( v + 2 ) dx Eq. 3•144
∂c dx  jdx dx N 
ci 0

Program Listing 3•19 implements the three-parameters approximation of the weak formulation I(c) (Eq. 3•141)
and its tangent IT (Eq. 3•144), then, uses the iterative alogrithm (Eq. 3•143; i.e., Newton’s method) to solve for
the increment of the Ritz coefficients δc. The preprocessing macro “__PETROV_GALERKIN”, if defined at
compile time, the corresponding code segment implements the Petrov-Galerkin method with the approximation
basis functions for weighting as

ψ i = cos  -------------- π , i = 0, 1, 2
2i + 1
 2 
Eq. 3•145

Otherwise, the Bubnov-Galerkin method is assumed and ψi = φi. The approximated solution is

2
u = wN + 2 = c0 ( 1 – x ) + c 1 ( 1 – x ) + c2 ( 1 – x 3 ) + 2 Eq. 3•146

The results of the computation are shown in Figure 3•30. The data speak for themselve without the need for any
elaboration.

Workbook of Applications in VectorSpace C++ Library 237


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

#include "include\vs.h"
#define EPSILON 1.e-12
int main() {
double weight[13] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, extended Bode’s integration rule
24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0,
14.0/45.0};
Quadrature qp(weight, 0.0, 1.0, 13);
J d_l(1.0/12.0);
H1 x(qp),
phi = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 3, 1, qp);
phi[0] = 1.0-x; phi[1] = 1.0-x.pow(2); phi[2] = 1.0-x.pow(3); φi = (1-xi+1), i = 0, 1, 2
#if defined(__PETROV_GALERKIN) Petrov-Galerkin method
#define PI 3.141592654
H1 psi = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( (2 i + 1)
"int, int, Quadrature", 3, 1, qp); ψ i = cos ---------------------- π, i = 0, 1, 2
psi[0] = cos(PI/2.0*x); psi[1] = cos(3.0*PI/2.0*x); psi[2] = cos(5.0*PI/2.0*x);
2
#else
H1 psi; Bubnov-Galerkin method
psi &= phi; ψi = φ i
#endif 1
dψ i dv N

C0 c(3,(double*)0), delta_c(3,(double*)0);
do { I= ( vN + 2 ) – ψ i dx
dx dx
H1 v = c * phi; 0
C0 I = ( d(psi)(0) * ( ((H0)v)+sqrt(2.0) ) * d(v)[0] + ((H0)psi) ) | d_l, 1
dψ i dv N dφj
φ ( v + 2 ) dx

I_t = ( d(psi)(0) % (((H0)phi) * d(v)[0] + d(phi)(0) * ( ((H0)v)+sqrt(2.0) ) ) ) | d_l;
IT= +
delta_c = - I / I_t; dx  jdx dx N 
c += delta_c; 0
cout << c << ", " << norm(delta_c) << endl; δc = - I / IT , and ck+1 = ck + δ ck
} while((double)norm(delta_c ) > EPSILON);
cout << c << endl; Bubnov: c = {0.00539687, -0.547092,
return 0; 0.127479}T, Petrov: c = {0.00884522, -
} 0.554076, 0.131396}T .

Listing 3•19 Galerkin method for a non-linear differential equation (project: “nonlinear_galerkin”).

1.4
u exact ( x ) = 1 + x2 0.0006

1.3 0.0004
Petrov-Galerkin
u Bubnov-Galerkin
1.2 0.0002

Errors
1.1 0.2 0.4 0.6 0.8 1 x

-0.0002
0.2 0.4 0.6 0.8 1 x

Figure 3•30 Solution of non-linear differential equation using Bubnov-Galerkin


and Petrov-Galerkinmethods.

238 Workbook of Applications in VectorSpace C++ Library


Variational Methods
Least Squares Method
The basic idea of the least squares method is introduced in Eq. 1•26 of Chapter 1 on page 35. The minimiza-
tion of the squares of the residual norm is

∂ R N 22 ∂R N
------------------ = 2  RN, ----------  = 0 Eq. 3•147
∂c  ∂c 

The factor “2” can be dropped since the equation equals zero. Comparing Eq. 3•147 with the weighted-residual
statement ( R N, w ) = 0 it shows that the least squares method is a special case of the weighted-residual method
with the weighting function “w” as

∂R N
w = ---------- Eq. 3•148
∂c

For a non-linear problem, we define

∂R N ∂I ∂RN ∂R N ∂ 2 RN
I ( c ) =  R N, ---------- , and I T = =  ----------, ---------- +  R N, -------------  Eq. 3•149
 ∂c  ∂c  ∂c ∂c   ∂c 2 
ci

For the non-linear problem in the last section,

2
d vN dv N 2
RN = ( vN + 2 ) +  –1 Eq. 3•150
dx2 dx 

and the first derivatives of RN is


2 2
∂R N dv dφ dφ dv N
---------- = φ N + (v + 2) + 2 Eq. 3•151
∂c d x2 dx2 N dxdx

The second derivatives is

2 2
∂2 RN dφ dφ dφ dφ
------------- = φ ⊗ + ⊗φ+2 ⊗ Eq. 3•152
∂c 2 dx2 dx2 dx dx

Program Listing 3•20 implements Eq. 3•149 to Eq. 3•152, with three-parameter approximation. Considering that
the exact solution of the problem is an even function we assume the approximation basis functions are “φ0 = (1-
x2), φ1 = (1-x4), and φ2 = (1-x6)”. This set of approximation basis functions are as accurate as if we had used six-
parameter approximation with continuous power of algebraic functions. The errors of this computation are
shown in Figure 3•31. Before we proceed any further, we notice that the combination of the concept of subdo-
main collocation method and weak formulation with Galerkin method provides the foundations of the finite ele-
ment method which we will discuss in details in Chapter 4 and Chapter 5.

Workbook of Applications in VectorSpace C++ Library 239


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

#include "include\vs.h"
#define EPSILON 1.e-12
int main() { extended Bode’s integration rule
double weight[13] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, φ0 = (1-x2), φ1 = (1-x4), and φ2 = (1-x6)
24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 2
d vN dv N 2
+  –1
14.0/45.0};
RN = ( vN + 2 )
Quadrature qp(weight, 0.0, 1.0, 13); dx2 dx 
J d_l(1.0/12.0);
H2 x((double*)0, qp), 2 2
phi = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE( ∂R N d vN d φ dφ dv N
---------- = φ + 2 ( vN + 2 ) + 2
"int, int, Quadrature", 3, 1, qp); ∂c dx 2 dx dxdx
phi[0] = 1.0-x.pow(2); phi[1] = 1.0-x.pow(4); phi[2] = 1.0-x.pow(6);
H0 d2_phi = INTEGRABLE_VECTOR("int, Quadrature", 3, qp); 2 2
for(int i = 0; i < 3; i++) d2_phi[i] = dd(phi)(i)[0][0]; ∂2 RN dφ dφ dφ dφ
------------- = φ ⊗ + ⊗φ+2 ⊗
C0 c(3, (double*)0), delta_c(3, (double*)0); ∂c 2 dx2 dx2 dx dx
do {
H2 w = c * phi;
∂R
I =  R N, ----------
H0 R = d(w)[0].pow(2)+(((H0)w)+sqrt(2.0))*dd(w)[0][0]-1.0, N
dR = 2.0*d(phi)(0)*d(w)[0]+((H0)phi)*dd(w)[0][0]+d2_phi*(((H0)w)+sqrt(2.0)), ∂c
ddR = 2.0*(d(phi)(0)%d(phi)(0))+((H0)phi)%d2_phi+d2_phi%((H0)phi);
C0 I = ( dR*R ) | d_l,
∂R ∂R ∂2R
IT=  ----------, ---------- +  R N, -------------
I_t = ( dR%dR+ddR*R ) | d_l; N N N
delta_c = -I /I_t; ∂c ∂c ∂c 2 
c += delta_c;
cout << c << ", " << norm(delta_c) << endl; δc = - I / IT , and ck+1 = ck + δ ck
} while((double)norm(delta_c ) > EPSILON);
cout << c << endl;
return 0; c= {-0.491866, 0.0970614, -0.018541}T
}

Listing 3•20 Least squares method for a non-linear differential equation (project:
“nonlinear_least_squares”).

0.0008

0.0006
Error
0.0004

0.0002

0.2 0.4 0.6 0.8 1


x

Figure 3•31 The errors of the nonlinear least squares method.

240 Workbook of Applications in VectorSpace C++ Library


Variational Methods
3.3.3 Boundary Solution Methods
Consider the residual of the Poisson equation as in Eq. 3•95; i.e., R N = ∇ 2 u + f , in the context of the
weighted-residual statement,

( R N, w ) = ∫ RN w dΩ = ∫ ( ∇ 2 u + f )w dΩ = 0 Eq. 3•153
Ω Ω

If we integrate by part once, and apply Green’s theorem to transform the volume integral to the surface integral
as

∫ ∇u ∇w + f w dΩ = ∫ ( n • ∇u )w dΓ Eq. 3•154
Ω Γ

when the approximation basis functions for u and w are the same the first term in the left-hand-side of Eq. 3•154
is equivalent to the corresponding term in the Bubnov-Galerkin method for a self-adjoint operator (weak formu-
lation) discussed in Eq. 3•125. When the right-hand-side of Eq. 3•154 is included in the variational statment, Eq.
3•154 is also equivalent to the corresponding term in the Rayleigh-Ritz method in Eq. 3•53. Taking Eq. 3•154
and integrating by parts once more, and applying Green’s theorem to transform volume integral to surface inte-
gral,again, we have

∫ ( ( ∇ 2 w )u + fw ) dΩ = ∫ ( n • ∇w )u dΓ – ∫ ( n • ∇u )w dΓ Eq. 3•155
Ω Γ Γ

An alternative view to the weighted-residual derivation from Eq. 3•153 is possible. By setting f = -∇ 2 u , we iden-
tify, directly, Eq. 3•154 as Green’s first identity, and Eq. 3•155 as Green’s second identity.1

Trefftz Method
In Trefftz method, both u and w in Eq. 3•155 are taken as harmonic functions. By definition, harmonic founc-
tions satisfy the Laplace operator

∇2 u = ∇2w = 0 Eq. 3•156

(i.e., f = 0). We obtain the boundary integral equation as

∫ ( n • ∇w )u dΓ = ∫ ( n • ∇u )w dΓ Eq. 3•157
Γ Γ

Considering the Poisson equation2

1. p. 450 in L.E. Malvern, 1969, “Introduction to the mechanics of a continuous medium”, Prentice-Hall, Englewood Cliffs,
N.J.
2. p. 38 in C.A. Brebbia, J.C.F. Telles, and L.C. Wrobel, 1984, “Boundary element Techniques: Theory and applications in
engineering”, Springer-Verlag, Berlin, Germany.

Workbook of Applications in VectorSpace C++ Library 241


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

–∇ 2 u = f Eq. 3•158

with u = 0 at x = ± 1 , and y = ± 1 . It can be transformed into a Laplace equation with

f
u = – --- (x2+y2) + v Eq. 3•159
4

where v is approximated by a subset of algebraic harmonic basis functions

H0 = 1.0,
H1 = (x2-y2).
H2 = (x4 - 6x2y2 + y4),
H3 = (x6-15x4y2+15x2y4-y6),
H4 = (x8 - 28 x6y2 + 70 x4y4 -28 x2y6 + y8),
... etc.,

with v = ci φi and vΓ ≡ v = (x2+y2) f /4, at the boundaries x = ± 1 , and y = ± 1 . Then, v is taken in place of u and
w in Eq. 3•157. For this problem ∀q ≡ ( n • ∇u ) = 0 ; i.e., total flux vanish on the boundaries, the right-hand-
side of Eq. 3•157 equals zero. Considering the symmetry of the problem, we only need to compute the boundary
at x = 1, and 0 < y < 1.
1 1
∂v  ∂v
∫  -----
∂x 
v dy = ∫  -----
∂x 
v dy Eq. 3•160
0 x=1 0 x=1

Substituting v = ci φi into Eq. 3•160, we have


1 1
∂φ i ∂φ i
∫  ------
∂x
- ⊗ φ i

x=1
dy c = ∫  ------
∂x 
- v
x=1
dy Eq. 3•161
0 0

Eq. 3•161 can be re-written in matrix form as “M c = b” where

1 1
∂φ i ∂φ i
M = ∫  ------
∂x
- ⊗ φ i

x=1
dy, and b = ∫  ------
- v
∂x  x = 1
dy Eq. 3•162
0 0

Due to the symmetry of the problem with respect to x and y, only H0, H2 and H4 out of the list of algebraic har-
monic functions will be taken. In view of the problem at hand, H0 = 1.0 can not be in φi, because the x-deriva-
tives in the matrix M and vector b are both zero. We can remedy this by taking only

φ1 = H2 = (x4 - 6x2y2 + y4) and φ2 = H4 = (x8 - 28 x6y2 + 70 x4y4 -28 x2y6 + y8) Eq. 3•163

242 Workbook of Applications in VectorSpace C++ Library


Variational Methods
v = c1 φ1 + c2 φ2 with u = -v + v + c0 where v = (x2+y2) f /4. Without the solution for a constant term, the bound-
ary integral equation is indetermined up to the constant value c0. After we obtain c1 and c2, c0 can be computed
by requiring

1 1

∫ u dy = 0 = ∫ ( – v + c1 φ1 + c2 φ2 + c0 ) x = 1 dy Eq. 3•164
0 0

considering u = 0 at this boundary. That is


1

c0 = –∫ ( – v + c1 φ1 + c 2 φ2 ) dy Eq. 3•165
x=1
0

Program Listing 3•21 implements Eq. 3•162 with basis functions Eq. 3•163 and integrating constant c0 com-
puted from Eq. 3•165. Alternatively we can use transcendental harmonic functions symmetrized with respect to x
and y as

φ1 = cos(πx/2) cosh(πy/2) + cos(πy/2) cosh(πx/2), and


φ2 = cos(3πx/2) cosh(3πy/2) + cos(3πy/2) cosh(3πx/2)

The macro definition “__TRANSCENDENTAL”, if defined at compile time, turns on the corresponding code
segments.
The first quadrant solution of this problem is shown in Figure 3•32, which is directly comparable to the solu-
tion of the same problem shown in the right-hand side of Figure 3•23. Transcendental harmonic functions give
almost identical results.

u = -v -0.0453175 H2+0.0013438 H4+0.29469

0.3
3
u
0.2
2 1
0.1
.1 0.8
0 0.6
0
0.2 0.4 y
0.4
0.2
x 0.6
0.8
10

Figure 3•32 The first quadrant solution of the Poisson equation using the Trefftz method.

Workbook of Applications in VectorSpace C++ Library 243


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

#include "include\vs.h"int main() {


const double f_ = 1.0; const double PI = 3.141592654;
double weight[13] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, extended Bode’s integration rule
24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 14.0/45.0};
Quadrature qp(weight, 0.0, 1.0, 13);
J d_l(1.0/12.0);
H1 X = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 2, 2, qp),
phi = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( x evaluated at “1”
"int, int, Quadrature", 2, 2, qp),
x, y; x &= X[0]; y &= X[1]; v = f (x2+y2) /4
((H0)x) = 1.0; d(y) = 0.0;
H0 v_bar = f_/4.0 *(((H0)x).pow(2)+((H0)y).pow(2)); φ1, and φ2
#if defined(__TRANSCENDENTAL) 1
H1 cosh(const H1& a) { return (exp(a)+exp(-a))/2.0; } ∂φ i
phi[0] = cos(PI/2.0*x)*cosh(PI/2.0*y) + cos(PI/2.0*y)*cosh(PI/2.0*x);
phi[1] = cos(3.0*PI/2.0*x)*cosh(3.0*PI/2.0*y) + cos(3.0*PI/2.0*y)*cosh(3.0*PI/2.0*x);
M = ∫  ------
∂x
- ⊗ φ i

x=1
dy
0
#else 1
phi[0] = x.pow(4) - 6.0*x.pow(2)*y.pow(2) + y.pow(4); ∂φ i
phi[1] = x.pow(8) -28.0*x.pow(6)*y.pow(2)+70.0*x.pow(4)*y.pow(4)
-28.0*x.pow(2)*y.pow(6)+y.pow(8);
b = ∫  ------
- v
∂x  x = 1
dy
0
#endif 1
H0 dphi_dx = d(phi)(0);
C0 M = dphi_dx % ((H0)phi) | d_l, c0 = –∫ ( – v + c1 φ1 + c2 φ2 ) dy
b = dphi_dx * v_bar | d_l; x=1
0
C0 c = b / M;
cout << c << endl; algebraic: c0 = 0.29469
C0 c_0 = -((- v_bar + ((H0)phi)*c) | d_l); c= {-0.0453175, 0.0013438}T
cout << c_0 << endl; transcendental: c0 = 0.496296
return 0;
} c= {-0.10095, 0.00014436}T

Listing 3•21 Solution of Poisson equation with Trefftz method (project: “trefftz_method”).

244 Workbook of Applications in VectorSpace C++ Library


Variational Methods
Boundary Element Method
If we consider the Poisson equation and set f = - ∇ 2 u , Eq. 3•155 becomes the Green’s second identity

∫ ( ( ∇ 2 w )u – ( ∇2 u ) w ) dΩ = ∫ ( n • ∇w )udΓ – ∫ ( n • ∇u )w dΓ Eq. 3•166


Ω Γ Γ

In the case of heat conduction, we use temperature T in place of u; therefore, we have the heat flow q =
n • ∇u = ∂u ⁄ ∂n , the weighting function w is taken as the fundamental solution (Green’s function) T* in the
boundary element method, which satisfies

– δ ( x, ξ )
∇ 2 T ∗ ( x, ξ ) = -------------------- Eq. 3•167
k

where δ(x, ξ) is the Dirac delta function with x as the sampling point, ξ as the point source location, and k is the
conductivity. In two-dimensional case, the solution is

–1
T ∗ ( x, ξ ) = ---------- ln ( r ) Eq. 3•168
2πk

where k is the conductivity and r is the distance between x and ξ; i.e., r ( x, ξ ) = ( x0 – ξ 0 ) 2 + ( x 1 – ξ1 ) 2 . We


have also taken the thickness of the medium as unity for simplicity. The heat flow q according to Fourier’s law of
conduction is

1
q∗ = – k∇ T ∗ = ----------2- [ ( x 0 – ξ 0 )e 0 + ( x 1 – ξ 1 )e 1 ] Eq. 3•169
2πr

For simplicity, we deal with problems without internal heat source; i.e., f = 0. When f ≠ 0 , domain integral is
required. From programming point of view, the advantage of the boundary element method begins to make con-
cession to the finite element method. Substituting Eq. 3•168 and Eq. 3•169 into Eq. 3•166, we have

– δ ( x, ξ ) n • q∗
∫ -------------------- T dΩ – ∫ ( 0 )T ∗ dΩ =
k ∫  – --------------
k 
- T dΓ – ∫ ( n • q )T ∗ dΓ Eq. 3•170
Ω Ω Γ Γ

This can be proved to be

c T(ξ) = ∫ ( n • q )T ∗ dΓ– ∫ ( n • q∗ )T dΓ Eq. 3•171


Γ Γ

where c = 1 if ξ is inside Ω, c = 0 if ξ is outside Ω, and c = 1/2 if ξ is on a smooth boundary Γ. The boundary inte-
gral equation for the boundary element method is obtained by discretizing the boundary Γ to boundary elements
as

Workbook of Applications in VectorSpace C++ Library 245


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

c T(ξ ) = ∑ ( n • q ) e ∫T ∗( x, ξ ) dΓ – ∑ T e ∫ ( n • q ∗( x, ξ )) dΓ Eq. 3•172


e Γe e Γe

where T and q can be either a variable or a specified boundary condition. For the matrix form representation of
Eq. 3•172, we reserve T and q as the unknown and T and q as corresponding boundary conditions, and denote

δ ij
∫T ∗( x, ξi ) dΓ, ∫ ( n • q∗ ( x, ξi ) ) dΓ,
ˆ ˆ
G ij = and H ij = with H ij = H ij – ------ Eq. 3•173
2
Γj Γj

Therefore the matrix form of Eq. 3•172 is

Tj
H ij – G ij = – H ij Tj + G ij q j Eq. 3•174
qj

This can be re-written as “A x = b”. Two singular integrations in Eq. 3•174 occur, when the source location ξ is
right on the element under consideration; i.e., all diagonal terms on the left-hand-side. For an element with con-
stant shape function, it can be proved that 1

ln  --- + 1
h 2
∫ (n • q ∗) dΓ = 0, ∫ T ∗ dΓ= ---------
ˆ -
H ii = and G ii =
 h
Eq. 3•175
2πk
Γe Γe

where h is the size of the constant element with one variable node on the middle of the element. After the vari-
ables T and q are solved from Eq. 3•174. The interior temperature at any location ξ can be recovered using Eq.
3•172, by setting c = 1. The interior heat flux can also be recovered by applying Fourier’s law of conduction and
Eq. 3•172 (c = 1). We have a response gradient boundary integral equation as

∂T ( ξ )  ∂T ∗ ( x, ξ ) ∂(n • q ∗( x, ξ )) 
q ( ξ ) = – k -------------- = – k  ∑ ( n • q ) e ∫ ----------------------- dΓ – ∑ T e ∫ ------------------------------------- dΓ  Eq. 3•176
∂ξ  e ∂ ξ ∂ ξ 
Γ e e Γ e

where

∂T ∗ ( x, ξ ) –1 ∂r ( xi – ξi ) ∂r –( x i – ξi )
----------------------- = ---------- r – 1 ------- = ------------------- , sin ce ------- = ----------------------- Eq. 3•177
∂ξ i 2πk ∂ξ i 2πkr 2 ∂ξi r

and

1. p. 69 in C.A. Brebbia, J.C.F. Telles, and L.C. Wrobel, 1984, “Boundary element Techniques: Theory and applications in
engineering”, Springer-Verlag, Berlin, Germany.

246 Workbook of Applications in VectorSpace C++ Library


Variational Methods

∂( n • q∗ ( x, ξ ) ) 1  ∂r 
------------------------------------- = ---------------  – r – 2 ( n • e i ) – 2r –3 ----- ( ( x – ξ ) • n ) 
∂ξ i 2πkr 2  ∂ξ 

1  2 ( xi – ξi ) ( ( x – ξ ) • n ) 
= --------------2-  – n i + -----------------------------------------------------
- Eq. 3•178
2πkr  r2 

We consider a trivial example which solution is self-evident for checking our implementation of this method.
We investigate conduction of heat on a squre region -1 < x < 1, and -1 < y < 1. The upper and lower boundaries
are insulated from its sorrounding by setting qy = 0, the left boundary has T = 0, and the right boundary has T =
100. Because of the steady state condition ∇ 2 T = 0 , the gradient on the x direction is constant. This leads to a
linear temperature distribution and constant heat flux on x direction. On y direction, temperature distribution is
uniform and heat flux is zero. Program Listing 3•22 to Program Listing 3•26 implement Eq. 3•174 to solve for
the variables T and q on the boundaries, then, use Eq. 3•172 and Eq. 3•176 to recover the interior temperature
and heat flux. We discritize each side of the square region into eight equal length constant elements. Program
Listing 3•22 is the “main()” program of the boundary element method.
#include "include\vs.h"
static const double PI = 3.141592654;
static C0 A(32, 32, (double*)0);
static C0 f(32, (double*)0);
static C0 H(32, 16, A, 0, 0);
static C0 mG(32, 16, A, 0, 16);
double w[9] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, extended Bode’s integration rule
64.0/45.0, 24.0/45.0, 64.0/45.0, 14.0/45.0},
x[8][2] = {{-1.0, -0.75}, {-0.75, -0.5}, {-0.5, -0.25}, {-0.25, -0.0}, {0.0, 0.25}, {0.25, 0.5}, element end points coordinates
{0.5, 0.75}, {0.75, 1.0}},
y[8][2] = {{-1.0, -0.75}, {-0.75, -0.5}, {-0.5, -0.25}, {-0.25, -0.0}, {0.0, 0.25}, {0.25, 0.5},
{0.5, 0.75}, {0.75, 1.0}},
zai[8] = {-0.875, -0.625, -0.375, -0.125, 0.125, 0.375, 0.625, 0.875}, element variable node coordinates
eta[8] = {-0.875, -0.625, -0.375, -0.125, 0.125, 0.375, 0.625, 0.875};
void LHS_0_15();
void LHS_16_31();
void RHS();
void T_recovery(const C0&, const C0&, const C0&);
void q_recovery(const C0&, const C0&, const C0&, const C0&);
int main() { row 0-15 of A
LHS_0_15(); row 16-31 of A
LHS_16_31();
RHS(); b
C0 Y = f / A, Solve boundary integral equations
T_gamma(16, Y, 0), q_gamma(16, Y, 16),
T(8, 8, (double*)0), q_x(8, 8, (double*)0), q_y(8, 8, (double*)0);
cout << T_gamma << endl;
cout << q_gamma << endl;
T_recovery(T_gamma, q_gamma, T); interior temperature recovery
cout << T << endl;
q_recovery(T_gamma, q_gamma, q_x, q_y);
cout << q_x << endl; interior heat flux recovery
cout << q_y << endl;
return 0;
}

Listing 3•22 The main program of the boundary element method (project: “boundary_element_method”).

Workbook of Applications in VectorSpace C++ Library 247


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

void LHS_0_15() { Hii (diagonal) is -1/2


for(int i = 0; i < 16; i++) H[i][i] = -1.0/2.0;
for(int i = 0; i < 8; i++) {
for(int j = 0; j < 8; j++) {
Quadrature qp(w, x[j][0], x[j][1], 9);
1
∫ -------
J d_l(0.25/8.0); ˆ - dΓ
H ij =
H0 X(qp), πr 2
R_pow_2 = 4+(X-zai[i]).pow(2); Γj
H[i][j+8] = H[i+8][j] = 1.0/(PI*R_pow_2) | d_l;
}
}
for(int i = 0; i < 8; i++) {
for(int j = 0; j < 8; j++) {
Quadrature qp(w, y[j][0], y[j][1], 9);
J d_l(0.25/8.0);
H0 Y(qp),
R0 = sqrt(pow(1.0-zai[i], 2)+(Y-1).pow(2)),
R1 = sqrt(pow(1.0+zai[i], 2)+(Y-1).pow(2)),
R2 = sqrt(pow(1.0-zai[i], 2)+(Y+1).pow(2)), ln ( r )
R3 = sqrt(pow(1.0+zai[i], 2)+(Y+1).pow(2));
mG[i][j] = 1.0/(2.0*PI)*log(R0) | d_l; mG[i][j+8] = 1.0/(2.0*PI)*log(R1) | d_l;
– G ij = ∫ -----------
2πk
- dΓ
Γj
mG[i+8][j] = 1.0/(2.0*PI)*log(R2) | d_l; mG[i+8][j+8] = 1.0/(2.0*PI)*log(R3) | d_l;
}
}
}

Listing 3•23 Function LHS_0_15() (project: “boundary_element_method”).

Program Listing 3•23 implements the first 16 rows of matrix A corresponding to source position ξi on the 8
upper boundary elements and 8 lower boundary elements. From Eq. 3•173 the diagonal elements of Hij are first
assigned “-1/2”. For this particular case, if source position ξi is located at any element of the upper boundary, the
integration of Hij for all upper boundary elements (with second index “j”) become signular and their values are
zero according to Eq. 3•175. This is also true for sources position at lower boundary elements, which makes all
lower boundary element integrals zero. For source position ξi at upper boundary and integral on the lower
boundary elements (with index “j”), and vice versa, we have

( x 1 – ξ1 ) 2 1
∫ (n • q ∗ ( x, ξi )) dΓ ∫ --------------------- ∫ ---------- ∫ -------
ˆ
H ij = = dΓ = - dΓ = - dΓ Eq. 3•179
2πr 2 2πr 2 πr 2
Γj Γj Γj Γj

The -Gij terms in the first 16 rows of matrix A corresponding to the 8 upper boundary elements and 8 lower
boundary elements are simply

ln ( r ( x, ξ i ) )
– G ij = – ∫T ∗( x, ξi ) dΓ = ∫ --------------------------- dΓ Eq. 3•180
2πk
Γj Γj

248 Workbook of Applications in VectorSpace C++ Library


Variational Methods
void LHS_16_31() {

– G ii = – ---------- ln  --- + 1
double mG_diag = 0.125 / PI * (log(0.125)-1.0); h 2
for(int i = 0; i < 16; i++) mG[i+16][i] = mG_diag; 2πk  h
for(int i = 0; i < 8; i++) {
for(int j = 0; j < 8; j++) {
Quadrature qp(w, y[j][0], y[j][1], 9);
J d_l(0.25/8.0);
H0 Y(qp);
if(i != j) {
ln ( r )
∫ -----------
if(i<j) mG[i+16][j] = mG[i+24][j+8] = 1.0/(2.0*PI) * log(Y-eta[i]) | d_l;
else mG[i+16][j] = mG[i+24][j+8] = 1.0/(2.0*PI) * log(eta[i]-Y) | d_l; – G ij = - dΓ
2πk
} Γj
}
}
for(int i = 0; i < 8; i++)
for(int j = 0; j < 8; j++) {
Quadrature qp(w, y[j][0], y[j][1], 9);
J d_l(0.25/8.0);
H0 Y(qp),
R = sqrt(4+(Y-eta[i]).pow(2));
mG[i+16][j+8] = mG[i+24][j] = 1.0/(2.0*PI) * log(R) | d_l;
}
for(int i = 0; i < 8; i++)
for(int j = 0; j < 8; j++) {
Quadrature qp(w, x[j][0], x[j][1], 9);
J d_l(0.25/8.0);
H0 X(qp),
R0_2 = pow(1-eta[i], 2) +(X-1).pow(2),
R1_2 = pow(1+eta[i], 2) +(X-1).pow(2),
R2_2 = pow(1-eta[i], 2) +(X+1).pow(2),
R3_2 = pow(1+eta[i], 2) +(X+1).pow(2); ( n • e 1 ) ( x1 – ξ1 )
∫ ----------------------------------------
ˆ - dΓ
H[i+16][j] = (1.0-eta[i])/(2.0*PI*R0_2) | d_l; H ij =
H[i+16][j+8] = (1.0+eta[i])/(2.0*PI*R1_2) | d_l; 2πr 2
Γj
H[i+24][j] = (1.0-eta[i])/(2.0*PI*R2_2) | d_l;
H[i+24][j+8] = (1.0+eta[i])/(2.0*PI*R3_2) | d_l;
}
}
Listing 3•24 Function LHS_16_31() (project: “boundary_element_method”).

Similarly, Program Listing 3•24 implements the last 16 rows of matrix A corresponding to source position ξi
on 8 right boundary elements and 8 left boundary elements. Now the singular integration corresponds to diago-
nals of -Gij terms in the last 16 rows of matrix A. According to Eq. 3•175, that is

– G ii = – ∫ T ∗ dΓ = – ---------- ln  --- + 1
h 2
Eq. 3•181
2πk  h
Γi

Otherwise, -Gij use the definition in Eq. 3•180. For the terms of Hij

( n • e 1 ) ( x1 – ξ1 )
∫ ( n • q∗ ( x , ξ i ) ) d Γ ∫ ----------------------------------------
ˆ - dΓ
H ij = = Eq. 3•182
2πr 2
Γj Γj

Workbook of Applications in VectorSpace C++ Library 249


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

void RHS() {
for(int i = 0; i < 8; i++) {
for(int j = 0; j < 8; j++) {
Quadrature qp(w, y[j][0], y[j][1], 9);
J d_l(0.25/8.0);
100 ( 1 – ξ 0 )
b i = – --------- ∫ ------------------
H0 Y(qp),
R0_2 = pow(1-zai[i], 2)+(Y-1).pow(2),
- dΓ
2π r2
R1_2 = pow(1-zai[i], 2)+(Y+1).pow(2); Γj
f[i] += (-100.0*(1-zai[i]))/(2.0*PI*R0_2) | d_l;
f[i+8] += (-100.0*(1-zai[i]))/(2.0*PI*R1_2) | d_l;
}
} bi = 100/2
for(int i = 16; i < 24; i++) f[i] = 100.0 / 2.0;
for(int i = 24; i < 32; i++) {
for(int j = 0; j < 8; j++) {
Quadrature qp(w, y[j][0], y[j][1], 9);
100 1
J d_l(0.25/8.0); b i = – --------- ∫ ---2- dΓ
H0 Y(qp), R_pow_2 = 4+(Y-eta[i-24]).pow(2); π r
f[i] += - 100.0 / (PI*R_pow_2) | d_l; Γj
}
}
}

Listing 3•25 Function RHS() (project: “boundary_element_method”).


where both coordinate x1 and the inner product of surface normal and coordinate basis n • e 1 can have the value
of either 1 or -1.
Program Listing 3•25 implements the right-hand-side vector of Eq. 3•174. Since only Tj corresponding to
right boundary elements are not zero (i.e., T16 ~ T23 = 100, all other T = 0 and all q = 0), we only need to con-
sider right-hand-side values of -Hij Tj with index “j” running from 16 to 23, i.e., 16th to 23rd columns of H. For
source position ξ on upper and lower boundary elements (i = 0~15), we have

( n • e0 ) ( x0 – ξ0 ) 100 ( 1 – ξ 0 )
b i = – H ij T j = – 100 ∫ ( n • q∗ ( x, ξ i ) ) dΓ = – 100 ∫ ----------------------------------------
- dΓ = – --------- ∫ ------------------
ˆ - dΓ
2
Eq. 3•183
2πr 2π r2
Γj Γj Γj

For source position ξ on left boundary elements, singular integrals occur. Therefore, bi = -c Tj, where c = -1/2;
i.e., bi = 100/2 = 50, for i = 16~23. For source position ξ on right boundary elements (i = 24~31), we have

( n • e0 ) ( x0 – ξ0 ) 100 1
b i = – H ij T j = – 100 ∫ (n • q ∗ ( x, ξ i )) dΓ = – 100 ∫ ----------------------------------------
- dΓ = – --------- ∫ ---- dΓ
ˆ
Eq. 3•184
2πr 2 π r2
Γj Γj Γj

Program Listing 3•26 for interior temperature and interior heat flux recovery is a straight forward imple-
mentation of Eq. 3•172 and Eq. 3•176.
The results of this simple problem are trivial. It is suffice to say that close to boundaries the accuracy deteri-
orates fast, since we have a lot of ln(r), 1/r, 1/r 2 ... etc. in the equations. This phenomenon is known as hypersin-
gularity of the boundary integral equation. These complex functions are very chanlleging for numerical
integration. Special integration rules for these functions are common practice in boundary element method.

250 Workbook of Applications in VectorSpace C++ Library


Variational Methods

void T_recovery(const C0& T_gamma, const C0& q_gamma, const C0& T) {

∑ ( n • q ) e ∫ T∗ ( x, ξ ) dΓ
for(int i = 0; i < 8; i++)
for(int j = 0; j < 8; j++) { T(ξ) =
for(int k = 0; k < 8; k++) { e Γe
Quadrature qpx(w, x[k][0], x[k][1], 9);

– ∑ T e ∫ ( n • q∗ ( x, ξ ) ) dΓ
J d_l(0.25/8.0);
H0 X(qpx),
R0_2 = pow((1-eta[i]), 2) + (X-zai[j]).pow(2), e Γe
R1_2 = pow((1+eta[i]), 2) + (X-zai[j]).pow(2);
T[i][j] += T_gamma[k] /(2.0*PI)*(1-eta[i])/R0_2 | d_l;
T[i][j] += T_gamma[k+8] /(2.0*PI)*(1+eta[i])/R1_2 | d_l; where
Quadrature qpy(w, y[k][0], y[k][1], 9);
1
H0 Y(qpy), q ∗ = ----------2- [ ( x 0 – ξ 0 )e 0 + ( x 1 – ξ1 )e 1 ]
R2_2 = pow((1-zai[j]), 2) + (Y-eta[i]).pow(2), 2πr
R3_2 = pow((zai[j]+1), 2) + (Y-eta[i]).pow(2);
T[i][j] += 100.0 / (2.0*PI)*(1-zai[j])/R2_2 | d_l;
–1
T[i][j] += q_gamma[k] / (2.0*PI)*log(sqrt(R2_2)) | d_l; T ∗ ( x, ξ )= ---------- ln ( r )
T[i][j] += q_gamma[k+8] / (2.0*PI)*log(sqrt(R3_2)) | d_l; 2πk
}
}
}
void q_recovery(const C0& T_gamma, const C0& q_gamma,
const C0& q_x, const C0& q_y) { ∂ T ∗ ( x, ξ )
double nx, ny; q ( ξ ) = – k ∑ ( n • q ) e ∫----------------------- dΓ
nx = 1.0;
∂ξ
e Γe
for(int i = 0; i < 8; i++)
for(int j = 0; j < 8; j++) {
for(int k = 0; k < 8; k++) {
∂( n • q∗ ( x, ξ ) )
Quadrature qpx(w, x[k][0], x[k][1], 9); + k ∑ T e ∫ ------------------------------------- dΓ
J d_l(0.25/8.0); ∂ξ
H0 X(qpx), e Γe
R0_2 = pow((1-eta[i]), 2) + (X-zai[j]).pow(2),
R1_2 = pow((1+eta[i]), 2) + (X-zai[j]).pow(2); where
ny = 1.0;
q_x[i][j] -= (ny*T_gamma[k] /(2.0*PI*R0_2)*(2.0*(X-zai[j])*(1-eta[i])/R0_2)) | d_l; ∂T ∗ ( x, ξ ) ( x i – ξi )
q_y[i][j] -= (ny*T_gamma[k] /(2.0*PI*R0_2)* ----------------------- = -------------------
(2.0*(1-eta[i])*(1-eta[i])/R0_2-1.0)) | d_l;
∂ξ i 2πkr 2
ny = -1.0;
q_x[i][j] -= (ny*T_gamma[k+8] /(2.0*PI*R1_2)* and
(2.0*(X-zai[j])*(-1-eta[i])/R1_2)) | d_l;
q_y[i][j] -= (ny*T_gamma[k+8] /(2.0*PI*R1_2)* ∂(n • q ∗ ( x, ξ ))
------------------------------------- =
(2.0*(-1-eta[i])*(-1-eta[i])/R1_2-1.0)) | d_l; ∂ξi
Quadrature qpy(w, y[k][0], y[k][1], 9);
H0 Y(qpy),
1  2 ( xi – ξi ) ( ( x – ξ ) • n ) 
R2_2 = pow((1-zai[j]), 2) + (Y-eta[i]).pow(2), ---------------  – n i + -----------------------------------------------------
-
2πkr  2 r2
R3_2 = pow((zai[j]+1), 2) + (Y-eta[i]).pow(2); 
q_x[i][j] -= (nx*100.0 / (2.0*PI*R2_2)*(2.0*(1-zai[j])*(1-zai[j])/R2_2 - 1.0)) | d_l;
q_y[i][j] -= (nx*100.0 / (2.0*PI*R2_2)*(2.0*(Y-eta[i])*(1-zai[j])/R2_2 )) | d_l;
q_x[i][j] += (q_gamma[k] / (2.0*PI*R2_2)*(1-zai[j])) | d_l;
q_y[i][j] += (q_gamma[k] / (2.0*PI*R2_2)*(Y-eta[i])) | d_l;
q_x[i][j] += (q_gamma[k+8] / (2.0*PI*R3_2)*(-1-zai[j])) | d_l;
q_y[i][j] += (q_gamma[k+8] / (2.0*PI*R3_2)*(Y-eta[i])) | d_l;
}
}
}

Listing 3•26 Function T_recovery() and q_recovery() (project: “boundary_element_method”).

Workbook of Applications in VectorSpace C++ Library 251


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
3.3.4 Transient Problems—Initial Value Problem
In this section, the weighted residual method (Section 3.3.2) is applied to time domain of (1) heat conduc-
tion, and (2) structural dynamics. Heat conduction (a parabolic equation) is of the form

Ca· + Ka + f = 0 Eq. 3•185

where C is the heat capacitiy matrix, K is the conductivity matrix, and f is heat source vector. The variable a is
the temperature and a· is the time derivative of temperature. Structural dynamics (a hyperbolic equation) is of
the form

Ma·· + Ka + f = 0 Eq. 3•186

where M is the consistent mass matrix, K is the stiffness matrix, and f is the force vector. The variable a is the
displacement and the second time derivative of the displacement, a··· gives the acceleration.

Parabolic Equation
Considering a time interval tn to tn+1, and ∆t = tn+1 - tn. At this time interval, τ (= t - tn) can be normalized by
∆t as a referential coordinate ξ = τ / ∆t, with 0 < ξ < 1. The variable a at time tn and tn+1 is denoted as an and an+1,
respectively. A linear interpolation function, the trapezoidal rule for time integration, is used to approximate a in
the time interval tn to tn+1 as1

τ
a ≅ â ( τ ) = ( 1 – ξ )a n + ξa n + 1 = a n + ξ ( a n + 1 – a n ) = a n + ----- ( a n + 1 – a n ) Eq. 3•187
∆t

Now, the residual of Eq. 3•185 is distributed through-out the time domain (tn, tn+1) by a weighted-residual state-
ment similar to Eq. 3•105 as

∆t ∆t
·
∫ WR dτ = ∫ W ( Câ ( τ ) + Kâ ( τ ) + f ) dτ = 0 Eq. 3•188
0 0

A parameter θ is defined as

 ∆t   ∆t 
θ =  ∫ W τ dτ ⁄  ∆t ∫ W dτ Eq. 3•189
   
0   0 
∆t

Eq. 3•188 devided by ∆t ∫ W dτ gives


0

1. We follow Chapter 10 in Zienkiewicz and Taylor, 1991, “The finite element method”, 4th eds. vol. 2, McGraw-Hill Book
Company, UK.

252 Workbook of Applications in VectorSpace C++ Library


Variational Methods
· · d ( an + 1 – a n )
C ( â ( τ ) ) + K [ a n + θ ( a n + 1 – a n ) ] + [ f n + θ ( f n + 1 – f n ) ] = 0, where â ( τ ) = â ( τ ) = ----------------------------- Eq. 3•190
dτ ∆t

Note that similar interpolation of force vector f is taken as that for the variable a. Re-arranging in order to solve
for the unknown an+1, we have

( C + ∆tθK )a n + 1 = ( C – ∆t ( 1 – θ )K )a n – f̂ Eq. 3•191

where

f̂ = f n + θ ( fn + 1 – fn ) Eq. 3•192

Eq. 3•191 is a time recurrence formula with which the solutions of consequtive time steps can be calculated.
From the results of Eq. 3•113 and Eq. 3•114, we have, in the context of the point-collocation method, the values
of θ = 0, 1, 0.5 corresponding to the backward difference, forward difference, and central difference, respectively.
Consider an initial value problem1

∂u ∂ 2 u
----- – -------- = 0, 0 < x < 1 Eq. 3•193
∂t ∂x 2

the boundary and initial conditions are

∂u
u ( 0, t ) = 0, ------ ( 1, t ) = 0, and u ( x, 0 ) = 1 Eq. 3•194
∂x

The exact solution to this problem is


e –λn t sin λ n x
2
( 2n + 1 )π
u exact ( x, t ) = 2 ∑ -----------------------------, λ n = ------------------------
λn 2
Eq. 3•195
n=0

Making (1) a weighted-residual statement, and (2) integration by part on the spatial derivative term of Eq. 3•193,
we have the weak form with Bubnov-Galerkin weighting v ≡ w = u as

1 1 1 1
∂v ∂ 2 v ∂v ∂v ∂v ∂v ∂v ∂v ∂v
0 = ∫ v  ----- – --------2 dx = ∫  v ----- + ------ ------ dx –  v ------ = ∫  v ----- + ------ ------ dx Eq. 3•196
 ∂t ∂x  ∂t ∂x ∂x  ∂x ∂t ∂x ∂x
0 0 0 0

1. p. 323-324 in J.N. Reddy, 1986, “Applied functional analysis and variational methods in engineering”, McGraw-Hill, Inc.

Workbook of Applications in VectorSpace C++ Library 253


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
This is the equation of the form of Eq. 3•185; i.e., Ca· + Ka + f = 0 , where f = 0. Two term approximation for v is

v(x, t) = c0(t) φ0(x) + c1(t) φ1(x) Eq. 3•197

where we take φ0(x) = x, and φ1(x) = x2. Now the matrices in Eq. 3•185 can be identified as

1 1 1 1
∂φ 0 ∂φ 0 ∂φ ∂φ
∫ φ0 φ0 dx ∫ φ0 φ1 dx ∫  --------
- --------- dx ∫  --------0- --------1- dx
∂x ∂x   ∂x ∂x 
C = 0 0 , and K = 0 0 Eq. 3•198
1 1 1 1
∂φ 1 ∂φ 0 ∂φ 1 ∂φ 1
∫ φ1 φ0 dx ∫ φ1 φ1 dx ∫  --------
- --------- dx ∫  --------- --------- dx
∂x ∂x   ∂x ∂x 
0 0 0 0

The initial condition u(x, 0) = 1, is approximated by a Bubnov-Galerkin weighted-residual statement as

1 1

∫ v [ v ( x, 0 ) – 1 ] dx = 0 = ∫ [ φ ⊗ ( φ • c ( 0 ) ) – φ ] dx Eq. 3•199
0 0

or in matrix form,

1 1 1

∫ φ0 φ0 dx ∫ φ0 φ1 dx c0 ( 0 ) ∫ φ0 dx
0 0 = 0 Eq. 3•200
1 1 1
c1 ( 0 )
∫ φ1 φ0 dx ∫ φ1 φ1 dx ∫ φ1 dx
0 0 0

Therefore, the results are c0(0) = 4 and c1(0) = -10/3. This serves as the initial condition in place of Eq. 3•200 for
the basis function approximation in Eq. 3•197. Although Eq. 3•200 is simple enough to be solved by hand, we
can easily implement Eq. 3•200 with VectorSpace C++ Library.
Program Listing 3•27 first implements the approximated initial condition with Eq. 3•200, and then, uses the
definitions of C and K in Eq. 3•198 to solve for the time recurrence formula provided by Eq. 3•191. In this
implementation, the time step ∆t = 0.05, and θ = 0.5 is used for the central difference method. Note that for the
initial condition approximation the highest order polynomial is the fifth-order. We use Bode’s integration rule for
the accuracy of the initial condition approximation. For the time recurrence formula the highest order polyno-
mial for integration is only up to second order, so Simpson’s rule will be sufficient.
The results of the computation are shown in Figure 3•33. A large error occurs at time zero, since the initial
condition is only approximated through Eq. 3•199.

254 Workbook of Applications in VectorSpace C++ Library


Variational Methods

#include "include\vs.h" φ0(x) = x, and φ1(x) = x2


int main() {
C0 c_t_n(2, (double*)0), c_t_n1(2, (double*)0); initial condition approximation
{ Bode’s integration rule
double weight[5] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 14.0/45.0};
Quadrature qp(weight, 0.0, 1.0, 5);
J d_l(1.0/4.0);
H1 x(qp),
phi = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( 1 1 1
"int, int, Quadrature", 2, 1, qp);
phi[0] = x; phi[1] = x.pow(2); ∫ φ0 φ0 dx ∫ φ0 φ1 dx c0 ( 0 ) ∫ φ0 dx
C0 C = (((H0)phi)%((H0)phi))|d_l, 0 0 0
b = ((H0)phi)|d_l; =
1 1 1
c_t_n = b / C; c1 ( 0 )
}
double weight[3] = {1.0/3.0, 4.0/3.0, 1.0/3.0};
∫ φ1 φ0 dx ∫ φ1 φ1 dx ∫ φ1 dx
0 0 0
Quadrature qp(weight, 0.0, 1.0, 3);
J d_l(1.0/2.0);
H1 x(qp),
phi = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 2, 1, qp);
phi[0] = x; phi[1] = x.pow(2);
C0 C = (((H0)phi)%((H0)phi))|d_l, c(t=0) = {4, -10/3}T
K = (d(phi)(0)%d(phi)(0))|d_l; 1
double theta_ = 0.5, dt_ = 0.05;
C0 LHS = C + theta_ * dt_ * K,
C = ∫ φ ⊗ φ dx
RHS = C - (1.0 - theta_) * dt_ * K; 0
C0 d_LHS = !LHS; 1
∂φ ∂φ
for(int i = 0; i < 30; i++) {
c_t_n1 = d_LHS*(RHS*c_t_n);
K = ∫  -----
- ⊗ ------ dx
∂x ∂x
double iptr; 0
if(modf( ((double)(i+1))/2.0, &iptr)==0) cout << c_t_n1 << endl;
c_t_n = c_t_n1; ( C + ∆tθK )a n + 1=( C – ∆t ( 1 – θ )K )a n
}
return 0;
}

Listing 3•27 Parabolic equation (project: “parabolic_equation”).

Hyperbolic Equation
We consider a more general second order differential equation (hyperbolic-parabolic equation) such as

Ma·· + Ca· + Ka + f = 0 Eq. 3•201

where M is the mass matrix, C is the viscous damping, K is the stiffness, and f is the forcing terms. In structural
dynamics, a is the displacement, a· is the velocity, and a·· is the acceleration. When C = 0, Eq. 3•201 reduces to
the hyperbolic case. For Eq. 3•201, we use a time recurrence formula in which given a n, a· n , and a··n at time tn,
we seek solutions of a n + 1, a· n + 1, and a··n + 1 at time tn+1. Similar to Eq. 3•187, we use a linear interpolation func-
tion with a natural coordinate ξ = τ / ∆t, where 0 < ξ < 1, to approximate the acceleration as

Workbook of Applications in VectorSpace C++ Library 255


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

1
Exact Solution t=0.0 1 Numerical Solution at x = 0.5 & 1.0
x = 0.5 x = 1.0
0.8 0.8
t=0.2
0.6 0.6
u u x = 1.0
t=0.4
0.4 0.4
t=0.6
0.2 t=0.8 0.2
x = 0.5
t=1.0
t=1.2
t=1.4
0 0.2 0.4 0.6 0.8 1 0.2 0.4 0.6 0.8 1 1.2 1.4
x t
Figure 3•33 The left-hand-side is the exact solution computed from Eq. 3•195. The results of
numerical solution on x = 0.5 and 1.0 are shown.

·· τ
a·· ≅ â ( τ ) = ( 1 – ξ )a··n + ξa··n + 1 = a··n + ξ ( a··n + 1 – a··n ) = a··n + ----- ( a··n + 1 – a··n ) Eq. 3•202
∆t

For the displacement a and the velocity a· , we can approximate them with Taylor series approximation to the
second order of ∆t for an+1 as

∆t 2
a n + 1 ≅ a n + ∆ta· n + -------- [ ( 1 – 2β )a··n + 2βa··n + 1 ] Eq. 3•203
2

or, to the first order of ∆t for a· n + 1 as,

a· n + 1 ≅ a· n + ∆t [ ( 1 – γ )a·· n + γa··n + 1 ] Eq. 3•204

The two parameters β and γ are choosen independently. Notice that Eq. 3•204 is an expression only up to the
first order of ∆t. Therefore, we restrict our choice of γ = 1/2 to keep the order of accuracy up to O(∆t2). Two
choices of β are popular. For the so-called constant average acceleration γ = 2β = 1/2, the weighting functions
W(τ) for acceleration terms in Eq. 3•203 and Eq. 3•204 are the same. In the context of point-collocation the
value 1/2 corresponds to central difference method. This value is also equivalent to collocation with a constant
weighting function throught out the time interval tn to tn+1; i.e., W(τ) = 1, such that from the definition in Eq.
3•189, we get

 ∆t   ∆t  ∆t
τ2
γ = 2β =  ∫ W τ dτ ⁄  ∆t ∫ W dτ =  ----- ⁄ ( ∆tτ )
1
= --- Eq. 3•205
     2 2
0   0  0

256 Workbook of Applications in VectorSpace C++ Library


Variational Methods
Similarly, for the so-called linear acceleration, γ = 1/2 is to keep the second order accuracy, but now set β = 1/6.
That is we use W(τ) = 1 for Eq. 3•204, while we take a linear weighting function W(τ) = (1−ξ) = (1−τ / ∆t) for
Eq. 3•203, so

 ∆t   ∆t  1
ξ 2 ξ3 ξ2
2β =  ∫ W τ dτ ⁄  ∆t ∫ W dτ =  ----- – ----- ⁄  -----
1
= --- Eq. 3•206
     2 3  2 3
0   0  0

Now, we work out the formula for the Newmark method for the hyperbolic-parablic equation. From Eq.
3•203, we have

1 ∆t 2
a··n + 1 = --------- a n + 1 – a n – ∆ta· n – -------- ( 1 – 2β )a··n Eq. 3•207
β∆t 2

Substituting this into Eq. 3•204, we have

γ ∆t 2
a· n + 1 = a· n + ∆t ( 1 – γ )a··n + ---------  a n + 1 – a n – ∆ta· n – -------- ( 1 – 2β )a··n Eq. 3•208
β∆t  2 

Since a n, a· n , and a··n at time tn are all given values, the three unknowns a n + 1, a· n + 1, and a··n + 1 at time tn+1 can be
substituted into Eq. 3•201 as

Ma··n + 1 + Ca· n + 1 + Ka n + 1 + fn + 1 = 0 Eq. 3•209

where a· n + 1 , and a··n + 1 are expressed in Eq. 3•207 and Eq. 3•208, and the only unknown is a n + 1 in Eq. 3•209.
Re-arranging Eq. 3•209, we have1

ˆ ˆ
Kan + 1 = Rn + 1 Eq. 3•210

where,

ˆ ˆ
K = K + a 0 M + a 1 C, and R n + 1 = – f n + 1 + ( a 0 a n + a 2 a· n + a 3 a··n )M + ( a 1 a n + a 4 a· n + a 5 a··n )C Eq. 3•211

a n + 1 is solved from Eq. 3•210, then, a· n + 1 , and a··n + 1 are calculated from re-arranged Eq. 3•207 and Eq. 3•208
as

a··n + 1 = a 0 ( a n + 1 – a n ) – a 2 a· n – a 3 a··n
a·n+1 = a· + a a·· + a a··
n 6 n 7 n+1 Eq. 3•212

The so-called Newmark coefficients ai in Eq. 3•211 and Eq. 3•212 are

1. p. 323 in K Bathe, and E.L. Wilson, 1976, “Numerical method in finite element analysis”, Prentice-Hall, New Jersey.

Workbook of Applications in VectorSpace C++ Library 257


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects
γ γ ∆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. 3•213
β∆t β∆t β∆t 2β β 2 β 

Consider a hyperbolic equation for beam vibration

∂2 w ∂4 w
---------- = – ---------4-, 0 < x < 1, t > 0 Eq. 3•214
∂t 2 ∂x

where w(x, t) is the deflection of the beam , boundary conditions are

∂w ( 0, t ) ∂w ( 1, t )
w ( 0, t ) = w ( 1, t ) = -------------------- = -------------------- = 0 , Eq. 3•215
∂x ∂x

and initial conditions are

∂w ( x, 0 )
w(x, 0) = sin(πx)-πx(1-x), and --------------------- = 0 Eq. 3•216
∂t

Two approximation basis functions are taken as

φ0 = 1-cos(2πx), φ1 = 1-cos(4πx)

for w2 = c0(t) φ0(x) + c1(t) φ1(x), with C = 0 and f = 0 in Eq. 3•201, M and K can be identified as
1 1 2 2
d φ d φ 
M = ∫ ( φ ⊗ φ ) dx, and K = ∫  d x2 ⊗ d x2  dx Eq. 3•217
0 0

The coefficients for the initial condition in Eq. 3•216 is approximated by weighted-residual method
1

∫ W [ w2 ( x, 0 ) – sin ( πx ) + πx ( 1 – x ) ] dx = 0 Eq. 3•218


0

With Bubnov-Galerkin weighting W(x, t) = w2(x, t) = c0(t) φ0(x) + c1(t) φ1(x) , Eq. 3•218 becomes
1 1

∫ ( φ ⊗ φ ) dx c(0) = ∫ φ [ sin ( πx ) –π x ( 1 – x ) ] dx Eq. 3•219


0 0

Therefore, the intial condition coefficient vector c(0) can be obtained.


Program Listing 3•28 implements Newmark method (Eq. 3•211 to Eq. 3•213) with constant average acceler-
ation γ = 1/2, β = 1/4, and ∆t = 0.01. The initial condition coefficients are approximated by Eq. 3•219.
The results of the computation are shown in Figure 3•34.

258 Workbook of Applications in VectorSpace C++ Library


Variational Methods

#include "include\vs.h"
#define PI 3.141592654
int main()
{ extended Bode’s integration rule
double weight[33] = {14.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0,
24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0,
28.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0,
24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0,
28.0/45.0, 64.0/45.0, 24.0/45.0, 64.0/45.0, 28.0/45.0, 64.0/45.0,
24.0/45.0, 64.0/45.0, 14.0/45.0};
Quadrature qp(weight, 0.0, 1.0, 33);
J d_l(1.0/32.0);
H2 x((double*)0, qp),
phi = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 2, 1, qp);
phi[0] = 1.0-cos(2.0*PI*x); phi[1] = 1.0-cos(4.0*PI*x); φ1 = 1-cos 2πx, φ2 = 1-cos 4πx
H0 d2_phi = INTEGRABLE_VECTOR("int, Quadrature", 2, qp); 1

∫ ( φ ⊗ φ ) dx
for(int i = 0; i < 2; i++) d2_phi[i] = dd(phi)(i)[0][0];
C0 mass = (((H0)phi)%((H0)phi))|d_l, M =
stiff = (d2_phi%d2_phi)|d_l; 0
double gamma_ = 0.5, beta_ = 0.25, dt_ = 0.01, a[8]; 1 2 2
d φ d φ 
K = ∫  2 ⊗ 2  dx
C0 c_old(2, (double*)0), c_new(2, (double*)0), dc_old(2, (double*)0),
dc_new(2, (double*)0), ddc_old(2, (double*)0), ddc_new(2, (double*)0);
a[0] = 1.0/(beta_*pow(dt_,2)); a[1] = gamma_/(beta_*dt_); a[2] = 1.0/(beta_*dt_);
dx dx 
0
a[3] = 1.0/(2.0*beta_)-1.0; a[4] = gamma_/beta_-1.0; a[5] = dt_/2.0*(gamma_/beta_-2.0);
a[6] = dt_*(1.0-gamma_); a[7] = gamma_*dt_;
H0 w_0 = sin(PI*((H0)x))-PI*((H0)x)*(1.0-((H0)x)); initial condition approximation
c_old = ( ( ((H0)phi)*w_0 )|d_l ) / ( ( ((H0)phi)%((H0)phi) )|d_l ); by Eq. 3•219
C0 LHS = stiff + a[0]*mass, ˆ
K = K + a0 M
d_LHS = !LHS;
for(int i = 0; i < 28; i++) {
ˆ
C0 RHS = mass * (a[0]*c_old + a[2] * dc_old + a[3] * ddc_old); R n + 1 = ( a 0 a n + a 2 a· n + a 3 a··n )M
c_new = d_LHS*RHS;
double iptr;
if(modf( ((double)(i+1))/2.0, &iptr)==0) cout << "t= " << ((i+1)*dt_) << ", u(0.5) = " <<
(c_new[0]*(1.0-cos(PI))+c_new[1]*(1.0-cos(2.0*PI))) << endl;
ddc_new = a[0]*(c_new - c_old)-a[2]*dc_old-a[3]*ddc_old; a··n + 1 = a 0 ( a n + 1 – a n ) – a 2 a· n – a 3 a··n
a· = a· + a a·· + a a··
dc_new = dc_old + a[6]*ddc_old + a[7]*ddc_new;
c_old = c_new; dc_old = dc_new; ddc_old = ddc_new; n+1 n 6 n 7 n+1
} update ( . )n = ( . )n+1
return 0;
}

Listing 3•28 Hyperbolic equation using Newmark method with constant average acceleration (project:
“hyperbolic_equation”).

Workbook of Applications in VectorSpace C++ Library 259


Chapter 3 Variational Methods Using H0, H1, and H2 Type Objects

t=0.02 t=0.28
0.2 0.2 t=0.26
t=0.04
t=0.24
0.1 0.1
t=0.08
t=0.22
w
w x x
0.2 0.4 0.6 0.8 1 0.2 0.4 0.6 0.8 1
t=0.08

-0.1 -0.1 t=0.20


t=0.10
t=0.12 t=0.18
-0.2 -0.2
t=0.14 t=0.16

Figure 3•34 Hperbolic equation using Newmark method.

260 Workbook of Applications in VectorSpace C++ Library


CHAPTER

Four Finite Element Method Primer

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-

Workbook of Applications in VectorSpace C++ Library 265


Chapter 4 Finite Element Method Primer
matical problems just like what VectorSpace C++ Library is designed for. However, for general purpose pro-
gramming, none of these alternative approaches could come close to rival that of C++. If we have chosen those
alternative approaches, we will be seriously penalized by their limited capability in non-mathematical aspects of
the programming. If you choose to program in C++ with the VectorSpace C++ Library, your programming task
will be significantly empowered by the object-oriented programming—the modern programming paradigm.
Time have proven that specific purpose languages do not last long, they come and go and never gaining any
wide acceptance. Sometimes, they are even quickly forgotten by the communities of the applications that they
are specifically targeting for. Jump on the band wagon of C++, you have entire software industry (particularly all
the first-ranked compiler vendors), professional programmers, and a vast number of C++ literatures behind you.
Our program’s potential can only be limited by our own imagination, not some un-supported language features.

4.1 Basics of Finite Element Method

4.1.1 Mathematical Abstraction of Finite Element Method


Finite element method can be considered as a special case of variational methods, with special emphases on
the a systematic treatment of complicated geometry.

Finite Element—A Systematic Treatment for Complex Geometry


In finite element method, the approximation basis functions for the variable ue is defined in each subdo-
main—element Ωeh (see Figure 4•1, the subscript “e” denotes “element”, and “h” denotes element discretization
into a “characteristic size”—h)

u e ≅ u eh ≡ φ ea û ea, where a = 0, … ,n en – 1 Eq. 4•1

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

discretized global domain— Ω h

global domain —Ω

boundary—Γ

Figure 4•1 Geometry of global domain discretization.

266 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
number of nodal values ûea (“a” is the element node number, “hat” denotes a nodal value). The approximated
function, rewritten as u eh ( û ea ; x), is defined through a set of interpolation (basis) function φ ea on the element as
in Eq. 4•1. The space spans by these bases, φea , is known as the finite element space. The trio set ≡ { Ωeh , φea ,
u eh }, defined as a finite element is consists of (1) element (domain) “Ωe”, (2) interpolation functions “ φ ea ”, and
(3) degree of freedoms “ u eh ”.1
We have seen some examples of linear and quadratic interpolation functions for the purpose of numerical
integration in 1-D and 2-D in the Chapter 3. For example, interpolation functions for a bilinear four-node element
can be defined with the formula

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

x ( Ω e ) ≡ φ ea x ea, where a = 0, nen – 1 Eq. 4•6

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.

Workbook of Applications in VectorSpace C++ Library 267


Chapter 4 Finite Element Method Primer

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.

Finite Element Approximation


In the standard finite element method, the weighting functions, W, is taken as that in the Galerkin method in
the context of weighted residual methods (see page 232), which are the same as the element interpolation func-
tions φ ea in Eq. 4•1, but vanishing at boundaries corresponding to the essential boundary conditions; i.e.,

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.

268 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
For a self-adjoint operator, from Eq. 3•125 in Chapter 3, the finite element approximation, at each element,
gives

a ( φ ea, φeb )û eb = ( φ ea, f ) + ( φ ea, h ) Γ – a ( φ ea, φ eb )u eb Eq. 4•8

or in matrix forms

k eab û eb = f ea Eq. 4•9

where,

k eab = a ( φea, φ eb )

f ea = ( φ ea, f ) + ( φ ea, h ) Γ – a ( φ ea, φ eb )u eb Eq. 4•10

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.

Workbook of Applications in VectorSpace C++ Library 269


Chapter 4 Finite Element Method Primer
Global Matrix and Solution Phase—Systematic Treatment for Large-Size Degree of Freedoms
Eq. 4•8 to Eq. 4•10 are defined only on an element domain— Ωeh , while the variational principle needs to be
applied on the global discretized domain— Ω h ; i.e., the element stiffness matrix k eab and the element force vec-
tor f ea need to be assembled into a global stiffness matrix K and global force vector F as

K ij û j = Fi

K ij = A k eab, and F i = A f ea Eq. 4•12


∀e ∀e

where û j is the global nodal solution vector. The symbol A


∀e
stands for the procedure of assembly of all ele-
ments. The index “i” is the global equation number and index “j” is the global variable number of ûj .

4.1.2 Object-Oriented Modeling of the Finite Element Method


The central theme of the object-oriented programming is the data abstraction and inheritance. Firstly, the
data abstraction enabling software modules to be modeled after the real world objects. Each of such a software
module —class defines the states of an object as its member data, and the behaviors of the object as its member
functions. In the procedure programming method, data structure and algorithms (subroutines) performing on the
data structure are separate. In an abstract data type, they are organized into a coherent unit; i.e., the class. C++
also provides user access control mechanism to declare its member data or functions as private, protected, or
public, such that the complexities can be encapsulated inside the abstract data type. Secondly, the inheritance
relation enables factoring of common parts to define a more general base class higher in the class hierarchy.
More specific classes can be derived from the base class by adding details to facilitate the idea of programming
by specification and to enforce code reuse. The most impressive power comes out of this inheritance relation is
the dynamic binding mechanism provided to implement the concept of polymorphism. In C++ such mechanism
is provided by declare member functions as virtual. A call on the virtual function of a base class is dispatched by
the virtual function mechanism to the corresponding member function of the derived class, where a specific
behavior is actually defined. We explore all these programming concepts in the modeling of the finite element
library— “fe.lib”, in which the source codes are provided for demonstrating the object-oriented method.
Then, we go further on. The object-oriented paradigm is meant to replace the old-way—the procedure pro-
gramming. As we have mentioned earlier, the data and function are now organized together as a coherent
abstract data type—class. The objects are empowered with inheritance and virtual function mechanism. How-
ever, the dependency relations among the objects can grow to an extremely complicated network of objects. The
object-oriented analysis is applied on the problem domains to understand the dependency relations among
objects and the object-oriented design is the newly programming discipline taken to harness the rampant power
of C++. It sounds so familiar that we used to write “go to” among Fortran statements which has the potential to
grow into an extremely complicated flow chart (a network of statements). The procedure programming is the old
discipline proposed to rescue the old-world from chaos. Now, we introduce the object-oriented method and the
resultant complicated network of objects turns out to be a serious problem too. A new discipline, the object-ori-
ented design, is a lesson learned from a frequently cited costly experience from Mentor Graphics (one of the
world largest CAD company today), which is the very first company to attempt a large-scale C++ project1.

270 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
In the section on the mathematical abstraction of finite element method, only Eq. 4•8 to Eq. 4•10 contain the
core of mathematics of the differential equation problems. All other things in finite element method are really
complicated details. As we have mentioned earlier, the finite element method can be viewed as a systematic
treatment for these non-mathematical trivia. However, these trivia are no simple matter, which are actually quite
a challenging task that we will use object-oriented modeling for their implementation.

Step 1. Discretization Global Domain— Ω h


The first step of the finite element method is to discretize the problem domain into element— Ωeh . An element
Ω eh
is often described as simple geometrical area like triangles or quadrilaterals. The vertices for these simple
geometrical objects are called nodes with nodal coordinates as x . A node object is instantiated by its constructor

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.

Workbook of Applications in VectorSpace C++ Library 271


Chapter 4 Finite Element Method Primer

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

1 class Omega_h { // discretized global domain— Ω h


2 protected:
3 Dynamic_Array<Node> the_node_array;
4 Dynamic_Array<Omega_eh> the_omega_eh_array;
5 public:
6 Omega_h(); // declared by not defined
7 ...
8 };

272 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
12 13 14 15

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 Omega_h::Omega_h() { // define default constructor


2 int row_node_no = 4;
3 row_element_no = row_node_number -1;
4 double v[2];
5 for(int i = 0; i < row_node_no; i++)
6 for(int j = 0; j < row_node_no; j++) { // ena[3] ena[2]
7 int nn = i * row_node_no + j; // +-------------------+
8 v[0] = (double) j; v[1] = (double) i; // | |
9 Node *node = new Node(nn, 2, v); // | |
10 the_node_array.add(node); // | |
11 } // | |
12 int ena[4]; // +-------------------+
13 for(int i = 0; i < row_element_no; i++) // ena[0] ena[1]
14 for(int j = 0; j < row_element_no; j++) {
15 int nn = i * row_node_no + j; // node number at lower left corner
16 ena[0] = nn; ena[1] = ena[0] + 1;
17 ena[3] = nn + row_node_no; ena[2] = ena[3] +1;
18 int en = i * row_element_no + j; // element number

1. P.J. Plauger, 1995, “The draft standard C++ library”, Prentice-Hall, Inc., Englewood Cliffs, New Jersey.

Workbook of Applications in VectorSpace C++ Library 273


Chapter 4 Finite Element Method Primer
19 Omega_eh *elem =
20 new Omega_eh(en, 0, 0, 4, ena);
21 the_omega_eh_array.add(elem);
22 }
23 }

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)”.

Step 2. Free and Fixed Variables


The discretized global free degree of freedoms are (“hat” indicate a nodal value)

û 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.

274 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
5 double *the_value;
6 public:
7 operator[](int);
8 ...
9 };

Now the three classes are publicly derived from the base class “Nodal_Value” as

1 class Node : public Nodal_Value { ... }


2 class U_h : public Nodal_Value { ... }
3 class gh_on_Gamma_h : public Nodal_Value { ... }

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 nd[0]; // first coordinate value


2 uh[1]; // second degree of freedom
3 gh[0]; // first constraint values
The common part of the three classes are factored out to form a new base class “Nodal_Value”. The code will
be significantly duplicated, if we have not done so. In addition, factoring out this common part is good for the
maintenance of the code. If we have found out in the future that the way we modeled the “nodal values” is unsat-
isfactory, changes made in this single class are sufficient comparing to changes needed to be made in all three
classes. In general, the object-oriented programming method not only use data abstraction to organize data and
functions (the algorithm operating upon data), it also help to classify these software modules, which are modeled
after real world objects, into a hierarchical structure. We note that classification of things into hierarchical struc-
ture is one of the most powerful tools that human beings have to built knowledge.
We now consider an example of heat conduction (see Figure 4•5) using the discretized global domain,
declared as “oh” previously, and was illustrated in Figure 4•4. The number of degree of freedom “ndf” = 1; i.e.,
the temperature. We should instantiate, in the “main()” program, the variable “uh” of class U_h, and the bound-
ary conditions “gh” of class gh_on_Gamma_h as the followings

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-

Workbook of Applications in VectorSpace C++ Library 275


Chapter 4 Finite Element Method Primer

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

1 gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& oh) {


2 __initialization(df, omega_h);
3 int row_node_no = 4;
4 for(int i = 0; i < row_node_no; i++) {
5 the_gh_array[node_order(i)](0) = gh_on_Gamma_h::Dirichlet;
6 the_gh_array[node_order(row_node_no*(row_node_no-1)+i)](0) = gh_on_Gamma_h::Dirichlet;
7 the_gh_array[node_order(row_node_no*(row_node_no-1)+i)][0] = 30.0;
8 }
9 }

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.

276 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
Step 3. Element Formulation
At the very heart of finite element program is the element formulation. This part does every thing that is most
relevant to the variational methods we have introduced in Chapter 3. Henceforth, this part is highly mathemati-
cal. The VectorSpace C++ Library is therefore most heavily used in the element formulation. For every differen-
tial equation problem, the element formulation is different. The impact of change to the code from one problem
to the other is a routine rather than an exception. Under the procedure programming paradigm, it is soon recog-
nized that an element subroutine should be used to form an replaceable module. In object-oriented programming,
further flexibility for element formulation can be obtained through the polymorphism supported in C++.
We have seen that for data abstraction C++ provides class to organize data and functions into a coherent
object. The inheritance is provided to build hierarchical structure of objects and enable code reuse. Now the
objects put into the hierarchical structure can be made to be intelligent to perform some autonomous tasks. For
example, we may have a base class of “Animal”. Then, we derived from this class of “Animal” to form classes of
“Lion”, “Horse”, and “Whale”. Next, we declare three instances “lion”, “horse”, and “whale” of general type
“Animal”, each of polymorphic concrete types “Lion”, “Horse”, and “Whale”. Now, God says “Animals eat food
!” The “lion” goes to catch a zebra, the “horse” bites grass, and the “whale” catches tons of fishes. The advantage
of this higher level of intelligent is enormous. Now we can have one single generic command for all kinds of des-
perately different individual objects.
A simple algebraic example is described in root-finding problem in page 40 of Chapter 1, where the New-
ton’s formula gives the increment of solution dx as

dx = - f / df

The corresponding C++ code can be written as a function

C0 dx(const C0& f, const C0& df) { return - 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

Workbook of Applications in VectorSpace C++ Library 277


Chapter 4 Finite Element Method Primer
1 class HeatQ4 : public Element_Formulation {
2 public:
3 HeatQ4(Element_Type_Register a) : Element_Formulation(a) {}
4 Element_Fomulation *make(int, Global_Discretization&);
5 HeatQ4(int, Global_Discretization&);
6 };
7 Element_Formulation* HeatQ4::make(int en, Global_Discretization& gd) {return new HeatQ4(en, gd);}
8 HeatQ4::HeatQ4(int en, Global_Discretization&) : Element_Formulation(en, gd) {
9 ...
10 }

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.

278 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method

Symbol Element_Formulation
rep_ptr

Content Element_Formulation

HeatQ4

Figure 4•6 Emulating symbolic language features using C++.

of HeatQ4. This is done by invoking “Element_Formulation* HeatQ4::make(int, Global_Discretization&)” to


produce a pointer to HeatQ4 instance. We also see that the two public member functions lhs() and rhs() are for-
warding, by its delegate “rep_ptr”, to its derived class protected member functions __lhs() and __rhs(), in the
present case, forwarding to an instance of HeatQ4’s two protected virtual member function __lhs() and __rhs().
The default behaviors of these two protected virtual member function has been defined to return element stiffness
matrix and element force vector.
We have explained the mechanisms built for polymorphism. Now we can consider how the impact of change
bring out by nonlinear and transient problems can be accommodated under this design. For a nonlinear problem
the solution is obtained from an iterative scheme u i+1 = ui + δui for the convergence of the residual vector R = F
- K(u) u (from Eq. 4•12) defined as

∂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

1 class Nonlinear : public Element_Formulation {


2 C0 ul;
3 void __initialization(int, Global_Discretization&) { ul &= gd.element_free_variable(en); }
4 public:

Workbook of Applications in VectorSpace C++ Library 279


Chapter 4 Finite Element Method Primer
5 Nonlinear(int, Global_Discretization&);
6 ...
7 };
8 Nonlinear::Nonlinear(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
9 __initialization(en, gd);
10 ...
11 };

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

( M + ∆tθK ) û n + 1 = ( M – ∆t ( 1 – θ )K ) û n – f Eq. 4•15

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”.

1 class Transient::public Element_Formulation {


2 C0 mass, ul;
3 void __initialization(int, Global_Discretization&) { ul &= gd.element_free_variable(en); }
4 public:
5 Transient(Global_Discretization&);
6 ...
7 C0& __lhs();
8 C0& __rhs();
9 };
10 Transient::Transient(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
11 __initialization(en, gd);
12 ...
13 };
14 static double theta = 0.5, dt = 0.01; // central difference θ = 0.5
15 C0& Transient::__lhs() {
16 the_lhs &= mass + dt*theta *stiff; // M + ∆tθK
17 return the_lhs;
18 }
19 C0& Transient::__rhs() {
20 Element_Formulation::__rhs(); // - f; the default force vector
21 the_rhs += (mass -dt*(1-theta)*stiff)*ul; // ( M – ∆t ( 1 – θ )K ) u n – f

280 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
22 return the_rhs;
23 }

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

virtual C0& __lhs() { return stiff; }


virtual C0& __rhs() { return force; }

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

1 Element_Fomulation* Element_Formulation::type_list = 0; // register element type


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 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

1 HeatQ4::HeatQ4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {


2 Quadrature qp(2, 4);
3 H1 Z(2, (double*)0, qp), // natrual coordinates
4 N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(4, 2, qp),
5 Zai, Eta; // alias
6 Zai &= Z[0]; Eta &= Z[1];

Workbook of Applications in VectorSpace C++ Library 281


Chapter 4 Finite Element Method Primer
1
7 N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4; // N a ( ξ, η ) = --- ( 1 + ξa ξ ) ( 1 + η a η )
4
8 N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4;
9 H1 X = N*xl // coordinate transformation
10 H0 Nx = d(N)*d(X).inverse(); // derivative of shape functions
11 J dv(d(X).det()); // the Jacobian
12 double k = 1.0, q = 1.0; // conductivity and heat source
13 stiff &= (Nx * k * (~Nx)) | dv; // element stiffness matrix
14 force &= (((H0)N)*q) | dv; // element force vector
15 }

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;
}

The the “reaction” is added to the element force vector as

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

assembly(FALSE); // FALSE turns off nodal force loading

282 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
Then, redefine “__rhs()” in user defined element. In the definition of the user element, we can define boundary
integration of these two terms to the element force vector. The basic idea is just like we can overwrite the virtual
functions “__lhs()” and “__rhs()” for the transient problem.
Now we have shown that object-oriented programming does provide unprecedented flexibility to implement
seemly incompatible problems in finite element method. Most importantly, the flexibility does not come by sac-
rifying the organization or simplicity of the code. A beginner of “fe.lib” can always study the same simple kernel
code. The kernel code does not grow because of the irrelevant details have been added during the course of evo-
lution of “fe.lib” to encompass more advanced problems. The “code-reuse” and “programming by specification”
can be repeated applied to the “fe.lib” relentlessly, while the very kernel of the “fe.lib” may reside in the ever
grander architecture un-disturbed.

Step 4. Matrix Representation and Solution Phase


The user’s code for the steps of matrix representation and solution phase is

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

Workbook of Applications in VectorSpace C++ Library 283


Chapter 4 Finite Element Method Primer

Element connectivity Step 4a: eliminate fixed degree of freedoms

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.

284 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
there are many matrix computational methods specifically designed for the finite element method. To name a
few, profiled sparse matrix, frontal method, and nested dissection1. These methods are not supported by “fe.lib”.
However, we reserve an entry point to declare the Matrix_Representation as
Matrix_Representation mr(gd, “ ... user defined string to identify special matrix ...”);.

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.

4.1.3 Object-Oriented Analysis and Design of Finite Element Method


As in many books on object-oriented analysis and design have suggested, we define that the object-oriented
analysis is to understand the object dependency relation, and the object-oriented design is the discipline to man-
age the potentially complicated dependency relation among objects.
We may think of analysis and design probably is the first thing to consider, logically, even before the model-
ing in the previous section. However, an experienced programmer will point out that the nature of the program-
ming is more like an iterative process that one goes over again and again from analysis/design to modeling then
re-analysis/re-design and then to re-modeling. Some problems are unraveled only after first model has been pro-
posed. In this perspective, the modeling in the previous section provides us the materials to begin with for analy-
sis and design process.

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.

Workbook of Applications in VectorSpace C++ Library 285


Chapter 4 Finite Element Method Primer

tetrahedron planner graph


1
Ωh
uh 6 EF
0 3
uh 5 2
Ωh
4

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.

Graph Level Structure


A complicated network such as the one in Figure 4•8 may look aesthetically pleasant, but it isn’t the best for
human mind to comprehend. A clique is formed if we starts the flows of dependency steps from node MR to EF
then to u h it goes right back to MR itself. The members in a clique depend on each other so strongly that they
are not separable. It is much easier to understand if the relation is hierarchical. In our mind we only need to pic-
ture a simple sequence of states and top-dwon relations. We would like to change the graph into a level structure
such as a tree or even better a simple chain. These are same structures that we always preferred in procedure pro-
gramming method. Therefore, we proceed to sort out the planner graph into a graph level structure.

286 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
First we compare the degree of entrance of the four components (see TABLE 4•1) to transform, by escalation
and demotion1 of nodes on the planner graph in Figure 4•8, into a graph level structure.

Component Degree of entrance


Ωh 3
uh 2
EF 2
MR 1
TABLE 4•1 Degree of entrance of the four components.

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

1. J. Lakos, 1996, “Large-scale C++ software design”, Addison-Wesley, Reading, Massachusetts.

Workbook of Applications in VectorSpace C++ Library 287


Chapter 4 Finite Element Method Primer

(a) levelization (b) simplify to a chain


Level 0 Ωh Ωh
0
4 0
3 eliminate
Level 1 uh redundant uh
1 dependancies
6 1
5
Level 2 EF EF 6
2 2

Level 3 MR MR
7 7

Figure 4•9levelization of non-hierachical network into a level structure then to a


chain. The entrances 6 and 7 remained. We need to apply C++ levelization
idioms to reslove them.

C. Four separate files “u_h.h”, “u_h.cpp”, “matrix_representation.h” and “matrix_representation.cpp”, are


shown in the followings.

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:

288 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
20 void __initialization(char *s);
21 ...
22 };

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

Workbook of Applications in VectorSpace C++ Library 289


Chapter 4 Finite Element Method Primer
Matrix_Representation as the pointer to Matrix_Representation in the class U_h. We break up the dependency
relation using forward declaration now we reconnect them when an instance of class Matrix_Representation is
initiated. This closes the cyclic dependency relation, at link-time, that was broken at compile-time for making an
independent module of class U_h. Furthermore, the definitions of three public member operators “=”, “+=”, and
“-=”, which map the equation number of solution vector back to global degree of freedoms for output, are push
down the hierarchical levels. They are not defined in “u_h.cpp” with other class U_h member functions, because
the independent module class U_h has no idea what is a class Matrix_Represenation, let alone to access its infor-
mation for the mapping. Therefore, these three public member functions of class U_h are defined in
“matrix_representation.cpp” with other member functions of class Matrix_Representation. Certainly, had we not
defined these three operators anywhere, at link-time, the linker will refuse to build the executable module and
will complain that these three operators, declared in “u_h.h”, are un-resolved external references.

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

Omega_eh *elem = new Omega_eh(element_number, element_type_number,


material_number, nodes_per_element,
node_number_array);

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 class Element_Type_Register { public: Element_Type_Register() {} };


2 class Element_Formulation {
3 Global_Discretization& the_global_discretization;
4 ...
5 public:
6 static Element_Formulation *type_list;
7 Element_Formulation *next;
8 Element_Formulation(Element_Type_Register) :

1. see autonomous generic constructor in J. O. Coplien, 1992, “ Advanced C++: Programming styles and idioms”, Addison-
Wesley, Reading, Massachusetts.

290 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
9 the_global_discretization(Globa_Discretization()) { next = type_list; type_list = this; }
10 Element_Formulation& create(int, Global_Discretization&);
11 virtual Element_Formulation* make(int, Globa_Discretization&);
12 ...
13 };

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

1 Element_Formulation *element_type = Element_Formulation::type_list;


2 for(int i = 0; i < element_type_number; i++) element_type = element_type->next;
3 Element_Formulation ef = element_type->create(element_no, the_global_discretization);

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++.

Composite Class from a Dependency Graph


In Figure 4•8 and Figure 4•9, the four nodes are actually the software modules in “fe.lib” which are consist of
the classes. A class dependency graph, not including all classes, is shown in Figure 4•10. The entire picture is
much more complicated one. The definition of a compoiste class is similar to the partitioning of the graph to a
(quotient) tree structure with sets of composite vertices as composite nodes. In software design, the choice of the
composite class is somewhat more arbitrary than that of composite vertices in graph theory; as long as it is con-
ceptual meaningful to emphasis the essential and eliminate the irrelevant (i.e., the process of abstraction). For
example, it makes all sense to combined the level 0 and level 1 together and called it a Global_Discretization,
which is a discretization made to both the domain and the variables. We can even combined the
Global_Discretization class and Element_Formulation class to form a new conceptual class of
“Finite_Element_Approximation”. In this way, the designer may want to emphasize that the finite element
method is mainly consist of only two steps. One step is the finite element approximation, and the other step is the
solution in its matrix form. The coalescence of several composite classes into yet higher level of composite class
shows that the recognition of a composite class may depend on design decision on what conceptual abstraction
the designer wants to emphasize (an art), not just physical dependency relations and technical requirements to
separate them. Sometimes, the decision depends on the intent of the final product. For a product designed to be
used as a canned program, the abstraction can be put to a coherently higher level in which all the details are
encapsulated from the end users as much as possible. On the contrary, if the product is intended to be open, such
as “fe.lib” that large-scale change to the backbone structure of the program is to be permissible. Abstraction is
put down to a granularly lower level to facilitate the re-use of each composite class and therefore more flexibility
for change.

Workbook of Applications in VectorSpace C++ Library 291


Chapter 4 Finite Element Method Primer

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

Figure 4•10Composite class in the hierachical level structure.

292 Workbook of Applications in VectorSpace C++ Library


Basics of Finite Element Method
4.1.4 A Program Template for Using “fe.lib”
We summarize the Section 4.1 with a template for using fe.lib to write finite element programs. It is very
much like we have an extended C++ language features that are specialized in finite element method. “fe.lib” is a
framework-based package very similar to if you are writing a graphic user interface (GUI) program. In GUI pro-
gramming, there are some routine code that you need to incorporate with its framework to make the GUI kernel
up and running. On the other hand, since finite element method requires a lot of user input to specified the prob-
lem, the fe.lib acts much like a database engine that you write a database language to define the database
schema, manipulate the data and query its contents. The fancy term client-server package may even more appro-
priate for “fe.lib”. The client-server packages for writing business applications provide a high-level library for
routine database services and GUI interfaces. Under such model, the fe.lib is the server that provides the basic
mechanisms in finite element method for user programs to implement their own design policies in the vast area
of finite element problem domain.
A user program template is illustrated in the followings

//==========================================================================
// Step 1: Global_Discretization
//==========================================================================

1 Omega_h::Omega_h{ // define discretizaed global domain


// define nodes
2 ...
// define elements
3 ...
4 }
5 gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& oh) { // define boundary conditions
6 __initiialization(df, oh); // initialize internal data structure
// define b.c.
7 ...
8 }

//==========================================================================
// Step 2: Element_Formulation
//==========================================================================

9 class UserEL : public Element_Formulation { // define user element


10 public:
11 UserEL(Element_Type_Register a) : Element_Formulation(a) {}
12 Element_Formulation *make(int, Global_Discretization&);
13 UserEL(int, Global_Discretization&);
14 };
15 Element_Formulation* UserEL::make(int en, Global_Discretization& gd) {
16 return new UserEL(en, gd);
17 }
18 UserEL::UserEL(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {

Workbook of Applications in VectorSpace C++ Library 293


Chapter 4 Finite Element Method Primer
// define element formulation constructor
19 ...
20 }
21 Element_Formulation* Element_Formulation::type_list = 0; // register elements
22 Element_Type_Register element_type_register_instance;
23 static UserEL userel_instance(element_type_register_instance);

//==========================================================================
// 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.

294 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
4.2 One Dimensional Problems
We intent to go through many proto-type problems, in one dimension, to demonstrate a wide mathematical
variety in the finite element method.

4.2.1 A Second-Order Ordinary Differential Equation (ODE)


Considering a second-order differential equation we have solved using Rayleigh-Ritz method (Eq. 3•55 of
Chapter 3 in page 201)1

2
du
– 2
= cos πx, 0 < x < 1 Eq. 4•16
dx

with three sets of different boundary conditions

1. Dirichlet boundary conditions—u(0) = u(1) = 0


2. Neumann boundary condition—u’(0) = u’(1) = 0
3. Mixed boundary conditions—u(0) = 0, and u’(1) = 0

The Galerkin weak formulation is

a(φei, φej) - (φei , f) =


1 1 1 1
d 2 φ ej dφ ei dφ ej dφ j dφei dφ ej
∫  φei ----------
dx 2
- – φ ei cos πx dx =
 ∫  – --------
dx dx
- --------- + φ ei cos πx dx + φ ei --------e-
 dx 0
= ∫  – --------
dx dx
- --------- + φ ei cos πx dx
 = 0 Eq. 4•17
0 0 0

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

and the element force vector as

fei = ( φ ei , f ) + ( φ ei , h ) Γ – a ( φ ei , φ ej )u ej = ∫ ( φei cos πx )dx Eq. 4•19


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.

Workbook of Applications in VectorSpace C++ Library 295


Chapter 4 Finite Element Method Primer
nous conditions, the default behaviors of “fe.lib” will deal with these two terms behind the scene as long as you
have not overwritten them as we have discussed in the previous section.

Linear Line Element


We can choose the linear interpolation functions for both variable interpolation u eh ≡ φ ei û ei (Eq. 4•1) and
coordinate transformation rule x ≡ φ ei x ei (Eq. 4•6); i.e., an isoparametric element as

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

1 double v = (double)i/(double)element_no; // nodal coordinates, 0 < x < 1


2 Node *node = new Node(global_node_number,
3 spatial_dimension_number,
4 &v);
5 the_node_array.add(node);

The elements are defined with global node number associated with the element as

1 int ena[2]; ena[0] = first_node_number; ena[1] = ena[0]+1;


2 Omega_eh* elem = new Omega_eh(element_number,
3 element_type_number,
4 matrial_type_number,
5 number_of_node_per_element,
6 ena);
7 the_omega_eh_array.add(elem);

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;

296 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems

#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”).

Workbook of Applications in VectorSpace C++ Library 297


Chapter 4 Finite Element Method Primer
8 }
9 #elif defined(__TEST_NEUMANN_BOUNDARY_CONDITION)
10 gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
11 __initialization(df, omega_h);
12 the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Neumann;
13 the_gh_array[node_order(0)][0] = 0.0;
14 the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Neumann;
15 the_gh_array[node_order(node_no-1)][0] = 0.0;
16 the_gh_array[node_order((node_no-1)/2)](0) = gh_on_Gamma_h::Dirichlet;
17 the_gh_array[node_order((node_no-1)/2)][0] = 0.0;
18 }
19 #else
20 gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
21 __initialization(df, omega_h);
22 the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet;
23 the_gh_array[node_order(0)][0] = 0.0;
24 the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Dirichlet;
25 the_gh_array[node_order(node_no-1)][0] = 0.0;
26 }
27 #endif

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

1 class ODE_2nd_Order : public Element_Formulation {


2 public:
3 ODE_2nd_Order(Element_Type_Register a) : Element_Formulation(a) {}
4 Element_Formulation *make(int, Global_Discretization&);
5 ODE_2nd_Order(int, Global_Discretization&);
6 };
7 Element_Formulation* ODE_2nd_Order::make(int en, Global_Discretization& gd) {
8 return new ODE_2nd_Order(en,gd);
9 }
10 Element_Formulation* Element_Formulation::type_list = 0;

298 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
11 static Element_Type_Register element_type_register_instance;
12 static ODE_2nd_Order ode_2nd_order_instance(element_type_register_instance);

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

1 static const double PI = 3.14159265359;


2 ODE_2nd_Order::ODE_2nd_Order(int en, Global_Discretization& gd)
3 : Element_Formulation(en, gd) {
4 Quadrature qp(spatial_dim_no, 2); // 1d, 2-pts Gauss quadrature
5 H1 Z(qp), // natural coordinate—ξ
6 N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( // “shape functions”
7 "int, int, Quadrature", 2/*nen*/, 1/*nsd*/, qp);
8 N[0] = (1-Z)/2; N[1] = (1+Z)/2; // N0=(1-ξ)/2, N1 = (1+ξ)/2
9 H1 X = N*xl; // coordinate transformation x ≡ N i x ei
10 H0 Nx = d(N)(0)/d(X); // N,x
11 J dv(d(X)); // the Jacobian, X,ξ
12 stiff &= (Nx * (~Nx)) | dv; 1
dφei dφ ej
13 1 force &= ( ((H0)N)*cos(PI*((H0)X)) )| dv; // ke =∫  --------- --------- dx ,
ij and
dx dx
f ei= ∫ φ ei cos πx dx 0
14 0 }

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

stiff &= (Nx%Nx) | dv;


The instantiation of global discretized domain, fixed and free variables, and matrix representation and solu-
tion phase are taken directly from the template without modification

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

Workbook of Applications in VectorSpace C++ Library 299


Chapter 4 Finite Element Method Primer
10 gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h(); // update free and fixed degree of freedom
11 cout << gd.u_h(); // output solution
12 return 0;
13}

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.

Dirichlet Neumann Mixed


0.1
0.02 0.2 0.4 0.6 0.8 1

0.01 0.05 -0.05

1
-0.1
0.2 0.4 0.6 0.8 1 0.2 0.4 0.6 0.8

-0.01 -0.05 -0.15

-0.02 -0.1 -0.2

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.

Quadratic Line Element


The quadratic interpolation functions for both variable interpolation u eh ≡ φ ei û ei (Eq. 4•1) and coordinate
transformation rule x ≡ φ ei x ei (Eq. 4•6) are

–ξ ξ
φ 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

1 static const int node_no = 5;


2 static const int element_no = 2;
3 static const int spatial_dim_no = 1;
4 Omega_h::Omega_h() {
5 for(int i = 0; i < node_no; i++) {
6 double v; v = ((double)i)/((double)(node_no-1));
7 Node* node = new Node(i, spatial_dim_no, &v);
8 the_node_array.add(node);

300 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems

#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”).

Workbook of Applications in VectorSpace C++ Library 301


Chapter 4 Finite Element Method Primer
9 }
10 int ena[3];
11 for(int i = 0; i < element_no; i++) {
12 ena[0] = i*2; ena[1] = ena[0]+1; ena[2] = ena[0]+2;
13 Omega_eh* elem = new Omega_eh(i, 0, 0, 3, ena);
14 the_omega_eh_array.add(elem);
15 }
16 }

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.

Dirichlet Neumann Mixed


0.02 0.1
0.2 0.4 0.6 0.8 1

0.01 0.05 -0.05

0.2 0.4 0.6 0.8 1 -0.1


0.2 0.4 0.6 0.8 1

-0.01 -0.05 -0.15

-0.02 -0.1 -0.2

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.

Cylindrical Coordinates For Axisymmetrical Problem


In cylindrical coordinates (r, θ, z), the Laplace operator is written as1

1 ∂ ∂u 1 ∂2u ∂2u
∇ 2 u = --- -----  r ------ + ---2- --------2- + --------2 Eq. 4•23
r ∂r  ∂r  r ∂θ ∂z

We consider an axisymmetrical heat conduction problem governing by the Laplace equation – ∇ 2 u = 0


shown in Figure 4•13.2 This is a cross-section of two coaxial hollow cylinders. The inner and outer cylinder

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.

302 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems

50mm
31.6mm
κ=5 κ=1
20mm r 100oC 0oC
20mm 31.6mm 50mm
κ=5
κ=1

Figure 4•13Cross-section of two hollow cylinder with diffusivity of k = 5, and k =


1 for the inner and outer cylinder, respectively.
have different thermal diffusivity “5” and “1”, respectively. For this axisymmetrical problem u depends only on
r, the second and the third terms in the left-hand-side of Eq. 4•23 dropped out. The Laplace equation becomes

– --- -----  κr ------ = 0


1d du
Eq. 4•24
r dr  dr 

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

The C++ code for Eq. 4•25 is

“stiff &= (kapa[matrial_type_no] *(Nr%Nr)*2*PI((H0)r) ) | dr”

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.

Workbook of Applications in VectorSpace C++ Library 303


Chapter 4 Finite Element Method Primer

#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”).

304 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
4.2.2 A Fourth-Order ODE —the Beam Bending Problem
We recall, from the last chapter in the sub-section on fourth-order ODE (in page 205), that from balance of
force, the transverse loading (f) is equal to the derivative of shear force (V) as

dV/dx =- f Eq. 4•26

and the shear force V is equal to the derivative of bending moment (M) as

dM/dx =- V Eq. 4•27

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.

Workbook of Applications in VectorSpace C++ Library 305


Chapter 4 Finite Element Method Primer
Irreducible Formulation—Piecewise Cubic Hermite Shape Functions
The Lagrangian functional is obtained from integrating by parts twice on the weighted residual statement
from Eq. 4•30

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 
 

Dropping ε, since it is arbitrary, we have

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

u eh ≡ φei û ei Eq. 4•34

where the piecewise cubic Hermit shape functions φ ei , i = 0, 1, 2, 3 are1, 2

1. see derivation in p. 383 in J.N. Reddy, 1986, “Applied functional analysis and variational methods in engineering”,
McGraw-Hill, Inc.

306 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
ξ 2 ξ 3
φ e0 = 1 – 3  ----- + 2  -----
 h e  h e

ξ 2
φ e1 = – ξ 1 –  -----
 h e

ξ 2 ξ 3
φ e2 = 3  ----- – 2  -----
he he

ξ 2 ξ
φe3 = – ξ  ----- –  ----- Eq. 4•35
 h e  h e

The element stiffness matrix is

2 2
 d φ e d φ e
k e = a ( φ e, φ e ) = ∫ EI  2 ⊗ 2  dx
dx dx 
Eq. 4•36
Ωe

The element force vector is

f ei = ( φ ei , f ) + ( φei , h ) Γ – a ( φ ei , φ ej )u ej Eq. 4•37

dw dw
where essential boundary conditions are u e = [w0, – , w1, – ], and
dx 0 dx 1

( φ ei , f ) = ∫ φei fdx, and ( φei , h ) Γ = ∫ φei Pdx Eq. 4•38


Ωe Γ

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.

Workbook of Applications in VectorSpace C++ Library 307


Chapter 4 Finite Element Method Primer

#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”).

308 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
0.00001
0.0001
-6
Error 8. 10
0.00005
∆w -6
Error
6. 10
x
-6 ∆ dw 0.2 0.4 0.6 0.8 1
4. 10 dx
-0.00005
-6
2. 10
-0.0001
0.2 0.4 0.6 0.8 1
x

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

120 in. 240 in.

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.

Workbook of Applications in VectorSpace C++ Library 309


Chapter 4 Finite Element Method Primer
11 int ena[2];
12 for(int i = 0; i < element_no; i++) {
13 ena[0] = i; ena[1] = ena[0]+1;
14 Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena);
15 the_omega_eh_array.add(elem);
16 }
17 }
18 gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) { // boundary conditions
19 __initialization(df, omega_h);
20 the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet; // w(0) = 0
21 the_gh_array[node_order(0)][0] = 0.0;
22 the_gh_array[node_order(1)](0) = gh_on_Gamma_h::Neumann; // P(120) = -1.0
23 the_gh_array[node_order(1)][0] = -1.0;
24 the_gh_array[node_order(2)](0) = gh_on_Gamma_h::Dirichlet; // w(360) = 0
25 the_gh_array[node_order(2)][0] = 0.0;
26 }

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

50 100 150 200 250 300 350


x
-0.00005 -6
1. 10

-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.

310 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
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);
8 the_node_array.add(node);
9 }
10 int ena[2]; // element node number array
11 for(int i = 0; i < element_no; i++) {
12 ena[0] = i; ena[1] = ena[0]+1;
13 Omega_eh* elem = new Omega_eh(i, 0, 0, 2, ena);
14 the_omega_eh_array.add(elem);
15 }
16 }
17 gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) { // boundary conditions
18 __initialization(df, omega_h);
19 the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet; // w(0) = 0
20 the_gh_array[node_order(0)][0] = 0.0;
21 the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Dirichlet; // w(L) = 0
22 the_gh_array[node_order(node_no-1)][0] = 0.0;
23 the_gh_array[node_order(node_no-1)](1) = gh_on_Gamma_h::Dirichlet; // dw/dx(L) = 0
24 the_gh_array[node_order(node_no-1)][1] = 0.0;
25 }

In the constructor of the class Beam_Irreducible_Formulation the element force vector is computed as

1 H0 X = (1-((H0)z))*xl[0]+((H0)z)*xl[1], // global coordinates; xl is the nodal coordinates


2 f = (f_0/L_)*X; // distributed load function
3 force &= ( ((H0)N) * f) | d_l;

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.

Workbook of Applications in VectorSpace C++ Library 311


Chapter 4 Finite Element Method Primer
Mixed Formulation
In the irreducible formulation the second derivative appears in the weak formulation. We use the cubic Her-
mite functions, however, these interpolation functions are quite formidable. In the mixed formulation, we trade
somewhat more complicated variational formulations for reducing the order of derivative to satisfy the continu-
ity requirement (stated earlier in page 268). That is if “n” order derivative appears in the weak formulation, we
should have Cn-1-continuity at the nodes, in order to have entire domain to be integrable. For example, the first
derivative w,x is included in the nodal variables in the irreducible formulation in the last section, which has sec-
ond derivative in the weak formulation. In the cases of higher dimensions, e.g., plate and shell, the irreducible
formulations always lead to extremely complicated schemes. The current trend for these problems is to develop
formulations that requires only C0-continuity.1
Recall Eq. 4•28 and Eq. 4•29

2 2
dw M dM
= ---------, and = f Eq. 4•40
dx2 2EI dx2

Integration by parts on both equations, we have the Lagrangian functional

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.

312 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems

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.

Workbook of Applications in VectorSpace C++ Library 313


Chapter 4 Finite Element Method Primer

#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”).

314 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
1.5
0.6

0.5 1.4

0.4
1.3
w 0.3 M
1.2
0.2
1.1
0.1

0.2 0.4 0.6 0.8 1


x 0.2 0.4 0.6 0.8 1
x

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,

wexact(x) = ((2M+fL2)/4EI) x2 - fL/(6EI) x3 + f/(24EI) x4, and


Mexact(x) = f/2 (x-L)2 + M Eq. 4•47
Now we proceed to the same (1) nodal loading and (2) distributed loading cases solved in the irreducible for-
mulation. For the nodal loading case, the code for the definition of the problem gives

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 }

Workbook of Applications in VectorSpace C++ Library 315


Chapter 4 Finite Element Method Primer

50 100 150 200 250 300 350


x 80

-0.00005 60

-0.0001
w M 40
-0.00015
20
-0.0002

50 100 150 200 250 300 350 x

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 }

316 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
The element force vector in the constructor of class “Beam_Mixed_Formulation” is to define the loading function

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 );

The results of this problem are shown in Figure 4•21.

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

Figure 4•21Transverse deflection w and bending moment M for the distributed


loading problem using linear interpolation functions for both w and M.

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.

Workbook of Applications in VectorSpace C++ Library 317


Chapter 4 Finite Element Method Primer
Lagrange Multiplier Formulation
Recall Eq. 4•32 that the Lagrangian functional for the irreducible formulation is

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

318 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems

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..

Workbook of Applications in VectorSpace C++ Library 319


Chapter 4 Finite Element Method Primer

#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”).

320 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems

0.2 0.4 0.6 0.8 1 x 0.6


1

-0.2 0.5 0.8

-0.4 0.4
ψ w λ0.6
-0.6 0.3
0.4
-0.8 0.2
0.2
-1 0.1

0.2 0.4 0.6 0.8 1


x 0.2 0.4 0.6 0.8 1
x

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-

Workbook of Applications in VectorSpace C++ Library 321


Chapter 4 Finite Element Method Primer

ψ 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 }

The element force vector is implemented as

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;

322 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
The results of the distributed load case are un-acceptable that the solution of λ and ψ show oscillation, while
transverse deflection w is partially “locking” which systematically underestimates the magnitude of the exact
solution (see Figure 4•24).
ψ w λ
1.5 10
-6 0.2 0.4 0.6 0.8 1 x 100
-6 -0.00002
1. 10 75
-7
5. 10 -0.00004 50
0.2 0.4 0.6 0.8 1 x
-7 -0.00006 25
-5. 10

-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.

Workbook of Applications in VectorSpace C++ Library 323


Chapter 4 Finite Element Method Primer
Penalty Function Formulation
From Eq. 4•49 and Eq. 4•50, the Lagrangian functional for the penalty function formulation can be written in
the form of Eq. 2•61 of Chapter 2 in page 153

ρ 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

324 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
gradually. In principle the, exact solution is obtained at ρ → ∞ . However, when ρ is too large the left-hand-side
matrix in Eq. 4•59 becomes ill-conditioned. The solution will be corrupted.
The Program Listing 4•7 implements Eq. 4•59 with an ad hoc penalty iterative procedure which find a local
minimum solution with respect to “w” by monitoring the convergence of “∆w”. When the divergence of ∆w first
occurs we terminate the penalty loop The choice of this termination criterion is that we do not have the value of
the original objective functional available for determining the convergence of this problem. The solutions are
shown in Figure 4•25.
In general, the two constrained cases using lagrange multiplier formulation and penalty function formulation
do not work well. The penalty method is also not very efficient. Sometimes, the results are even disastrous. The
conditions to obtain an accurate formulation in constrained formulations were area of intensive interest in the
development of the finite element method. We devote entire Section 5.1 to this issue with some canonical formu-
lations in two-dimension are discussed in details.

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

0.2 0.4 0.6 0.8 1


x

Figure 4•25 The solutions of end-bending moment case with penalty formulation.

Workbook of Applications in VectorSpace C++ Library 325


Chapter 4 Finite Element Method Primer

#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”).

326 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
4.2.3 Nonlinear ODE
Consider the nonlinear problem in Chapter 3 (page 236)

2
du 2
u 2 +   = 1,
du
0 < x < 1, with u’( 0 ) = 0, and u ( 1 ) = 2 Eq. 4•60
dx  d x

with exact solution

u exact ( x ) = 1 + x2 Eq. 4•61

Eq. 4•60 can be rewritten as,

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

Define the residuals of the problem as

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

Integrating by parts on the first term gives the weak formulation

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.

Workbook of Applications in VectorSpace C++ Library 327


Chapter 4 Finite Element Method Primer

∂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

where the tangent stiffness matrix, IT, can be defined as

 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”

1 static int initial_newton_flag;


2 void Non_Linear_ODE_Quadratic::__initialization(int en) {
3 ul &= gd.element_free_variable(en) + gd.element_fixed_variable(en);
4 if(!initial_newton_flag) gl = 0.0;
5 }

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

328 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems

#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”).

Workbook of Applications in VectorSpace C++ Library 329


Chapter 4 Finite Element Method Primer
H1 U = N*ul; // u h ≡ φ i û ei
e e
H0 Ux = d(U)/d(X); // due/dx

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

Figure 4•26 Nonlinear finite element method using Galerkin formulation.

330 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
Least Squares Formulation
The basic idea of the least squares method is introduced in Eq. 1•26 of Chapter 1 in page 35. The first-order
condition for the minimization of the squares of the residual (Euclidean-) norm is

∂ R ( u h ) 22 ∂R ( u h )
------------------------- = 2  ------------------, R ( u h ) = 0 Eq. 4•71
∂u h  ∂u h 

Comparing to the weighted-residual statement ( w, R ( u h ) ) = 0 , the weighting function w is

∂R ( u h )
w = -----------------
- Eq. 4•72
∂u h

For a non-linear problem, we define

∂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

The second derivatives is

∂ 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

Workbook of Applications in VectorSpace C++ Library 331


Chapter 4 Finite Element Method Primer

∂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

1 double weight[3] = {1.0/3.0, 4.0/3.0, 1.0/3.0},


2 h_e = fabs( ((double)(xl[0] - xl[1])) );
3 Quadrature qp(weight, 0.0, h_e, 3);
4 J d_l(h_e/2.0);
5 H2 Z((double*)0, qp),
6 z = Z/h_e,
7 N = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(
8 "int, int, Quadrature", 4/*nen x ndf*/, 1/*nsd*/, qp); // Hermite cubics
9 N[0] = 1.0-3.0*z.pow(2)+2.0*z.pow(3); // u0
10 N[1] = Z*(1.0-z).pow(2); // du0/dx
11 N[2] = 3.0*z.pow(2)-2.0*z.pow(3); // u1
12 N[3] = Z*(z.pow(2)-z); // du1/dx
13 H0 Nx = INTEGRABLE_VECTOR("int, Quadrature", 4, qp),
14 Nxx = INTEGRABLE_VECTOR("int, Quadrature", 4, qp);
15 Nx = d(N)(0);
16 for(int i = 0; i < 4; i++) { Nxx[i] = dd(N)(i)[0][0]; }
17 H2 U = N*ul;
18 H0 Ux, Uxx; // due/dx, d2ue/dx2
19 Ux = d(U)(0);
20 Uxx = dd(U)[0][0];
21 H0 uR = ((H0)U)*Uxx + Ux.pow(2) - 1.0, // R(u)
22 Ru = ((H0)N)*Uxx + ((H0)U)*Nxx + 2.0*Nx*Ux, // dR/du
23 Ruu = (((H0)N)%Nxx) + (Nxx%((H0)N)) + 2.0*(Nx%Nx); // d2R/du2
24 stiff &= ( (Ru%Ru + Ruu*uR) ) | d_l; // tangent stiffness
25 force &= -(Ru*uR) | d_l ; // residual

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).

332 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems

#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]; }

Workbook of Applications in VectorSpace C++ Library 333


Chapter 4 Finite Element Method Primer

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

334 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
The nonlinear iterative algorithm with classical Newton’s method shows difficulty in getting convergence. A
quick fixed is to add line search algorithm, with golden section, on top of the classical Newton’s method, which
is implemented to tame the wild search path of the classical Newton’ method as introduced in Chapter 2 (see
page 125).

1 double left = 0.0, right = 1.0, length = right-left;


2 do {
3 Matrix_Representation::Assembly_Switch = Matrix_Representation::RHS;
4 du = (left + 0.618 * length) * p;
5 gd.u_h() = u + du;
6 (C0)(mr.rhs()) = 0.0;
7 mr.assembly();
8 double residual_golden_right = norm((C0)(mr.rhs()));
9 du = (left + 0.382 * length)* p;
10 gd.u_h() = u + du;
11 (C0)(mr.rhs())=0.0;
12 mr.assembly();
13 double residual_golden_left = norm((C0)(mr.rhs()));
14 if(residual_golden_right < residual_golden_left) left = left + 0.382 * length;
15 else right = left+0.618*length;
16 length = right - left;
17 } while(length > 1.e-2);
18 ...
19 Matrix_Representation::Assembly_Switch = Matrix_Representation::ALL;

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

0.2 0.4 0.6 0.8 1 x 0.2 0.4 0.6 0.8 1


x

Figure 4•27 Nodal solutions (open squares) comparing to the exact solutions (solid
curves) for the nonlinear least squares formulation.

Workbook of Applications in VectorSpace C++ Library 335


Chapter 4 Finite Element Method Primer
4.2.4 Transient Problems
The transient problem is introduced in Section 3.3.4. We consider the parabolic equation for heat conduction

Cu· + Ku + f = 0 Eq. 4•78

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

Mu·· + Ku + f = 0 Eq. 4•79

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),

( C + ∆tθK )u n + 1 = ( C – ∆t ( 1 – θ )K )u n – f̂ Eq. 4•80

Considering the initial-boundary value problem 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

The finite element formulation for C and K is

∂φ 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

mass &= ((H0)N)%((H0)N) | dv;

The protected member functions of the base class, “Element_Formulation::__lhs()” and


“Element_Formulation::__rhs()”, need to be overwritten in the derived class Parabolic_Equation as

1 C0& Parabolic_Equation::__lhs() {
2 the_lhs &= mass + theta_* dt_*stiff; // C + ∆tθK
3 return the_lhs;
4 }

336 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems

#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”).

Workbook of Applications in VectorSpace C++ Library 337


Chapter 4 Finite Element Method Primer
5 C0& Parabolic_Equation::__rhs() {
6 Element_Formulation::__rhs(); // –f̂
7 the_rhs += (mass - (1.0-theta_)*dt_*stiff)*ul; // ( C – ∆t ( 1 – θ )K )u n – f̂
8 return the_rhs;
9 }

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.

338 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
Hyperbolic Equation
From Eq. 3•211 of Chapter 3 (see page 257), Kˆ u n + 1 = Rˆ n + 1 is defined as

ˆ ˆ
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 β 

Consider the initial boundary value problem in page 258,

∂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 void Hyperbolic_Equation::__initialization(int en, Global_Discretization& gd) {


2 Omega_h& oh = gd.omega_h();

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.

Workbook of Applications in VectorSpace C++ Library 339


Chapter 4 Finite Element Method Primer

#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);
}

340 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems

Hyperbolic_Equation::Hyperbolic_Equation(int en, Global_Discretization& gd) :


Element_Formulation(en, gd) {
__initialization(en, gd);
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);
Hermite cubics
N[0] = 1.0-3.0*z.pow(2)+2.0*z.pow(3);
N[1] = -Z*(1.0-z).pow(2);
N[2] = 3.0*z.pow(2)-2.0*z.pow(3);
me = ∫ φe ⊗ φe dx
Ωe
N[3] = -Z*(z.pow(2)-z);
H0 Nxx = INTEGRABLE_VECTOR("int, Quadrature", 4, qp); ∂ 2 φe ∂ 2 φe
for(int i = 0; i < 4; i++) Nxx[i] = dd(N)(i)[0][0];
stiff &= (Nxx % Nxx) | d_l; mass &= ( ((H0)N)%((H0)N) ) | d_l;
ke = ∫ ----------
∂x 2
- ⊗ ----------- dx
∂x 2
Ωe
}
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static Hyperbolic_Equation hyperbolic_equation_instance(element_type_register_instance);
static Matrix_Representation mr(gd);
static const double gamma_ = 0.5;
static const double beta_ = 0.25; K + a0 M + a1 C
static const double dt_ = 0.01;
static double a[8];
C0& Hyperbolic_Equation::__lhs() { the_lhs &= stiff + a[0]*mass; return the_lhs; } – f n + 1 + ( a 0 u n + a 2 u· n + a 3 u·· n )M +
C0& Hyperbolic_Equation::__rhs() { Element_Formulation::__rhs();
( a u + a u· + a u·· )C
1 n 4 n 5 n
the_rhs += mass * (a[0]*ul+a[2]*dul+a[3]*ddul); return the_rhs; }
int main() {
for(int i = 0; i < node_no; i++) { u(x, 0) = sin(πx)-πx(1-x),
C1 x( ((double)i)*h_e ), w_0 = sin(PI*x)-PI*x*(1.0-x);
u_old[i][0] = ((C0)w_0); u_old[i][1] = -d(w_0); ∂u ( x, 0 )
and -------------------- = 0
for(int j = 0; j < ndf; j++) du_old[i][j] = ddu_old[i][j] = 0.0; ∂t
}
1 γ 1
gd.u_h() = gd.gh_on_gamma_h(); a 0 = -----------2, a 1 = ---------, a 2 = ---------
a[0] = 1.0/(beta_*pow(dt_,2)); a[1] = gamma_/(beta_*dt_); a[2] = 1.0/(beta_*dt_); β∆t β∆t β∆t
a[3] = 1.0/(2.0*beta_)-1.0; a[4] = gamma_/beta_-1.0; a[5] = dt_/2.0*(gamma_/beta_-2.0);
γ ∆t γ
a 3 = ------ – 1, a 4 = --- – 1, a 5 = -----  --- – 2
a[6] = dt_*(1.0-gamma_); a[7] = gamma_*dt_; 1
mr.assembly(); C0 decomposed_LHS = !((C0)(mr.lhs())); 2β β 2 β 
for(int i = 0; i < 28; i++) {
C0 u = decomposed_LHS*((C0)(mr.rhs()));
gd.u_h() = u;
a 6 = ∆t ( 1 – γ ), a 7 = γ∆t
u_new = ( (C0)(gd.u_h()) );
ddu_new = a[0]*(((C0)u_new)-((C0)u_old))-a[2]*((C0)du_old)-a[3]*((C0)ddu_old); u·· n + 1 = a 0 ( u n + 1 – u n ) – a 2 u· n – a 3 u·· n
du_new = ((C0)du_old) + a[6]*((C0)ddu_old)+a[7]*((C0)ddu_new);
u_old = ((C0)u_new); du_old = ((C0)du_new); ddu_old = ((C0)ddu_new); u· n + 1 = u· n + a 6 u·· n + a 7 u·· n + 1
double iptr;
if(modf( ((double)(i+1))/2.0, &iptr)==0) { cout << "time: " << (((double)(i+1))*dt_)
<< ", u: " << u_new[(node_no-1)/2][0] << endl; }
if(i < 27) { (C0)(mr.rhs()) = 0.0; (C0)(mr.lhs()) = 0.0; mr.assembly(); }
}
return 0;
}

Listing 4•11 Newmark scheme for hyperbolic equation using finite element method.

Workbook of Applications in VectorSpace C++ Library 341


Chapter 4 Finite Element Method Primer
3 gh_on_Gamma_h& gh = gd.gh_on_gamma_h();
4 Global_Discretization gd_u_old(oh, gh, u_old);
5 ul &= gd_u_old.element_free_variable(en);
6 Global_Discretization gd_du_old(oh, gh, du_old);
7 dul &= gd_du_old.element_free_variable(en);
8 Global_Discretization gd_ddu_old(oh,gh,ddu_old);
9 ddul &=gd_ddu_old.element_free_variable(en);
10 }

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.

342 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
4.2.5 The Mixed Formulation Revisited—Matrix Substructure Method
In the irreducible formulation for the beam bending problem (see page 306) the unwieldy piece-wise Hermit
cubic functions are used for the C1-continuity on the nodes since the second-order derivatives appeared in the
element stiffness matrix. The mixed formulation for the same problem (see page 312) reduces the element stiff-
ness matrix to have the first-order derivatives and only C0-continuity is required. However, this is achieved with
the expense of add nodal variables M ˆ in addition to ŵ . Recall Eq. 4•44 for the element formulation

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

Assign symbols for submatrices and subvectors in Eq. 4•87, we have

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

where B is symmetric negative definite.1

Workbook of Applications in VectorSpace C++ Library 343


Chapter 4 Finite Element Method Primer
Matrix Substructuring
We can solve the Eq. 4•90 with “matrix substructuring” (or static condensation). This is only possible
because of the special properties of the submatrices in Eq. 4•90 that (1) the diagonal submatrix B is symmetric
positive definite (or negative definite) which is invertible, and (2) the off-diagonal matrices are transpose of each
other. Therefore, pre-and post- multiplication of the symmetric positive definitive matrix B-1 such as “AB-1AT”
is a similarity transformation which preserves the symmetric positive definitive property. Therefore, it can also
be inverted. Now, let’s proceed with the substructuring. From second equation of Eq. 4•90, we have

ˆ = 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

Now we can solve for ŵ as

ŵ = ( 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.

344 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
Object-Oriented Modeling for Matrix Substructuring
With the understanding that we want to implement finite element in terms of Eq. 4•90, we may immediately
recognize that the “fe.lib” has no provision to define and to solve a problem described by Eq. 4•90. The object-
oriented modeling of the finite element method in fe.lib has four strong components, (1) discretized global
domain Ωh, (2) discretized variables uh, (3) element formulation “EF”, and (4) matrix representation “MR”.
ˆ }
Firstly, instead of one set of nodal variables combined together in the irreducible formulation as { ŵ i, M i
ˆ
now we have two (separate) sets of variables { ŵ i} and { M i}. The combination of Ω and u now yield two dif-
h h

ferent Global_Discretizations with {Ωh, ŵ h} and {Ωh, M ˆ h} as their constituents.

1 const int ndf = 1;


2 Omega_h oh; // Ωh
3 gh_on_Gamma_h_i wgh(0, ndf, oh); // w ∈ Γ g, and V ∈ Γ h
4 U_h wh(ndf, oh); // ŵ h
5 Global_Discretization wgd(oh, wgh, wh);
6 gh_on_Gamma_h_i mgh(1, ndf, oh); // M ∈ Γ g, and ψ ∈ Γ h
7 U_h mh(ndf, oh); ˆ h
// M
8 Global_Discretization mgd(oh, mgh, mh);
ˆ }.
The two sets of boundary conditions “gh_on_Gamm_h_i” corresponding to two set of variables { ŵ i} and { M i
The new class “gh_on_Gamma_h_i” is derived from the”gh_on_Gamma_h” with a subscript index included.
The constructor of this class is defined as

1 gh_on_Gamma_h_i::gh_on_Gamma_h_i(int i, int df, Omega_h& omega_h) :


2 gh_on_Gamma_h() {
3 gh_on_Gamma_h::__initialization(df, omega_h);
4 if(i == 0) {
// wΓ(0) = 0; deflection essential boundary condition
5 the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Dirichlet;
6 the_gh_array[node_order(0)][0] = 0.0;
// VΓ(L) = d/dx M(L) = d/dx (EI d2w/dx2) = 0; shear force natural boundary condition
7 the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Neumann;
8 the_gh_array[node_order(node_no-1)][0] = 0.0;
9 } else if(i == 1) {
// MΓ(L) = M_; bending moment essential boundary condition
10 the_gh_array[node_order(node_no-1)](0) = gh_on_Gamma_h::Dirichlet;
11 the_gh_array[node_order(node_no-1)][0] = M_;
// -dw/dx(0) = ψΓ(0) = 0; rotation natural boundary condition
12 the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Neumann;
13 the_gh_array[node_order(0)][0] = 0.0;
14 }
15 }
Secondly, for the definition of submatrix B we need the “Global_Discretization” with {Ωh, Mˆ h}. The rows
ˆ
and columns of submatrix B corresponding both to the M -dof. However, for the definition of submatrix A, we

Workbook of Applications in VectorSpace C++ Library 345


Chapter 4 Finite Element Method Primer
need both “Global_Discretization” with {Ωh, ŵ h} and {Ωh, M h}. Since the row of submatrix A corresponding
to the Mˆ -dof, and columns of submatrix A corresponding to the ŵ -dof. Therefore, the definition of submatrix B
reference only to the single Global_Discretization of {Ωh, Mˆ h}, which is the same as what is already available in
fe.lib. For the definition of submatrix A it needs to refer to a newly defined class of
“Global_Dsicretization_Couple” which is consists of “dual” Global_Discritization with both {Ωh, ŵ h} and
{Ωh, M ˆ h}. We have the declaration of the “deflection-and-bending moment” coupled global discretization as

static Global_Discretization_Couple gdc(wgd, mgd);


Thirdly, in the element formulation “EF”, we not only need to define the diagonal submatrix B, but also need
to define the off-diagonal submatrix A. The newly defined class is the “Element_Formulation_Couple” to han-
dle this additional complexity. The user defined element formulation is derived from this
“Element_Formulation_Couple” instead of the “Element_Formulation” such as

1 class Beam_Mixed_Formulation : public Element_Formulation_Couple {


2 public:
3 Beam_Mixed_Formulation(Element_Type_Register a) : Element_Formulation_Couple(a) {}
// diagonal block formulation; submatrix B
4 Element_Formulation *make(int, Global_Discretization&);
5 Beam_Mixed_Formulation(int, Global_Discretization&);
// off-diagonal block formulation; submatrix A
6 Element_Formulation_Couple *make(int, Global_Discretization_Couple&);
7 Beam_Mixed_Formulation(int, Global_Discretization_Couple&);
8 };
9 Element_Formulation* Beam_Mixed_Formulation::make(int en, Global_Discretization& gd) {
10 return new Beam_Mixed_Formulation(en,gd); }
11 Element_Formulation_Couple* Beam_Mixed_Formulation::make(int en,
12 Global_Discretization_Couple& gdc) { return new Beam_Mixed_Formulation(en,gdc); }

For the diagonal submatrix B, the constructor of element formulation is

1 Beam_Mixed_Formulation::Beam_Mixed_Formulation(int en, Global_Discretization& gd) :


2 Element_Formulation_Couple(en, gd) {
3 Quadrature qp(spatial_dim_no, 2); // 1-dimension, 2 Gaussian integration points
4 H1 Z(qp),
5 N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
6 "int, int, Quadrature", 2/*nen*/, 1/*nsd*/, qp);
7 N[0] = (1-Z)/2; N[1] = (1+Z)/2;
8 H1 X = N*xl;
9 H0 Nx = d(N)(0)/d(X);
10 J d_l(d(X)); φ eM ⊗ φ eM
11 stiff &= -(1.0/E_/I_)* ( (((H0)N)*(~(H0)N)) | d_l ); // B = – ∫  ---------------------- dx
EI
12 } Ωe

For the off-diagonal submatrix A, the constructor of element formulation is

346 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems
1 Beam_Mixed_Formulation::Beam_Mixed_Formulation(int en, Global_Discretization_Couple& gdc)
2 : Element_Formulation_Couple(en, gdc) {
3 Quadrature qp(spatial_dim_no, 2); // 1-dimension, 2 integration points
4 H1 Z(qp),
5 N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
6 "int, int, Quadrature", 2/*nen*/, 1/*nsd*/, qp);
7 N[0] = (1-Z)/2; N[1] = (1+Z)/2;
8 H1 X = N*xl;
9 H0 Nx = d(N)(0)/d(X);
10 J d_l(d(X)); dφ ew dφ eM
11 stiff &= -(Nx * (~Nx)) | d_l; // A = – ∫  ---------- ⊗ ---------- dx
dx dx
12 force &= ( (((H0)N)*f_0) | d_l ); // f = ∫ φ ew fdx Ωe
13 } Ωe

Finally, we recall the global submatrix form in Eq. 4•90

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

Matrix_Representation_Couple mrc(gdc, 0, 0, &(mr.rhs()) );

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-

Workbook of Applications in VectorSpace C++ Library 347


Chapter 4 Finite Element Method Primer
side will not be instantiated but will be referring to “mr.rhs()”, which has already been instantiated. Now we can
solve Eq. 4•90 with the matrix substructuring such as

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.

348 Workbook of Applications in VectorSpace C++ Library


One Dimensional Problems

#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);
}

Workbook of Applications in VectorSpace C++ Library 349


Chapter 4 Finite Element Method Primer

Beam_Mixed_Formulation::Beam_Mixed_Formulation(int en, Global_Discretization& gd) :


Element_Formulation_Couple(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;
φ eM ⊗ φ eM
B = – ∫  ---------------------- dx
H0 Nx = d(N)(0)/d(X);
J d_l(d(X)); EI
stiff &= -(1.0/E_/I_)* ( (((H0)N)*(~(H0)N)) | d_l ); Ωe
}
Element_Formulation_Couple* Beam_Mixed_Formulation::make(int en,
Global_Discretization_Couple& gdc) {
return new Beam_Mixed_Formulation(en,gdc);
}
Beam_Mixed_Formulation::Beam_Mixed_Formulation(int en, Global_Discretization_Couple&
gdc) : Element_Formulation_Couple(en, gdc) {
Quadrature qp(spatial_dim_no, 2);
H1 Z(qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature",2,1,qp);
N[0] = (1-Z)/2; dφ ew dφ eM
N[1] = (1+Z)/2; A = – ∫  ---------- ⊗ ---------- dx
H1 X = N*xl; dx dx
Ωe
H0 Nx = d(N)(0)/d(X);
J d_l(d(X));
stiff &= -(Nx * (~Nx)) | d_l;
force &= ( (((H0)N)*f_0) | d_l );
f= ∫ φew fdx
Ωe
}
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static Beam_Mixed_Formulation
beam_mixed_formulation_instance(element_type_register_instance);
static Matrix_Representation mr(mgd);
static Matrix_Representation_Couple mrc(gdc, 0, 0, &(mr.rhs()), &mr);
int main() {
mrc.assembly();
C0 f = ((C0)(mrc.rhs())),
A = ((C0)(mrc.lhs()));
mr.assembly();
C0 B = ((C0)(mr.lhs())),
r = ((C0)(mr.rhs()));
C0 B_inv = B.inverse(), ŵ = ( AB –1 A T ) – 1 ( AB –1 r – f )
w = (A*B_inv*r - f)/(A*B_inv*(~A)),
ˆ = B –1 ( r – A T ŵ )
M
M = B_inv*(r-(~A)*w);
wh = w;
wh = wgd.gh_on_gamma_h();
mh = M;
mh= mgd.gh_on_gamma_h();
cout << "deflection:" << endl << wh<< endl << "bending moment:" << endl << mh;
return 0;
}

Listing 4•12 Substructure solution for the mixed formulation of the beam bending problem.

350 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
4.3 Two Dimensional Problems
We went through various 1-D proto-type problems in finite element method. However, we may argue that it is
somewhat un-necessary to use finite element method for the 1-D problems. We can solve these problems with the
classical variational method as in Chapter 3. However, for more complicated geometry with the dimension
greater than or equal 2-D, the finite element method offers a systematic treatment of the complicated geometry
where the use of the finite element method becomes essential.

4.3.1 Heat Conduction

Basic Physics and Element Formulation


For heat conduction problem the divergence of heat flux of a body is equal to the heat generated from the
source contained within the body as

∇•q = f Eq. 4•96

where q is the heat flux and f is the heat source. This is subject to Dirichlet and Neumann boundary conditions

u = g on Γ g , and – q • n = h on Γ h , Eq. 4•97

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

∫ w ( ∇•q – f ) dΩ = ∫ w ( – κ ∇2u – f ) dΩ = 0 Eq. 4•99


Ω Ω

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

Workbook of Applications in VectorSpace C++ Library 351


Chapter 4 Finite Element Method Primer
the element stiffness matrix, under finite element approximation, is

k eab = a ( φea, φ eb ) = ∫ ( ∇φea κ∇φeb )dx Eq. 4•102


Ωe

and, the element force vector is

f ea = ( φ ea, f ) + ( φ ea, h ) Γ – a ( φ ea, φ eb )u eb Eq. 4•103


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

u eh ( ξ, η ) ≡ N a ( ξ, η )û ea Eq. 4•105

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

352 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

û e0 ∂N 0 ∂N ∂N 2 ∂N3 û e
0

--------- ---------1 --------- --------- 1


û e1 ∂x ∂x ∂x ∂x û e
∇u eh ( ξ, η ) = ( ∇N a ( ξ, η ) ) T û ea = ∇N 0 ∇N 1 ∇N2 ∇N 3 =
û e2 ∂N ∂N ∂N 2 ∂N3 û e2
---------0 ---------1 --------- ---------
∂y ∂y ∂y ∂y
û e3 û e3

∂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
 ∂ξ   ∂ξ

An Example with Bilinear 4-Node Element


We now consider an example of a 3 × 3 unit square insulated from the two sides with the top boundary and the
bottom boundary set at 30 oC and 0 oC, respectively. The thermal diffusivity is assumed to be isotropic with κ = 1
(see Figure 4•30). Combinding Eq. 4•96 and Eq. 4•98, we have

∇•( – κ ∇u ) = – κ ∇2u = f Eq. 4•111

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

Workbook of Applications in VectorSpace C++ Library 353


Chapter 4 Finite Element Method Primer

utop= 30oC

q•n=0 q•n=0

ubottom= 0oC
Figure 4•30 Conduction in a square insulated from two sides.

d2u du ( u top – u bottom )


-------- = 0 ⇒ ------ = constant = --------------------------------------- = 10 Eq. 4•112
dy 2 dy 3

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 }

354 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

#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”.

Workbook of Applications in VectorSpace C++ Library 355


Chapter 4 Finite Element Method Primer
This code generates 16 nodes and 9 bilinear 4-node elements in the constructor “Omega_h::Omega_h()”. After
node and element are created by their own constructors (i.e., “Node:Node(int, int, double*)” and
“Omega_eh::Omega_eh(int, int, int, int, int*)”), we use the member functions “Node::add(Node*)” and
“Omega_eh::add(Omega_eh*)” to add to the “database” the information on nodes and elements, respectively.
We observe that defining the nodes and the elements for a two dimensional problem may become a very compli-
cated task. We will discussed this issue later with a simple 2-D tool—“block()” function that has already been
introduced in Chapter 3 (see page 192) to enhance the capability to handle increasingly complicated geometry.
At the heart of the code is the element constructor “HeatQ4::HeatQ4()” which implements a 4-nodes bilinear
quadrilateral element

1 HeatQ4::HeatQ4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {


2 Quadrature qp(2, 4);
3 H1 Z(2, (double*)0, qp), Zai, Eta,
4 N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 4, 2, qp);
5 Zai &= Z[0]; Eta &= Z[1];
6 N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4;
7 N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4;
8 H1 X = N*xl;
9 H0 Nx = d(N) * d(X).inverse();
10 J dv(d(X).det());
11 double k = 1.0;
12 stiff &= (Nx * k * (~Nx)) | dv;
13 }

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.

A 2-D Geometrical Tool — “block()”


Even with the above extremely simple problem, the increasing difficulty in specifying geometry is exposed.
We use a few examples to demonstrate the tool “block()” function that facilitates the definition of 2-D geometry.
The first example constructs a set of nodes and elements with a single “block()” function call as (see Figure
4•31)

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”

356 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

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

1 const double PI = 3.141592653509,


2 c4 = cos(PI/4.0), s4 = sin(PI/4.0),

Workbook of Applications in VectorSpace C++ Library 357


Chapter 4 Finite Element Method Primer

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•32 A contiguous block generated by two “block()” function calls.

164 132 133


163

130 131 99 100


ui=100oC
ri = 0.5 98 66
97 67
65 33
64 34
uo = 0oC
31 32 0 1
ro = 1 tie nodes

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.

358 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
3 c8 = cos(PI/8.0), s8 = sin(PI/8.0),
4 c83 = cos(3.0*PI/8.0), s83 = sin(3.0*PI/8.0),
5 r1 = 0.5, r2 = 1.0;
6 double coord1[7][2] = {{0.0, r1},{c4*r1, s4*r1},{c4, s4},{0.0, r2},
7 {c83*r1, s83*r1},{0.0, 0.0},{c83*r2, s83*r2}},
8 coord2[7][2] = {{c4*r1, s4*r1},{r1, 0.0},{r2, 0.0},{c4, s4},
9 {c8*r1, s8*r1},{0.0, 0.0},{c8*r2, s8*r2}},
10 coord3[7][2] = {{r1, 0.0},{c4*r1, -s4*r1},{c4, -s4},{r2, 0.0},
11 {c8*r1, -s8*r1},{0.0, 0.0},{c8*r2, -s8*r2}},
12 coord4[7][2] = {{c4*r1, -s4*r1},{0.0, -r1},{0.0, -r2},{c4, -s4},
13 {c83*r1, -s83*r1},{0.0, 0.0},{c83*r2, -s83*r2}},
14 coord5[7][2] = {{0.0, -r1},{-c4*r1, -s4*r1},{-c4, -s4},{0.0, -r2},
15 {-c83*r1, -s83*r1},{0.0, 0.0},{-c83*r2, -s83*r2}},
16 coord6[7][2] = {{-c4*r1, -s4*r1},{-r1, 0.0},{-r2, 0.0},{-c4, -s4},
17 {-c8*r1, -s8*r1},{0.0, 0.0},{-c8*r2, -s8*r2}},
18 coord7[7][2] = {{-r1, 0.0},{-c4*r1, s4*r1},{-c4, s4},{-r2, 0.0},
19 {-c8*r1, s8*r1},{0.0, 0.0},{-c8*r2, s8*r2}},
20 coord8[7][2] = {{-c4*r1, s4*r1},{0.0, r1},{0.0, r2},{-c4, s4},
21 {-c83*r1, s83*r1},{0.0, 0.0},{-c83*r2, s83*r2}};
22 int flag[7] = {1, 1, 1, 1, 1, 0, 1};
23 block(this, 5, 5, 7, flag, coord1[0], 0, 0, 28, 28);
24 block(this, 5, 5, 7, flag, coord2[0], 4, 4, 28, 28);
25 block(this, 5, 5, 7, flag, coord3[0], 8, 8, 28, 28);
26 block(this, 5, 5, 7, flag, coord4[0], 12, 12, 28, 28);
27 block(this, 5, 5, 7, flag, coord5[0], 16, 16, 28, 28);
28 block(this, 5, 5, 7, flag, coord6[0], 20, 20, 28, 28);
29 block(this, 5, 5, 7, flag, coord7[0], 24, 24, 28, 28);
30 block(this, 5, 5, 7, flag, coord8[0], 28, 28, 28, 28);

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.

Workbook of Applications in VectorSpace C++ Library 359


Chapter 4 Finite Element Method Primer

100

80

60
o
C
40

20

0.5 0.6 0.7 0.8 0.9


r
Figure 4•34 Finite element nodal solutions in the radial direction comparing to the
analytical solution in Eq. 4•114 for the heat conduction of the cylinder.

-----  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

1 const double PI = 3.141592653509, c = cos(PI/4.0), s = sin(PI/4.0), c2 = c/2, s2 = s/2;


2 double coord1[4][2] = {{0.0,0.0},{0.5, 0.0},{c2, s2}, {0.0, 0.5}},
3 coord2[6][2] = {{0.5,0.0},{1.0,0.0},{c, s},{c2, s2}, {0.0,0.0},{cos(PI/8.0),sin(PI/8.0)}},
4 coord3[7][2] = {{0.0, 0.5},{c2, s2},{c, s},{0.0,1.0},
5 {0.0, 0.0},{0.0, 0.0}, {cos(3.0*PI/8.0), sin(3.0*PI/8.0)}};
6 int flag1[4] = {1, 1, 1, 1}, flag2[6] = {1, 1, 1, 1, 0, 1}, flag3[7] = {1, 1, 1, 1, 0, 0, 1};
7 block(this, 5, 5, 4, flag1, coord1[0], 0, 0, 4, 4);
8 block(this, 5, 5, 6, flag2, coord2[0], 4, 4, 4, 4);
9 block(this, 5, 5, 7, flag3, coord3[0], 45, 32, 0, 0);

360 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

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”.

Lagrange 9-nodes Element for Heat Conduction


The element formulation “HeatQ4” implemented the bilinear 4-node element for heat conduction. We intro-
duce a Lagrangian 9-node element “HeatQ9” as follows

1 class HeatQ9 : public Element_Formulation {


2 public:
3 HeatQ9(Element_Type_Register a) : Element_Formulation(a) {}
4 Element_Formulation *make(int, Global_Discretization&);
5 HeatQ9(int, Global_Discretization&);
6 };
7 Element_Formulation* HeatQ9::make(int en, Global_Discretization& gd) {
8 return new HeatQ9(en,gd);
9 }
10 HeatQ9::HeatQ9(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
11 Quadrature qp(2, 9); // 2-d 3 × 3 Gaussain quadrature
12 H1 Z(2, (double*)0, qp),
13 N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(

Workbook of Applications in VectorSpace C++ Library 361


Chapter 4 Finite Element Method Primer
14 "int, int, Quadrature", 9/*nen*/, 2/*nsd*/, qp),
15 Zai, Eta;
16 Zai &= Z[0]; Eta &= Z[1];
17 N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4; // 4-9 node shape functions
18 N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4;
19 N[8] = (1-Zai.pow(2))*(1-Eta.pow(2));
20 N[0] -= N[8]/4; N[1] -= N[8]/4; N[2] -= N[8]/4; N[3] -= N[8]/4;
21 N[4] = ((1-Zai.pow(2))*(1-Eta)-N[8])/2; N[5] = ((1-Eta.pow(2))*(1+Zai)-N[8])/2;
22 N[6] = ((1-Zai.pow(2))*(1+Eta)-N[8])/2; N[7] = ((1-Eta.pow(2))*(1-Zai)-N[8])/2;
23 N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2;
24 N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;
25 H1 X = N*xl;
26 H0 Nx = d(N) * d(X).inverse();
27 J dv(d(X).det());
28 double k_ = 1.0;
29 stiff &= (Nx * k_ * (~Nx)) | dv; // {9 × 2}*{2 × 9}={9 × 9}
30 }
31 Element_Formulation* Element_Formulation::type_list = 0;
32 Element_Type_Register element_type_register_instance;
33 static HeatQ9 heatq9_instance(element_type_register_instance); // element type # 1
34 static HeatQ4 heatq4_instance(element_type_register_instance); // element type # 0

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)

1 EP::element_pattern EP::ep = EP::LAGRANGIAN_9_NODES;


2 Omega_h::Omega_h() {
...
3 block(this, 5, 5, 7, flag, coord1[0], 0, 0, 28, 14, 1);
4 block(this, 5, 5, 7, flag, coord2[0], 4, 2, 28, 14, 1);
5 block(this, 5, 5, 7, flag, coord3[0], 8, 4, 28, 14, 1);
6 block(this, 5, 5, 7, flag, coord4[0], 12, 6, 28, 14, 1);
7 block(this, 5, 5, 7, flag, coord5[0], 16, 8, 28, 14, 1);
8 block(this, 5, 5, 7, flag, coord6[0], 20, 10, 28, 14, 1);
9 block(this, 5, 5, 7, flag, coord7[0], 24, 12, 28, 14, 1);
10 block(this, 5, 5, 7, flag, coord8[0], 28, 14, 28, 14, 1);
11 }

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”.

362 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

Figure 4•36 9-node Lagrangian quadrilateral elements generated by eight “block()” function
calls.

Post-Processing—Heat Flux on Gauss Points


After the solutions on temperature distribution is obtained, heat flux can be computed from Fourier law of
heat conduction of Eq. 4•98; i.e.,

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

∇u eh ( ξ, η ) ≡ ∇N a ( ξ, η )û ea Eq. 4•115

Therefore,

q eh = – κ ( ∇N a ( ξ, η )û ea ) Eq. 4•116

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,

Workbook of Applications in VectorSpace C++ Library 363


Chapter 4 Finite Element Method Primer
1 HeatQ4::HeatQ4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
2 ...
3 if(Matrix_Representation::Assembly_Switch == Matrix_Representation::FLUX) {
4 H0 flux = INTEGRABLE_VECTOR("int, Quadrature", 2, qp);
5 flux = 0.0;
6 for(int i = 0; i < nen; i++) {
7 flux[0] += - k * Nx[i][0]*(ul[i]+gl[i]); // q = – κ ∇u
8 flux[1] += - k * Nx[i][1]*(ul[i]+gl[i]);
9 }
10 int nqp = qp.no_of_quadrature_point(); cout.flush();
11 for(int i = 0; i < nqp; i++) {
12 cout << setw(9) << en
13 << setw(14) << ((H0)X[0]).quadrature_point_value(i)
14 << setw(14) << ((H0)X[1]).quadrature_point_value(i)
15 << setw(14) << (flux[0].quadrature_point_value(i))
16 << setw(14) << (flux[1].quadrature_point_value(i)) << endl;
17 }
18 } else stiff &= ...
19 }
20 int main() {
21 ...
22 Matrix_Representation::Assembly_Switch = Matrix_Representation::FLUX;
23 cout << "Heat flux on gauss integration points: " << endl;
24 cout.setf(ios::left,ios::adjustfield);
25 cout << setw(9) << " elem #, " << setw(14) << "x-coor.," << setw(14) << "y-coor.,"
26 << setw(14) << " q_x, " << setw(14) << " q_y, " << setw(14) << endl;
27 mr.assembly(FALSE);
28 }

Line 27 in the main() program is to call “Matrix_Representation::assembly()” with an argument “FALSE” to


indicate that the nodal loading on the right-hand-side is not to be performed. This function invokes element for-
mulation with a flag in class “Matrix_Representation” set to “Matrix_Representation::Assembly_Switch =
Matrix_Representation::FLUX”. The real computation is done at lines 7-8, where these two lines simply imple-
mented the Fourier law of heat conduction. The rest of lines is just the run-of-the-mill C++ output formatting.
The information on the coordinates of the Gauss points and their corresponding heat flux values are reported ele-
ment-by-element.

Post-Processing—Heat Flux Nodal Projection Method


Since the solutions of finite element computation are the temperatures on nodes, we may also interested in
having the heat flux to be reported on nodes. However, nodal heat flux, q̂ ea , requires much more elaborated post-
processing. The heat flux on an element can be interpolated from the nodal heat flux as

q eh ≡ N a ( ξ, η )q̂ ea Eq. 4•117

364 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
Since the shape function is an integrable object, its value is actually evaluated and stored only at the Gauss inte-
gration points. Now we can define error as the difference of qeh of Eq. 4•117 with qeh of Eq. 4•116. This error is
then distributed over the element domain by making a weighted-residual statement (as Eq. 3•105 of Chapter 3 on
page 352) with Galerkin weighting that w = Na

∫ Na ( qeh – qeh ) dΩ = 0 Eq. 4•118


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

∫ ( qeh – qeh ) 2 dΩ = 0 Eq. 4•122


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.

Workbook of Applications in VectorSpace C++ Library 365


Chapter 4 Finite Element Method Primer
Minimization by taking derivative with respect to the nodal heat flux, q̂ ea , and using its interpolation relation of
Eq. 4•117, gives back to Eq. 4•118. Therefore, the nodal flux can be considered as obtained through least squares
approximation too.
Eq. 4•119 can be solved with a full-scale finite element method, direct or iterative. However, as post-process-
ing procedure, it will be more desirable to have a simplified approximation that can be performed element-by-
element without even to assemble the global mass matrix, or to invoke matrix solver to solve for Eq. 4•119, such
as,

1 HeatQ4::HeatQ4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {


2 ...
3 if(Matrix_Representation::Assembly_Switch == Matrix_Representation::NODAL_FLUX) {
4 int flux_no = 2;
5 the_element_nodal_value &= C0(nen*flux_no, (double*)0);
6 C0 projected_nodal_flux = SUBVECTOR("int, C0&", flux_no, the_element_nodal_value);
7 H0 flux = INTEGRABLE_VECTOR("int, Quadrature", flux_no, qp);
8 flux = 0.0;
9 for(int i = 0; i < nen; i++) {
10 flux[0] += - k * Nx[i][0]*(ul[i]+gl[i]);
11 flux[1] += - k * Nx[i][1]*(ul[i]+gl[i]);
12 }
13 for(int i = 0; i < nen; i++) {
14 C0 lumped_mass(0.0);
15 for(int k = 0; k < nen; k++)
16 lumped_mass += (((H0)N[i])*((H0)N[k])) | dv;
17 projected_nodal_flux(i) = ( ((H0)N[i])*flux | dv ) / lumped_mass;
18 }
19 } else stiff &= (Nx * k * (~Nx)) | dv;
20 }

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 }

366 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
Lines 14-16 are the row-sum lumped mass matrix (in Eq. 4•121). Line 17 is the element-by-element solution of
approximated Eq. 4•119. In the main() program “NODAL_FLUX” switch is set (line 23) and assembly() invokes
the nodal projection procedure through Element_Formulation. Nodal values are shared by various number of ele-
ments. The assembly() function keeps and internal count on how many evaluations are performed on a particular
node, and it will compute an average nodal value from these nodal values for the node. The computation is done
with macro definitions “__TEST_CYLINDER” and “__TEST_FLUX”. The results of nodal heat flux are shown
in Figure 4•37 The projected nodal heat flux values are obviously less accurate than the temperature solutions
shown in Figure 4•37a. The projected nodal heat fluxes on the boundaries are significantly less accurate than
those in the interior. The reason can be easily deduce by studying Figure 4•37b, since the nodal heat fluxes (open
squares) are just least squares fit of a set of piece-wise line segments of the Gauss point heat fluxes (open cir-
cles).

280

260

240

220

200 q
180

160

0.5 0.6 0.7 0.8 0.9


r
(a) (b)
Figure 4•37 (a) Nodal heat flux shown in vectors, (b) projected radial heat flux on nodes are
shown in open squares. Heat flux on Gauss points are shown in open circles. The solid curve is the
analytical solution qr = du/dr = 100/(r ln 2), which is obtained from differentiation with respect to
r on Eq. 4•114.

Workbook of Applications in VectorSpace C++ Library 367


Chapter 4 Finite Element Method Primer
4.3.2 Potential Flow

Basic Physics and Element Formulation


We consider incompressible, inviscid fluid which gives the potential flow. The conditions of incompressible
(a solenoidal field) and irrotational (a toroial- free field) give

div u ≡ ∇•u = 0, and curl u ≡ ∇×u = 0 , Eq. 4•123

respectively. In 2-D, Eq. 4•123 reduces to the continuity equation

∂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

where ψ is a scalar function, and

∂ψ ∂ψ
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

We identify that ∇2ψ = 0 is the Laplace equation.


Similarly starting from Eq. 4•125 of condition of irrotationality, curl v = 0 at all point of the fluid. According
to Stokes’s theorem circulation along any closed loop is zero, as

368 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

volume flux across path


y = u dy - v dx
dx B
v
C1

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.,

dφ ( x ) = – u • dx, or ∇φ ( x ) = – u ( x ) Eq. 4•131

φ is known as the velocity potential, and the components of velocity as

∂φ ∂φ
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

– ∇2φ = 0 Eq. 4•133

Furthermore, from Eq. 4•127 and Eq. 4•132, we have

Workbook of Applications in VectorSpace C++ Library 369


Chapter 4 Finite Element Method Primer
∂φ ∂ψ ∂φ ∂ψ
– ------ = -------, and – ------ = – ------- Eq. 4•134
∂x ∂y ∂y ∂x

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.

Stream Function—ψ Formulation


Recall Eq. 4•127,
∂ψ ∂ψ
u = -------, and v = – -------
∂y ∂x

At the right-boundary ΓAE (Figure 4•39b), since u = ∂ψ/d∂, we can integrate ψ as

ψ(y) - ψ0 = U0 y. Eq. 4•136

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.

370 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
8 Velocity[0] += Nx[i][1]*(ul[i]+gl[i]);
9 Velocity[1] += - Nx[i][0]*(ul[i]+gl[i]);
10 }
11 for(int i = 0; i < nen; i++) {
12 C0 lumped_mass(0.0);
13 for(int k = 0; k < nen; k++)
14 lumped_mass += (((H0)N[i])*((H0)N[k])) | dv;
15 projected_nodal_velocity(i) = ( ((H0)N[i])*Velocity | dv ) / lumped_mass;
16 }
17 } else stiff &= (Nx * (~Nx)) | dv;

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

(b) stream function B.C. (c) velocity potential B.C.

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.

Workbook of Applications in VectorSpace C++ Library 371


Chapter 4 Finite Element Method Primer

#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).

372 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

∂N a ∂Na T
u eh = --------- ψ̂ a, – --------- ψ̂ a Eq. 4•137
∂y ∂x

The least squares nodal projection can be calculated accordingly as

û 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.

Velocity Potential—φ Formulation


The velocity potential formulation has the boundary conditions shown in Figure 4•39(c). Recall Eq. 4•132

∂φ ∂φ
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

Workbook of Applications in VectorSpace C++ Library 373


Chapter 4 Finite Element Method Primer

φ= 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

5.0 4.5 4.0 3.5 3.0 2.5

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.

374 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
4.3.3 Plane Elasticity
We introduce three commonly used formulations for plane elasticity (1) the coordinate-free tensorial formula-
tion, (2) the indicial notation formulation, and (3) the B-matrix (strain-displacement Matrix) formulation. We begin
from Cauchy’s equation of equilibrium which is the continuum version of Newton’s second law of motion. In
static state, the summation of surface force (= divergence of the stress tensor; i.e., div σ ≡ ∇•σ ) and external
force (f) equals zero. We expressed this balance of forces in both the coordinate free tensorial notation and the
indicial notation as

div σ+f = 0, or σ ij, j + f i = 0 Eq. 4•140

This is subject to displacement boundary conditions and traction boundary conditions

u = g on Γ g, and t = σ•n = ti = σ ij n j = h on Γ h Eq. 4•141

where t is the traction and n is the outward unit surface normal. The weighted-residual statement of the Eq. 4•140
is

∫ w ( div σ + f ) = 0, or ∫ wi ( σij, j + fi ) = 0 Eq. 4•142


Ω Ω

Integration by parts and then applying the divergence theorem of Gauss, we have

– ∫ ( grad w ) : σ dΩ + ∫ w ( σ • n )dΓ + ∫ wfdΩ = 0 , or


Ω Γ Ω
– ∫ w i, j σ ij dΩ + ∫ w i σ ij n j dΓ + ∫ w i f i dΩ = 0 Eq. 4•143
Ω Γ Ω

where the gradient operator, “grad”, and its relation to divergence operator, “div”, are

grad w = ∇w = ∇ ⊗ w = wi, j and div w = ∇ • w = tr ( grad w ) = w i, i , Eq. 4•144

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

– ∫ ( grad w ) : σ dΩ + ∫ whdΓh + ∫ wfdΩ = 0 , or


Ω Γh Ω

– ∫ w i, j σ ij dΩ + ∫ wi hi dΓh + ∫ wi fi dΩ = 0 Eq. 4•145


Ω Γh Ω

We first develop in tensorial notation for its clarity in physical meaning. The Cauchy stress tensor, σ, in Eq.
4•145 can be decomposed as

σ = –p I+τ Eq. 4•146

Workbook of Applications in VectorSpace C++ Library 375


Chapter 4 Finite Element Method Primer
where p is the pressure, I is the unit tensor, and τ is the deviatoric stress tensor. For isotropic material, the con-
stitutive equations are

p = – λ div u , and τ = 2µ def u Eq. 4•147

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.,

def u ≡ ∇ s u ≡ --- ( grad u + ( grad u ) T ) ≡ ε


1
Eq. 4•148
2

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

grad u = def u + rot u Eq. 4•150

Recall the first term in Eq. 4•145, and substituting the constitutive equations Eq. 4•146 and Eq. 4•147

∫ ( grad w ) : σ dΩ = ∫ ( grad w ) : ( λ I div u + 2µ def u )dΩ Eq. 4•151


Ω Ω

Note that,

grad w : I = tr(grad w) = div w Eq. 4•152

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

where the double contraction of def u can be written as

def u : def u = tr((def u)Tdef u) Eq. 4•154

With Eq. 4•152 and Eq. 4•153, the Eq. 4•151 becomes

∫ ( grad w) : σ dΩ = ∫ [ λ ( div w • div u ) + 2µ ( def w :( def u )]dΩ Eq. 4•155


Ω Ω

With the element shape function defined, e.g., as Eq. 4•104, the element stiffness matrix is

376 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

I. Coordinate-Free Tensorial Forumlation:


k e= a ( φea, φ eb ) = ∫ [ λ ( div N a • div N b ) + 2µ ( def N a : def N b )]dΩ Eq. 4•156

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

σij = cijkl εkl Eq. 4•157

cijkl is the elastic coefficients. For isotropic material, it is well-known that

cijkl = λ δij δkl +µ (δik δjl+ δil δjk) Eq. 4•158

where δij is the Kronecker delta (δij = 1 if i = j, otherwise δij =0). The equivalence of Eq. 4•155 is

∫ wi, j σij dΩ = ∫ w( i, j ) c ijkl u ( k, l ) dΩ = ∫ wi, j c ijkl u k, l dΩ Eq. 4•159


Ω Ω Ω

The last identity is due to the minor symmetry of cijkl. The element stiffness matrix for the indicial notation for-
mulation is

k epq = k eiajb = ∫ Na, k [ λ ( δ ik δ jl ) + µ ( δ ij δ kl + δ il δ kj ) ] N b, l dΩ Eq. 4•160


II. Indicial Notation Forumlation:


 
k eiajb = λ ∫ N a, i N b, j dΩ + µ  δ ij ∫ Na, k N b, k dΩ + ∫ N a, j N b, i dΩ Eq. 4•161

 Ω Ω

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

p = ndf (a-1) + i, and q = ndf (b-1)+j Eq. 4•163


In the engineering convention, the strain tensor, ε, and stress tensor, σ, are flatten out as vectors (e.g., in 2-D)

∂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

Workbook of Applications in VectorSpace C++ Library 377


Chapter 4 Finite Element Method Primer
The constitutive equation is

σ=Dε Eq. 4•165

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 µ

in plane stress case, D can be defined by replacing λ by λ, according to

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)

u eh ( ξ, η ) ≡ N a ( ξ, η )û eai e i, ( no sum on i ) Eq. 4•169

where ei is the Euclidean basis vector. We can write

ε( ueh ) = Ba û eai ei Eq. 4•170

∂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

The element stiffness matrix of the B-matrix (strain-displacement matrix) formulation is

ke = ∫ ε ( δu ) T σ ( u )dΩ = ∫ ε ( δu )T D ε ( u )dΩ Eq. 4•172


Ω Ω

III. B-matrix Formulation:


k epq = k eiajb = ∫ ε ( δu )T D ε ( u )dΩ = e iT ∫ BaT D B b dΩe j Eq. 4•173
Ω Ω

378 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
In Eq. 4•173, the relation of indices {p, q} and {i, a, j, b} are defined in Eq. 4•163.
Consider an example of a cantilever beam1 with Young’s modulus E = 30 × x106 psi, ν = 0.25 subject to a
uniformly distributed shear stress τ = 150 psi at the end (see Figure 4•42). The shear stress at the end is τy = -150
psi. For boundaries of a 4-node quadrilateral element, we use trapezoidal rule to compute the nodal load, because
the element boundary is linear. In the trapezoidal rule (Eq. 3•1 of Chapter 3 on page 166), the weighting for the
end-points of a line segment is {0.5, 0.5}. To element “0”, we add -75 psi to nodes “0”, and “5”, and for the ele-
ment “4”, we also add -75 psi to nodes “5”, and “10”. Adding the nodal loading on the two element together, this
yields nodal load specification of -75, -150, and -75 psi to nodes “0”, “5”, and “10”, respectively. Similarly, for a
9-nodes Lagrangian element, the boundary is quadratic, we use Simpson’s rule with weightings of {1/3, 4/3, 1/3}
to compute the three nodes on the boundary. This yields -50, -200, and -50 psi on nodes “0”, “5”, and “10”,
respectively. The analytical solution on the tip deflection is

PL 3 3(1 + ν)
v = – --------- 1 + -------------------
- Eq. 4•175
3EI L2

With the given parameters, this value is “-0.51875”.


We proceed to implement this problem in C++ with the aid of VectorSpace C++ Library and “fe.lib”. In most
finite element text, the B-matrix formulation is the carnonical formula provided. Therefore, we discuss the
implementation of the three formulations in reverse order.

τ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

Figure 4•42 Discretization of eight 4-nodes quadrilateral elements or two 9-nodes


Lagrangian elements for a cantilever beam.

1. p. 473 in Reddy, J.N. 1993, “ An introduction to the finite element method”, 2nd ed., McGraw-Hill, Inc., New York.

Workbook of Applications in VectorSpace C++ Library 379


Chapter 4 Finite Element Method Primer
Implementations for B-Matrix Formulation:
The Program Listing 4•15 implements Eq. 4•173. The Element_Formulation of “ElasticQ4” is

1 static const double a_ = E_ / (1-pow(v_,2)); // plane stress D matrix


2 static const double Dv[3][3] = {{a_, a_*v_, 0.0 },
4 {a_*v_, a_, 0.0 },
5 {0.0, 0.0, a_*(1-v_)/2.0} };
6 C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
7 ElasticQ4::ElasticQ4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
8 Quadrature qp(2, 4); // 2-dimension, 2x2 integration points
9 H1 Z(2, (double*)0, qp), // Natrual Coordinates
10 N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
11 "int, int, Quadrature", 4/*nen*/, 2/*nsd*/, qp),
12 Zai, Eta;
13 Zai &= Z[0]; Eta &= Z[1];
14 N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4;
15 N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4;
16 H1 X = N*xl;
17 H0 Nx = d(N) * d(X).inverse();
18 J dv(d(X).det());
19 H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx),
20 wx, wy, B;
21 wx &= w_x[0][0]; wy &= w_x[0][1]; // aliase submatrices; 1x2
22 B &= (~wx || C0(0.0)) & // dim B = {3x8}, where dim wx[i] = {1x4}
23 (C0(0.0) || ~wy ) &
24 (~wy || ~wx );
25 stiff &= ((~B) * (D * B)) | dv; // {8x3}*{3x3}*{3x8}={8x8}
26 }

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

w_x wx &= wy &= B &= (~wx || C0(0.0)) &


node w_x[0][0] w_x[0][1] (C0(0.0) || ~wy ) &
number (~wy || ~wx );
N0,x N0,y N0,x N0,y

N1,x N1,y N0,x 0 N1,x 0 N2,x 0 N3,x 0


N1,x N1,y

N2,x N2,y 0 N0,y 0 N1,y 0 N2,y 0 N3,y


N2,x N2,y
N3,x N3,y N0,y N0,x N1,y N1,x N2,y N2,x N3,y N3,x
N3,x N3,y

spatial dim. aliase submatrices B-matrix lay-out


Figure 4•43 Construction of B-matrix using one-by-one concatenation operation.

380 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

#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).

Workbook of Applications in VectorSpace C++ Library 381


Chapter 4 Finite Element Method Primer
also shown. The B-matrix, according to Eq. 4•171, is defined with one-by-one column-wise concatenation oper-
ation “H0::operator || (const H0&)”. When the argument of the concatenation operation is of type C0, it will be
promote to H0 type object before concatenation occurred. Line 25 is the element stiffness matrix definition of
the Eq. 4•173.
A complete parallel algorithm, without the use of the one-by-one concatenation operation, results in C++
statements closer to linear algebraic expression with basis

1 H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx),


2 wx, wy;
3 wx &= w_x[0][0]; wy &= w_x[0][1];
4 H0 zero = ~wx; zero = 0.0;
5 C0 e3(3), e(ndf), E(nen),
6 U = (e3%e)*(~E);
7 H0 B =+((~wx) * U[0][0] + zero * U[0][1] +
8 zero * U[1][0] + (~wy) * U[1][1] +
9 (~wy) * U[2][0] + (~wx) * U[2][1]) ;
10 stiff &= ((~B) * (D * B)) | dv;

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.

1 H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx),


2 B = INTEGRABLE_MATRIX("int, int, Quadrature", 3, nsd*nen, qp),
3 epsilon = INTEGRABLE_SUBMATRIX("int, int, H0&", 3, nsd, B),
4 wx, wy; // aliases of w_x components
5 wx &= w_x[0][0]; wy &= w_x[0][1];
6 epsilon[0][0] = ~wx; epsilon[0][1] = 0.0; // εx = ∂u/∂x
7 epsilon[1][0] = 0.0; epsilon[1][1] = ~wy; // εy = ∂v/∂y
8 epsilon[2][0] = ~wy; epsilon[2][1] = ~wx; // γxy = ∂u/∂y + ∂v/∂x
9 stiff &= ((~B) * (D * B)) | dv;

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.

382 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
The above two methods of programming depend heavily on the submatrix facility in the VectorSpace C++
Library. This dependency can be removed, if we flatten the submatrix into plain matrix with concatenation oper-
ations as (by setting only macro definition “__TEST_B_MATRIX_CONCATENATE_EXPRESSION” in the
same project)

1 H0 Nx0, Nx1, Nx2, Nx3; // aliases


2 Nx0 &= Nx[0]; Nx1 &= Nx[1]; Nx2 &= Nx[2]; Nx3 &= Nx[3];
3 H0 B = (Nx0[0] | C0(0.0) | Nx1[0] | C0(0.0) | Nx2[0] | C0(0.0) | Nx3[0] | C0(0.0) ) &
4 (C0(0.0) | Nx0[1] | C0(0.0) | Nx1[1] | C0(0.0) | Nx2[1] | C0(0.0) | Nx3[1] ) &
5 (Nx0[1] | Nx0[0] | Nx1[1] | Nx1[0] | Nx2[1] | Nx2[0] | Nx3[1] | Nx3[0] );
6 stiff &= ((~B) * (D * B)) | dv;

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 H0 k(8, 8, (double*)0, qp), DB(3, nsd, (double*)0, qp), B1, B2;


2 for(int b = 0; b < nen; b++) {
3 B1 &= Nx[b][0]; B2 &= Nx[b][1];
4 DB[0][0] = Dv[0][0]*B1; DB[0][1] = Dv[0][1]*B2; // D*B takes care of zeros
5 DB[1][0] = Dv[0][1]*B1; DB[1][1] = Dv[1][1]*B2;
6 DB[2][0] = Dv[2][2]*B2; DB[2][1] = Dv[2][2]*B1;
7 for(int a = 0; a <= b; a++) {
8 B1 &= Nx[a][0];
9 B2 &= Nx[a][1];
9 k[2*a ][2*b ] = B1*DB[0][0] + B2*DB[2][0]; // BT* DB takes care of zeros
10 k[2*a ][2*b+1] = B1*DB[0][1] + B2*DB[2][1];
11 k[2*a+1 ][2*b ] = B2*DB[1][0] + B1*DB[2][0];
12 k[2*a+1 ][2*b+1] = B2*DB[1][1] + B1*DB[2][1];
13 }
14 }
15 for(int b = 0; b < nen; b++) // determined by minor symmetry
16 for(int a = b+1; a < nen; a++) {
17 k[2*a ][2*b ] = k[2*b ][2*a ];
18 k[2*a ][2*b+1 ] = k[2*b+1 ][2*a ];

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.

Workbook of Applications in VectorSpace C++ Library 383


Chapter 4 Finite Element Method Primer
19 k[2*a+1 ][2*b ] = k[2*b ][2*a+1 ];
20 k[2*a+1 ][2*b+1 ] = k[2*b+1 ][2*a+1 ];
21 }
22 stiff &= k | dv;

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.

Implementations for Indicial Notation Formulation:


Recall Eq. 4•161
 
k eiajb = λ ∫ N a, i N b, j dΩ + µ  δ ij ∫ Na, k N b, k dΩ + ∫ N a, j N b, i dΩ

 Ω Ω

The integrand of the nodal submatrices kab (ndf × ndf submatrices) has the first term(the volumetric part) as

∂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” )

384 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
1 C0 e(ndf), E(nen), U = (e%e)*(E%E);
2 H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, nx), wx, wy;
3 wx &= w_x[0][0]; wy &= w_x[0][1]; //
∂N ∂N ∂N ∂N
4 C0 stiff_vol = lambda_bar* //  ---------a ---------b  ---------a ---------b
 ∂x ∂x   ∂y ∂x 
5 ( // λ
6 +( wx*~wx*U[0][0]+wx*~wy*U[0][1]+ //  ∂N ∂N ∂N ∂N
---------a ---------b  ---------a ---------b
7 wy*~wx*U[1][0]+wy*~wy*U[1][1] ) //  ∂x ∂y   ∂y ∂y 
8 | d_v);
11 C0 stiff_dev = mu_*
12 ( //
∂N a ∂N b ∂N a ∂N b ∂N ∂N
13 +( (2*wx*~wx+wy*~wy)*((e[0]%e[0])*(E%E))+// 2  --------- --------- +  --------- ---------  ---------a ---------b
 ∂x ∂x   ∂y ∂y   ∂y ∂x 
14 (wy*~wx) *((e[0]%e[1])*(E%E))+ // µ
∂N ∂N ∂N ∂N ∂N ∂N
15 (wx*~wy) *((e[1]%e[0])*(E%E))+ //  ---------a ---------b  ---------a ---------b + 2  ---------a ---------b
16 (wx*~wx+2*wy*~wy)*((e[1]%e[1])*(E%E))//  ∂x ∂y   ∂x ∂x   ∂y ∂y 
17 )
18 | dv);
19 stiff &= stiff_vol + stiff_dev;

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

1 H0 vol = INTEGRABLE_MATRIX("int, int, Quadrature", nsd*nen, nsd*nen, qp),


2 vol_sub = INTEGRABLE_SUBMATRIX("int, int, H0&", nsd, nsd, vol);
3 vol_sub[0][0] = wx*~wx; vol_sub[0][1] = wx*~wy;
4 vol_sub[1][0] = wy*~wx; vol_sub[1][1] = wy*~wy;
5 C0 stiff_vol = lambda_bar * (vol | dv);
6 H0 dev = INTEGRABLE_MATRIX("int, int, Quadrature", nsd*nen, nsd*nen, qp),
7 dev_sub = INTEGRABLE_SUBMATRIX("int, int, H0&", nsd, nsd, dev);
8 dev_sub[0][0] = 2*wx*~wx+wy*~wy; dev_sub[0][1] = wy*~wx;
9 dev_sub[1][0] = wx*~wy; dev_sub[1][1] = 2*wy*~wy+wx*~wx;
10 C0 stiff_dev = mu_ * (dev | dv);
11 stiff &= stiff_vol + stiff_dev;

The same implementation with one-by-one concatenation operations “||” and “&&” will be

1 C0 stiff_dev = mu_ *( ( ((2*Wx*~Wx+Wy*~Wy) || (Wy*~Wx) ) &&


2 ((Wx*~Wy) || (2*Wy*~Wy+Wx*~Wx))
3 ) | dv);
4 C0 stiff_vol = lambda_bar *( ( ((Wx*~Wx) || (Wx*~Wy)) &&
5 ((Wy*~Wx) || (Wy*~Wy))
6 ) | dv);
7 stiff &= stiff_vol + stiff_dev;

Workbook of Applications in VectorSpace C++ Library 385


Chapter 4 Finite Element Method Primer
The flatten-out coding using concatenation operations “|” and “&” is not recommended, since the 8 ×8 stiff-
ness matrix is just too much for either write it all out, or be read easily. An aggressively optimized counterpart
using less VectorSpace C++ library features is shown in the followings1

1 double c1 = lambda_bar + mu_, c2 = mu_, c3 = lambda_bar; // λ replaces λ for plane stress


2 H0 k(nen*ndf, nen*ndf, (double*)0, qp);
3 for(int b = 0; b < nen; b++) // upper triangular nodal submatrices +
4 for(int j = 0; j < ndf; j++) // diagonal nodal submatrices only
5 for(int a = 0; a <= b; a++) // ∂N ∂N ∂N ∂N
 ---------a ---------b  ---------a ---------b
6 for(int i = 0; i < ndf; i++) //  ∂x ∂x   ∂y ∂x 
// ke(temp) = ∫ ∂N ∂N
dΩ
7 if( (a != b) || (a == b && i <= j) ) ∂N ∂N
Ω  --------- ---------  --------- ---------
a b a b
8 k[i+a*ndf][j+b*ndf] = Nx[a][i]*Nx[b][j]; //  ∂x ∂y   ∂y ∂y 
9 for(int i = 1; i < nen*ndf; i++) //
10 for(int j = 0; j < i; j++) //
11 k[i][j] = k[j][i]; // get lower triangualr part by symmetry
12 C0 K = k | dv; // ke
13 for(int b = 0; b < nen; b++) //only the upper triangular of nodal submatrices—keab
14 for(int a = 0; a <= b; a++) {
15 C0 temp = 0.0;
16 for(int k = 0; k < ndf; k++)
17 temp += K[k+a*ndf][k+b*ndf];
18 for(int j = 0; j < ndf; j++)
19 for(int i = 0; i <= j; i++) {
20 if(i == j) // diagonal components of nodal submatrices—keiaib
21 K[i+a*ndf][i+b*ndf] = c1*K[i+a*ndf][i+b*ndf]+ c2*temp;
22 else if(a == b) // off-diagonal components of diagonal nodal submat-
23 K[i+a*ndf][j+a*ndf] *= c1; // rices—keiaja(those in upper triangular of ke; i<j)
24 else { // off-diagonals components
25 double Kij = K[i+a*ndf][j+b*ndf], // of off-diagonal nodal submatrices—keiajb (a ≠ b)
∂N ∂N ∂N ∂N
26 Kji = K[j+a*ndf][i+b*ndf]; //  λ  ---------a ---------b + µ  ---------a ---------b  dΩ
27 K[i+a*ndf][j+b*ndf] = c3*Kij + c2*Kji;// ∫   ∂x ∂y   ∂y ∂x  

28 K[j+a*ndf][i+b*ndf] = c3*Kji + c2*Kij;// ∂N a ∂Nb ∂N ∂N
29 } // ∫  λ  ---------
∂y ∂x 
--------- + µ  ---------a ---------b  dΩ
 ∂x ∂y  
30 } Ω
31 }
32 for(int i = 1; i < nen*ndf; i++) // get lower triangular part of ke by symmetry
33 for(int j = 0; j < i; j++) //
34 K[i][j] = K[j][i];
35 stiff &= K;

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.

386 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
All integration operations are done with between lines 3-12. Data of the derivatives of shape function are stored
in matrix ke temporarily as

∂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

Lines 20-21 gets diagonal components of the nodal submatrices according to

∂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-

Workbook of Applications in VectorSpace C++ Library 387


Chapter 4 Finite Element Method Primer
nents belong to the lower triangular part of ke, and they can be obtained by symmetry as in lines 32-34. Lines 24-
29 take care of the rest by Eq. 4•181; i.e., the off-diagonal components in off-diagonal nodal submatrices, they
all lie in the upper triangular part of ke. This implementation is probably the most efficient of all. However, the
code is quite abstruse without study its comments and explanations carefully. Lots of programming merits are all
compromised in the name of efficiency.

Implementation for Coordinate-Free Tensorial Formulation:


Now we turn away from the goal of optimization for efficiency completely to the goal of obtaining a most
physically and mathematically comprehensive implementation. For research scientists and engineers, it is most
likely to have a formula available that is derived from physical principles such as the development of elasticity
in the beginning of this section. The finite element formula may not be available. VectorSpace C++ Library
together with object-oriented features in C++ language may serve as the rapid proto-typing tools. A high-level
code can be quickly implemented with VectorSpace C++ Library because it provides capability of making com-
puter code very close to its mathematical counterparts. If it turns out further optimization is necessary for either
saving computation time or memory space, the numerical results of the high-level prototype code can be used to
debug the optimized code which is often quite un-readable and error-prone.
First we recall Eq. 4•155 for V ≡ { v ∈ H 1 }, we have the inner product defined by a symmetrical bilinear form

a ( v, v ) = ∫ [ λdiv v • div v + 2µ ( def v ): def v)]dΩ Eq. 4•183


The inner product gives a scalar. The implementation for the coordinate free tensorial formulation will be based
on Eq. 4•156 which is

k e = a ( N a, N b ) = ∫ [ λ ( div N a • div Nb ) + 2µ ( def N a : def Nb )]dΩ Eq. 4•184


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

1 class H0_h; // forward declaration


2 class H1_h { // finite element space—Vh(Ωe), where V ≡ { v ∈ H 1 }

388 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
3 H1 n, x;
4 public:
5 H1_h(H1&, H1&);
6 H0_h div_();
7 H0_h grad_();
8 H0_h grad_t_();
9 H0_h def_();
10 };
11 class H0_h : public H0 { // return type for the differential operators div, grad, def
12 public:
13 H0_h(const H0& a) : H0(a) {}
14 H0 operator ^(const H0_h&); // double contraction “:”
15 };
16 H0_h div(H1_h& n) { return n.div_(); }
17 H0_h grad(H1_h& n) { return n.grad_(); }
18 H0_h grad_t(H1_h& n) { return n.grad_t_(); }
19 H0_h def(H1_h& n) { return n.def_(); }
20 H1_h::H1_h(H1& N, H1& X) { n = N; x = X; }

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

1 H1_h N_(N, X); ∫ λ ( div N a • div N b )dΩ


2 C0 K_vol = lambda_bar*(((~div(N_))*div(N_)) | dv), // Ω
3 K_dev = (2*mu_) * ( (def(N_) ^ def(N_)) | dv); // ∫ 2µ ( def N a : def Nb )dΩ
4 stiff &= K_vol + K_dev; Ω

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

Workbook of Applications in VectorSpace C++ Library 389


Chapter 4 Finite Element Method Primer
∂u ∂v
div u = ui,i = ------ + ------ Eq. 4•185
∂x ∂y

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

of size 1 × 8. Eq. 4•186 can be implemented as

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;

390 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
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] + wx*a[0][3] + // Eq. 4•188
11 wy*a[1][0] + wy*a[1][3];
12 return ret_val;
13 }

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

Workbook of Applications in VectorSpace C++ Library 391


Chapter 4 Finite Element Method Primer
1 H0_h H1_h::def_() {
2 H0 ret_val = (+(grad_t(*this) + grad(*this))/2); // Eq. 4•190
3 return ret_val;
4 }

The differential operator def also return a 2 × 8 H0_h type object. The double contraction is defined in Eq. 4•154

def u : def u = tr((def u)Tdef u) Eq. 4•191

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.

392 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
Post-Processing—Nodal Reactions
The reaction on each node can be computed after the displacement is known, according to “Kijuj”. The actual
computation is done at the constructor of class “ElasticQ4”, and is invoked in the main() program as the follow-
ings.

1 ElasticQ4::ElasticQ4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {


2 ...
3 if(Matrix_Representation::Assembly_Switch == Matrix_Representation::REACTION) {
4 stiff &= K | dv;
5 the_element_nodal_value &= stiff * (ul+gl);
6 } else stiff &=K | dv;
7 }
8 int main() {
9 ...
10 Matrix_Representation::Assembly_Switch = Matrix_Representation::REACTION;
11 mr.assembly(FALSE);
12 cout << "Reaction:" << endl << (mr.global_nodal_value()) << endl;
13 }

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.

Post-Processing—Stresses on Gauss Points


After the displacement solution is obtained, stresses can be computed from stress-strain relation, e.g., in B-
matrix form of Eq. 4•173, the stress is

σeh = D Bû ea Eq. 4•192

After the nodal displacements, ûea , are obtained, we can loop over each element to calculate the stresses on each
Gaussian integration point as,

1 HeatQ4::HeatQ4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {


2 ...
3 if(Matrix_Representation::Assembly_Switch == Matrix_Representation::STRESS) {
4 H0 Sigma = INTEGRABLE_VECTOR("int, Quadrature", 3, qp);
5 Sigma = 0.0;
6 for(int i = 0; i < nen; i++) {
7 B1 &= Nx[i][0]; B2 &= Nx[i][1];
8 DB[0][0] = Dv[0][0]*B1; DB[0][1] = Dv[0][1]*B2;
9 DB[1][0] = Dv[0][1]*B1; DB[1][1] = Dv[1][1]*B2;
10 DB[2][0] = Dv[2][2]*B2; DB[2][1] = Dv[2][2]*B1;

Workbook of Applications in VectorSpace C++ Library 393


Chapter 4 Finite Element Method Primer
11 Sigma += DB(0)*(ul[i*ndf]+gl[i*ndf]) + DB(1)*(ul[i*ndf+1]+gl[i*ndf+1]); // σ eh = D Bû ea
12 }
13 int nqp = qp.no_of_quadrature_point();
14 for(int i = 0; i < nqp; i++) {
15 cout << setw(9) << en
16 << setw(14) << ((H0)X[0]).quadrature_point_value(i)
18 << setw(14) << ((H0)X[1]).quadrature_point_value(i)
19 << setw(14) << (Sigma[0].quadrature_point_value(i))
20 << setw(14) << (Sigma[1].quadrature_point_value(i))
21 << setw(14) << (Sigma[2].quadrature_point_value(i)) << endl;
22 }
23 } else stiff &= ...
24 }
25 int main() {
26 ...
27 Matrix_Representation::Assembly_Switch = Matrix_Representation::STRESS;
28 cout << << "gauss point stresses: " << endl;
29 cout.setf(ios::left,ios::adjustfield);
30 cout << setw(9) << " elem #, " << setw(14) << "x-coor.," << setw(14) << "y-coor.,"
31 << setw(14) << "sigma-11," << setw(14) << "sigma-22," << setw(14) << "sigma-12" << endl;
32 mr.assembly(FALSE);
33 }

Post-Processing—Stress Nodal Projection Method


Stress projection for nodal stress, σ̂ e , is similar to the heat flux projection on node q̂ea , the element stresses
a

are interpolated from the nodal stresses as

σe ≡ Na ( ξ, η ) σ̂e
h a
Eq. 4•193

The weighted-residual statement with Galerkin weighting that w = Na

∫ 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.

394 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
1 ElasticQ4::ElasticQ4(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
2 ...
3 if(Matrix_Representation::Assembly_Switch == Matrix_Representation::NODAL_STRESS) {
4 int stress_no = (ndf+1)*ndf/2;
5 the_element_nodal_value &= C0(nen*stress_no, (double*)0);
6 C0 projected_nodal_stress = SUBVECTOR("int, C0&", stress_no, the_element_nodal_value);
7 H0 Sigma = INTEGRABLE_VECTOR("int, Quadrature", 3, qp);
8 Sigma = 0.0;
9 for(int i = 0; i < nen; i++) {
10 B1 &= Nx[i][0]; B2 &= Nx[i][1];
11 DB[0][0] = Dv[0][0]*B1; DB[0][1] = Dv[0][1]*B2;
12 DB[1][0] = Dv[0][1]*B1; DB[1][1] = Dv[1][1]*B2;
13 DB[2][0] = Dv[2][2]*B2; DB[2][1] = Dv[2][2]*B1;
14 Sigma += DB(0)*(ul[i*ndf]+gl[i*ndf]) + DB(1)*(ul[i*ndf+1]+gl[i*ndf+1]);
15 }
16 for(int i = 0; i < nen; i++) {
17 C0 lumped_mass = ((H0)N[i]) | dv;
18 projected_nodal_stress(i) = ( ((H0)N[i])*Sigma | dv ) / lumped_mass;
19 }
20 } else stiff &= K | dv;
21 }
22 int main() {
23 ...
24 Matrix_Representation::Assembly_Switch = Matrix_Representation::NODAL_STRESS;
25 mr.assembly(FALSE);
26 cout << "nodal stresses: " << endl;
27 for(int i = 0; i < oh.total_node_no(); i++) {
28 int node_no = oh.node_array()[i].node_no();
29 cout << "{ " << node_no << "| "
30 << (mr.global_nodal_value()[i][0]) << ", "
31 << (mr.global_nodal_value()[i][1]) << ", "
32 << (mr.global_nodal_value()[i][2]) << "}" << endl;
33 }
34 ...
35 }

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.

Workbook of Applications in VectorSpace C++ Library 395


Chapter 4 Finite Element Method Primer

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.

396 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
Shear Locking of Bilinear 4-Node Element
The bilinear 4-node element has shape functions as

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

Λ in-plane bending key-stoning; u = xy, v = const.


(a) (b) (c)
Figure 4•45 Rectangular element shear locking analysis.

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.

Workbook of Applications in VectorSpace C++ Library 397


Chapter 4 Finite Element Method Primer
Let’s exam the “aliasing” of a quadratic solution u = ξ2 into a bilinear four-node element. The corresponding
nodal values û ea and discretized variable u eh are

ξ 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

398 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

ε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-

Workbook of Applications in VectorSpace C++ Library 399


Chapter 4 Finite Element Method Primer
torted at 2 × 2 Gauss integration points, while it is totally undisturbed at the center of the hourglass deformation
mode. That is the hourglass modes give zero energy if 1-point Gauss integration is used. An alternative view is
reveal by the rank of the element stiffness matrix. The bilinear four-node element has 4(nen) × 2(ndf) = 8 d.o.f. If
the three rigid body modes have been properly constrained for the problem, we are left with 8-3 = 5 d.o.f. The
rank of the stiffness matrix is provided by number of integration points (1) times the number of stress-strain rela-
tions (3); i.e., 1 × 3 = 3. Therefore, the rank deficiency for the 1-point integration element stiffness matrix is 5-3
= 2, which corresponding to the x-hourglass and y-hourglass modes. Therefore, in the selective reduced integra-
tion, the 2 × 2 integration on the terms involving the direct strains εx and εy provides a finite stiffness for the x-
hourglass and y-hourglass modes to prevent them from becoming spurious. The selective reduced integration on
the offending in-plane shear term is implemented in Program Listing 4•16 (project: “invariance_formulation” in
project workspace file “fe.dsw”).
However, the selective reduced integration for curing the in-plane shear locking has a side effect. For an iso-
parametric element such as the bilinear 4-node element, we expect spatial isotropy; i.e., the element is invariant
with respect to rotation since a complete order of polynomial has been used; i.e., the so-called completeness
requirement. This is true only if the element stiffness matrix is fully integrated. When the selective reduced inte-
gration is applied to the second term in Eq. 4•205 that involves in-plane shear strain γxy , the spatial isotropy
will be lost. Therefore the orientation of an element does matter.
A first-order approximation can be proposed to correct the frame dependent problem for the shear term. 1 The
idea is the shear term presented in Eq. 4•205 is not symmetrical. We can symmetrize the two off-diagonal terms
by choosing a local preferred coordinate system x’ as shown in Figure 4•46. The origin is at the centroid of the
element (computed as the intersections of two opposing mid-side line segments). The x’ and y’ axes are to make
angles with ξ and η axes in natural coordinates such that

∂ξ ∂η
------ 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.

400 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

#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) =

τy0 = τy10 = -75, τy5 = -150


gh_on_Gamma_h::Dirichlet;
for(int i = 0; i < col_node_no; i++) {
the_gh_array[node_order(i*row_node_no)](1) = gh_on_Gamma_h::Neumann;
if(i == 0 || i == (col_node_no-1)) the_gh_array[node_order(i*row_node_no)][1] = -75.0;
else the_gh_array[node_order(i*row_node_no)][1] = -150.0;
}
}
class Elastic_Invariant_Formulation_Q4 : public Element_Formulation { public:
Elastic_Invariant_Formulation_Q4(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Elastic_Invariant_Formulation_Q4(int, Global_Discretization&);
};

Workbook of Applications in VectorSpace C++ Library 401


Chapter 4 Finite Element Method Primer
Element_Formulation* Elastic_Invariant_Formulation_Q4::make(int en,
Global_Discretization& gd) { return new Elastic_Invariant_Formulation_Q4(en,gd); }
Elastic_Invariant_Formulation_Q4::Elastic_Invariant_Formulation_Q4(
int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
Quadrature qp(2, 4); 2 × 2 integration
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; H0 Nx = d(N) * d(X).inverse(); J dV(d(X).det());
volumetric terms
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), Wx, Wy;
∂N ∂N ∂N ∂N
Wx &= W_x[0][0]; Wy &= W_x[0][1];  ---------a ---------b  ---------a ---------b
C0 e = BASIS("int", ndf), E = BASIS("int", nen),  ∂x ∂x   ∂x ∂y 
λ
u = e*E, U = (e%e)*(E%E);
∂N ∂N ∂N ∂N
C0 stiff_vol = ( lambda_bar*( +((Wx*~Wx)*U[0][0]+(Wx*~Wy)*U[0][1]+  ---------a ---------b  ---------a ---------b
(Wy*~Wx)*U[1][0]+(Wy*~Wy)*U[1][1] ) ) ) | dV;  ∂y ∂x   ∂y ∂y 

Quadrature qp1(2, 1);


H1 z(2, (double*)0, qp1), zai, eta, 1 point integration(deviatoric stiffness
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 2, qp1); which only involve shear strain γxy)
zai &= z[0]; eta &= z[1];
n[0] = (1.0-zai)*(1.0-eta)/4.0; n[1] = (1.0+zai)*(1.0-eta)/4.0; ∂N ∂N ∂N ∂N
n[2] = (1.0+zai)*(1.0+eta)/4.0; n[3] = (1.0-zai)*(1.0+eta)/4.0;  ---------a ---------b  ---------a ---------b
 ∂x ∂x   ∂y ∂x 
H1 x = n*xl; H0 nx = d(n) * d(x).inverse(); J dv(d(x).det()); µ
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, nx), wx, wy; ∂N ∂N ∂N ∂N
 ---------a ---------b  ---------a ---------b
wx &= w_x[0][0]; wy &= w_x[0][1];
 ∂x ∂y   ∂y ∂y 
C0 stiff_dev_shear = mu_* (+( (wy*~wy)*U[0][0] +(wy*~wx)*U[0][1]+
(wx*~wy)*U[1][0] +(wx*~wx)*U[1][1] ) )| dv;

H1 x1 = N*xl; H0 nx1 = d(n) * d(x1).inverse(); J dv1(d(x1).det());


2 × 2 integration (deviatoric stiffness
H0 w_x1 = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, nx1), wx1, wy1; which only involve direct strains εx &
wx1 &= w_x1[0][0]; wy1 &= w_x1[0][1]; εy; notice that if the coordinates has
C0 stiff_dev_direct_strain = (2.0*mu_)*
(+( (wx1*~wx1)*U[0][0]+(wy1*~wy1)*U[1][1] ) )| dv1;
been rotated the local preferred coordi-
C0 stiff_dev = stiff_dev_shear + stiff_dev_direct_strain; nates is the same as the pure shear term
stiff &= stiff_vol + stiff_dev; in the above not the volumetric term.)
}
Element_Formulation* Element_Formulation::type_list = 0; ∂N a ∂N b
Element_Type_Register element_type_register_instance; 2  --------- --------- 0
∂x ∂x
static Elastic_Invariant_Formulation_Q4 µ
elastic_invariant_formulation_q4_instance(element_type_register_instance); ∂N a ∂N b
0 2  --------- ---------
int main() {
 ∂y ∂y 
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() << endl;
return 0;
}

Listing 4•16 Seletive reduce integration on the offending shear term (project workspace file “fe.dsw”,
project “invariance_formulation”.)

402 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

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.

1. p.292 in MacNeal, R.H., 1994, same as the above.

Workbook of Applications in VectorSpace C++ Library 403


Chapter 4 Finite Element Method Primer
The solutions of the selective reduced integration with invariance formulation is listed in TABLE 4•2. The
invariance formulation are performed on a distorted meshes as shown in Figure 4•48.

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.

0.0625 0.125 0.0625


(a)
Λ

1 δ δ

x-stretching & y-stretching x- tapering & y-tapering


rectangulars parallelogram trapezoids
(b)
Figure 4•48 (a) Distorted element mesh for testing invariance formulation in the
selective reduced integration for shear terms. (b) 5 eigenvectors for an arbitary shape
distortion (x-, y- translation and rotation are not included).

404 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
Quadratic Element: The Lagrangian 9-Node Element
The Lagrangian 9-node element is implemented as class “ElasticQ9” derived from class
Element_Formulation. The shape function is implemented based on a 4-to-9 nodes algorithm (see page 190 in
Chapter 3)

1 Quadrature qp(2, 9); // 2-dimension, 3 × 3 integration points;


2 H1 Z(2, (double*)0, qp), // Natrual Coordinates
3 N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 9, 2, qp), Zai, Eta;
4 Zai &= Z[0]; Eta &= Z[1];
// initial four corner nodes
5 N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4;
6 N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4;
// add ceter node
7 N[8] = (1-Zai.pow(2))*(1-Eta.pow(2));
// modification to four corner nodes due to the presence of the center node
8 N[0] -= N[8]/4; N[1] -= N[8]/4; N[2] -= N[8]/4; N[3] -= N[8]/4;
// add four edge nodes
9 N[4] = ((1-Zai.pow(2))*(1-Eta)-N[8])/2; N[5] = ((1-Eta.pow(2))*(1+Zai)-N[8])/2;
10 N[6] = ((1-Zai.pow(2))*(1+Eta)-N[8])/2; N[7] = ((1-Eta.pow(2))*(1-Zai)-N[8])/2;
// modification to four corner nodes due to the presence of the four edge nodes
11 N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2;
12 N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;
The element is registered with element type number “1” in project “2d_beam”. When define element in the
constructor of the discretized domain “Omega_h” this is the number to be referred to the “ElasticQ9” element.
For using this example, we set macro definition to “__LAGRANGIAN_9_NODES”. The results of tip deflection
of the problem in this section are listed in TABLE 4•3.We observe that the shear locking problem in bilinear four-
node element is easily removed by using higher-order interpolation functions.

Element Type Tip Deflection


ElasticQ9 -0.00503098
Analytical -0.00518750
TABLE 4•3. Tip deflection of Lagrangian 9-node element
comparing to the analytical solution of Eq. 4•175.

Workbook of Applications in VectorSpace C++ Library 405


Chapter 4 Finite Element Method Primer
Dilatation Locking of Nearly Incompressible Elasticity in Plane Strain
Considerable attention has been paid to the condition of incompressibility (with Poisson ratio ν = 0.5) or
nearly incompressibility ( ν → 0.5). We will show examples that standard element formulation, in plain strain
case, with ν → 0.5 will have its solution “locked” severely. A more systematic study is the main subject of
Chapter 5 on the “Mixed and hybrid finite element methods”. In this section, we introduce the popular engineer-
ing approach, the selective reduced integration for dilatational locking, which has been shown to be both very
simple and very successful. Let’s first resume the analysis for bending problem in the bilinear 4-node element in
plane strain. For ν = 0.5 in elasticity the condition is equivalent to imposing a kinematic constraint that the
material is incompressible. The analytical solution is1

1 ν
u = xy, and v = – --- x 2 – -------------------- y 2 Eq. 4•208
2 2(1 – ν )

The corresponding analytical strains are

εx y
εy ν
= – ---------------- y Eq. 4•209
(1 – ν)
γ xy 0

The volumetric strain is

ε v = ε x + εy =  --------------- y, and p = Kεv = --------------------


1 – 2ν Ey
Eq. 4•210
1–ν 3(1 – ν )

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 – ν)

The corresponding strains manifested in the bilinear 4-node element are

ε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.

406 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
Now the volumetric strain εv = y, which is a finite value. When ν → 0.5, K → ∞ and p → ∞ from Eq. 4•210. This
is the dilatation locking at the incompressible limit of ν → 0.5 . Comparing Eq. 4•209 and Eq. 4•213, both εy and
γxy are in error. The error is caused by the interpolating failure of the bilinear four-node element in representing
x2 and y2. The situation is exactly the same as in the shear locking problem. Therefore, the non-conforming ele-
ment (page 502 in Chapter 5), which adds back x2 and y2 to the set of the interpolation functions, will have the
capability to remedy both the shear locking and dilation locking problems for the bilinear four-node element.
A quick fix to solve this “dilatation locking” problem is that we can divide the stiffness matrix into volumet-
ric and deviatoric part. Then, the volumetric part is applied the reduced integration. With this selective reduced
integration scheme, the condition of constant volume constraint can be relaxed.
It is not immediately clear that how we can perform selective reduced integration on the B-matrix formula-
tion, that is

k epq = k eiajb = ∫ ε ( δu ) T Dε ( u )dΩ = e iT ∫ B aT D B b dΩe j Eq. 4•214


Ω Ω

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

K is the bulk modulus of the material. We define the devioatric strain εd as

mε m⊗m
ε d ≡ ε – --------- =  I – ----------------- ε
v
- Eq. 4•217
3  3 

The deviatoric stress σd is (in vector form σ = [σx, σy, τxy]T)

σ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.

Workbook of Applications in VectorSpace C++ Library 407


Chapter 4 Finite Element Method Primer
From Eq. 4•216 and Eq. 4•218 the volumetric-deviatoric split version of the B-matrix formulation (Eq. 4•214)
becomes

k epq = k eiajb = ∫ ε ( δu ) T σ ( u )dΩ = ∫ ε ( w )T [ σd ( u ) + mp ( u ) ]dΩ


Ω Ω

 
= 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 

We may define the volumetric stiffness and deviatoric stiffness separately as

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

1 Quadrature qp(2, 4); // 2 × 2 points standard integration


2 H1 Z(2, (double*)0, qp),
3 N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 4, 2, qp),
4 Zai, Eta;
5 Zai &= Z[0]; Eta &= Z[1];
6 N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4;
7 N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4;
8 H1 X = N*xl; // Physical Coordinates
9 H0 Nx = d(N) * d(X).inverse();
10 J dv(d(X).det());
11 Quadrature qp1(2, 1); // 1-point reduced integration
12 H1 z(2, (double*)0, qp1),
13 n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 4, 2, qp1),
14 zai, eta;
15 zai &= z[0]; eta &= z[1];
16 n[0] = (1-zai)*(1-eta)/4; n[1] = (1+zai)*(1-eta)/4;
17 n[2] = (1+zai)*(1+eta)/4; n[3] = (1-zai)*(1+eta)/4;
18 H1 x = n*xl;
19 H0 nx = d(n) * d(x).inverse();
20 J d_v(d(x).det()); 20 0
21 double d_0[3][3] = { {2.0, 0.0, 0.0}, // D 0 = 0 2 0
22 {0.0, 2.0, 0.0}, 00 1
23 {0.0, 0.0, 1.0}};
24 C0 D_0 = MATRIX("int, int, const double*", 3, 3, d_0[0]);

408 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
25 double m_0[3] = {1.0, 1.0, 0.0};
26 C0 m = VECTOR("int, const double*", 3, m_0); // m = [1, 1, 0]T
27 H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx),
28 Wx, Wy, B;
29 Wx &= W_x[0][0]; Wy &= W_x[0][1];
30 B &= (~Wx || C0(0.0)) &
31 (C0(0.0) || ~Wy ) &
32 (~Wy || ~Wx );
C0 stiff_dev = ((~B) * (mu_*(D_0-2.0/3.0*(m%m)) * B)) | dv; // kdev = e iT ∫ BaT µ  D 0 – --- m ⊗ m B b dΩ e j
2
33
3
34 H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, nx), Ω
35 wx, wy, b;
36 wx &= w_x[0][0]; wy &= w_x[0][1];
37 b &= (~wx || C0(0.0)) &
38 (C0(0.0) || ~wy ) &
39 (~wy || ~wx );
40 C0 stiff_vol = ((~b) * ((K_*(m%m)) * b)) | d_v; // kvol = e iT ∫ B aT K ( m ⊗ m )Bb dΩe j
41 stiff &= stiff_dev + stiff_vol; Ω

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 e= a ( φ ea, φeb ) = ∫ [ λ ( div N a • div N b ) + 2µ ( def N a ): def N b )]dΩ Eq. 4•222


and the indicial notation formulation of Eq. 4•161,

 
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

 Ω Ω

We notice that in Eq. 4•221, the bulk modulus1 is

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.

Workbook of Applications in VectorSpace C++ Library 409


Chapter 4 Finite Element Method Primer
At the nearly incompressible limit ( ν → 0.5 ), λ >> µ. We have K ≈ λ . The two first terms of Eq. 4•222 and Eq.
4•223 are approximately equivalent to the kvol in Eq. 4•221. We can simply choose these two terms for reduced
integration and the implementation is straight forward. The implementation can be activated, in project
“2d_beam”, by setting the macro definitions “__TEST_PLAIN_STRAIN”, “__NEARLY_INCOMPRES
SIBLE”, and“__TEST_SELECTIVE_REDUCED_INTEGRATION” together with corresponding macro defini-
tions for the above two formulations, “__TEST_COORDINATE_FREE_TENSORIAL_FORMULATION” and
“__TEST_INDICIAL_NOTATION_FORMULATION”, respectively. These two alternative formulations,
involve µ and λ, give the same results. With standard integration scheme, the tip-deflection is “-0.000149995”,
and with the reduced integration scheme, the tip-deflection is “-0.00311641”.

410 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
4.3.4 Patch Tests—Finite Element Test Suites for Software Quality Assurance (SQA)
Finite element is such a complicated method that the software quality assurance (SQA) can be quite a chal-
lenging task. As a framework based library, not a caned-program, fe.lib requires user’s participation in program-
ming to complete the application programs. Therefore, a well-thought-out plan for debugging and testing is of
primary importance for hands-on finite element practitioners. This section gives many examples of how to
develop proper test suites for finite element method. These test suites are based on a well known test plans1.

Patch Tests—Consistency and Stability


Consider an element patch shows in Figure 4•49 in plane stress with material properties of Young’s modulus
E = 1x103, and Poisson’s ratio ν = 0.3. A simple constant stress (strain) solution over entire problem domain is
assumed. In this case, the only non-zero stress is a constant stress in x-direction σx = 2, and σy = τxy = 0. The
strain-stress relation for the plane stress assumption gives solutions of constant strain, and displacement (u, v) as

σ 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.

Workbook of Applications in VectorSpace C++ Library 411


Chapter 4 Finite Element Method Primer

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

Figure 4•49 Patch of elements for consistency and stablility test.

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

Kijuj = fi Eq. 4•226

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

uj = (Kij)-1fi Eq. 4•227

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

412 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

#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];
}

Workbook of Applications in VectorSpace C++ Library 413


Chapter 4 Finite Element Method Primer
if(Matrix_Representation::Assembly_Switch == Matrix_Representation::REACTION) { Post-processing
stiff &= K | dv; the_element_nodal_value &= stiff * (ul+gl);
} else if(Matrix_Representation::Assembly_Switch == Matrix_Representation::STRESS) {
compute reaction
H0 Sigma = INTEGRABLE_VECTOR("int, Quadrature", 3, qp); Sigma = 0.0; compute stresses on Gauss integration
for(int i = 0; i < nen; i++) { B1 &= Nx[i][0]; B2 &= Nx[i][1]; points
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;
Sigma += DB(0)*(ul[i*ndf]+gl[i*ndf]) + DB(1)*(ul[i*ndf+1]+gl[i*ndf+1]);
}
int nqp = qp.no_of_quadrature_point();
for(int i = 0; i < nqp; i++) { cout << setw(9) << en
<< setw(14) << ((H0)X[0]).quadrature_point_value(i)
<< setw(14) << ((H0)X[1]).quadrature_point_value(i)
<< setw(14) << (Sigma[0].quadrature_point_value(i))
<< setw(14) << (Sigma[1].quadrature_point_value(i))
<< setw(14) << (Sigma[2].quadrature_point_value(i)) << endl;
}
} else if (Matrix_Representation::Assembly_Switch ==
Matrix_Representation::NODAL_STRESS) {
compute nodal stresses projection
int stress_no = (ndf+1)*ndf/2; the_element_nodal_value &= C0(nen*stress_no, (double*)0);
C0 projected_nodal_stress = SUBVECTOR("int, C0&", stress_no, the_element_nodal_value);
H0 Sigma = INTEGRABLE_VECTOR("int, Quadrature", 3, qp); Sigma = 0.0;
for(int i = 0; i < nen; i++) { B1 &= Nx[i][0]; B2 &= Nx[i][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;
Sigma += DB(0)*(ul[i*ndf]+gl[i*ndf]) + DB(1)*(ul[i*ndf+1]+gl[i*ndf+1]);
}
for(int i = 0; i < nen; i++) { C0lumped_mass = ((H0)N[i]) | dv;
projected_nodal_stress(i) = ( ((H0)N[i])*Sigma | dv ) / lumped_mass;
}
} else stiff &= K | dv;
}
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); U_h hh(ndf, oh); declare global discretization and matrix
Global_Discretization gd(oh, gh, uh);
Global_Discretization hd(oh, gh, hh);
representation
Matrix_Representation mr(gd);
Matrix_Representation::Assembly_Switch = Matrix_Representation::REACTION; compute reaction
mr.assembly(FALSE);
cout << "reaction:" << endl << (mr.global_nodal_value()) << endl;
Matrix_Representation::Assembly_Switch = Matrix_Representation::STRESS; compute stresses on Gauss points
cout << "gauss point stresses: " << endl;
cout.setf(ios::left,ios::adjustfield);
cout << setw(9) << " elem #, " << setw(14) << "x-coor.," << setw(14) << "y-coor.,"
<< setw(14) << "sigma-11," << setw(14) << "sigma-22," << setw(14) << "sigma-12" << endl;
mr.assembly(FALSE);
Matrix_Representation::Assembly_Switch = Matrix_Representation::NODAL_STRESS;
mr.assembly(FALSE);
compute nodal stresses projection
cout << "nodal stresses: " << endl << (mr.global_nodal_value()) << endl;
return 0;
}

Listing 4•17 Patch test A(project workspace file “fe.dsw”, project “patch_test” with Macro definition
“__PATCH_TEST_A” set at compile time).

414 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
activated by setting macro definition “__PATCH_TEST_B” in the same project. Again, both the standard inte-
gration and uniform reduced integration produce the exact internal nodal displacements as listed in TABLE 4•4.
Stability Requirement examines if the zero-energy modes (eigenvalues) of the stiffness matrix Kij can be
excited from loading fi on the boundaries. If this does occur the eigenvectors, with arbitrary magnitudes but no
energy contribution, will pollute the solution and render the solution useless. In the “Test C” in Figure 4•49,
node # 0 is fixed on both directions and node # 3 is fixed on x-direction but allowed to be moved on y-direction.
This suppresses three degree of freedoms, which is chosen to prohibit three modes of rigid body motions,
namely, x-translation, y-translation, and infinitesimal rotation. Note that fixing these three degree of freedoms to
zero is still consistent with the assumed solution of u = 0.002x, and v = -0.0006y. In this case, node #1 is given
loading of fx = 3, and node #2 is given loading of fx = 2, which is also the same as the reactions on these two
nodes computed from the reaction of Test A. The displacement solutions from Test C are, then, checked against
the assumed solutions.

(b) 1x1 solution (c) Pseudo-inverse 1x1


solution

(a) 2x2 solution

(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.

Workbook of Applications in VectorSpace C++ Library 415


Chapter 4 Finite Element Method Primer
With the same project “patch_test” in project workspace file “fe.dsw”, the Test C is activated by setting the
macro definition “__PATCH_TEST_C”. For standard integration (2 × 2), the exact solution in TABLE 4•4. is
reproduced. The solution magnified by 50 times is shown in Figure 4•50a. If we set macro definition
“__TEST_UNIFORM_REDUCED_INTEGRATION” (1-point Gaussian integration) at compile time, the solu-
tion rendered is useless as shown in Figure 4•50 (b). We can then set macro defintion
“__TEST_SINGULAR_VALUE_DECOMPOSITION” to analyze the problem. Under the uniform reduced
integration, the rank of the stiffness matrix has rank deficiency of 2 (the full rank = 13 under 2 × 2 integration,
and the rank = 11 under 1-point integration). With singular value decomposition the matrix can be solved with
the so-called Moore-Penrose (pseudo-) inverse as in page 40 of Chapter 1. The effect of this generalized inverse
is to filter out two eigen-modes corresponding to the two singular values which are very close to zero. These two
eigen-modes are plotted and compared to two spurious hourglass modes of a square bilinear element (some use
the “key-stoning mode” for bilinear element and reserve the term “hourglass mode” for quadratic element)1.
Note that we compare the outlines of these two eigen-modes to the two spurious modes of a square bi-linear ele-
ment. The internal nodes of the current element patch can be considered as to add to higher order variations on
top of the two spurious modes. However, we emphasize that the singular value decomposition is used as an ana-
lytical tool to analyze the rank-deficient nature of the problem. The computation cost of the singular value
decomposition is very expensive compared to that of the Cholesky decomposition for a symmetrical matrix; i.e.,
the most expensive one among the matrix solver provided in Chapter 1.

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.

416 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
Weak Patch Test for an Axisymmetrical Problem
The patch test require only when the mesh size “h” approach zero, the approximated solution should con-
verge to the exact solution. For problem in the Cartesian coordinates the “coefficients” of the governing partial
differential equation are constants. Therefore, an arbitrary mesh size should produce the exact solution. The
weak patch test, for problem written in other than the Cartesian coordinates, revert the criterion to pass the patch
test as a series of solutions converging to the exact solution when the mesh size approaches zero.
Consider an axisymmetrical problem as shown in Figure 4•51.1 For an axisymmetrical problem with coordi-
z
θ

h h

3 4 5
h
0 1 2 r

r=1

Figure 4•51An axisymmetricl problem for weak patch test.


nate system denoted as r, z, θ, and the displacement along θ-direction is assumed zero, and u, w are the displace-
ment along r-, and z- directions, respectively. We assume solution as

u = 2r, and w = 0 Eq. 4•228

The strain vector is defined as2

∂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.

Workbook of Applications in VectorSpace C++ Library 417


Chapter 4 Finite Element Method Primer

∂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

For isotropic material case the D matrix becomes

ν ν
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 – ν )

In the integration of axisymmetrical problem the stiffness matrix is

Ke = ∫ B T D BdΩ Eq. 4•232

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

k vol = e iT ∫ BaT 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•233
3

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

εr= εθ = σr = σθ = 2 Eq. 4•235

418 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
and all other stresses and strains as zero. The z-displacement of node number 1 is fixed to zero to prevent the z-
translation of the rigid body motion. For the patch test we take the element size “h” as 0.05, 0.1, 0.2, 0.4, and 0.8.
The Program Listing 4•18 implements the axisymmetrical patch test (Eq. 4•230, and Eq. 4•233 with definition of
D0 in Eq. 4•234). The results of nodal radial displacement (u) at node number 1 and 4 are all exact under the 2 × 2
integration scheme. The macro definition “__TEST_SELECTIVE_REDUCED_INTEGRATION” can be set at
compiled time for the selective reduced integration. The radial displacement on nodes 1 and 4 under the selective
reduced integration are shown in TABLE 4•5. It shows that when the element size “h” goes down, the radial dis-
placement solution converges quickly to the assumed solution

Element size “h” Node # 1, and 4


0.05 2.0
0.1 2.0
0.2 2.00003
0.4 2.00049
0.8 2.01114
TABLE 4•5. The radial displacement for axisymmetrical problem.

We notice that the implementation of the B-matrix for the axisymmetrical problem is implemented according to
Eq. 4•230 as

1 H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), ∂N


0 ---------a
2 Wr, Wz, B, R; ∂z
3 Wr &= W_x[0][0]; Wz &= W_x[0][1]; ∂N a
--------- 0
4 R &= (H0)X[0]; ∂r
Ba =
5 B &= (C0(0.0) || ~Wz )& // Na
6 (~Wr || C0(0.0) )& ------ 0
r
7 (~((H0)N)/R || C0(0.0) )& ∂N a ∂N a
8 (~Wz || ~Wr ); --------- ---------
∂z ∂r

Lines 5-8 use matrix concatenation operation to capture the semantics of B-matrix directly.

Workbook of Applications in VectorSpace C++ Library 419


Chapter 4 Finite Element Method Primer

#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);
}

420 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
ElasticAxisymmetricQ4::ElasticAxisymmetricQ4(int en, Global_Discretization& gd) :
Element_Formulation(en, gd) {
Quadrature qp(2, 4);
H1 Z(2, (double*)0, qp), Zai, Eta,
∂N a
0 ---------
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 4, 2, qp); ∂z
Zai &= Z[0]; Eta &= Z[1];
∂N
N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4; ---------a 0
N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4; ∂r
Ba =
H1 X = N*xl; Na
H0 Nx = d(N) * d(X).inverse(); ------ 0
J dV(d(X).det()); r
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), Wr, Wz, B, R; ∂N ∂N
Wr &= W_x[0][0]; Wz &= W_x[0][1]; R &= (H0)X[0]; ---------a ---------a
B &= (C0(0.0) || ~Wz ) & ∂z ∂r
(~Wr || C0(0.0)) &
(~((H0)N)/R || C0(0.0)) &
(~Wz || ~Wr );
double d_0[4][4]={ {2.0, 0.0,0.0,0.0}, 2 0 0 0
{0.0, 2.0,0.0,0.0},
{0.0, 0.0,2.0,0.0}, D0 = 0 2 0 0
{0.0, 0.0,0.0,1.0} }; 0 0 2 0
C0 D_0 = MATRIX("int, int, const double*", 4, 4, d_0[0]); 0 0 0 1
double m_0[4] = {1.0, 1.0, 1.0, 0.0};
C0 m = VECTOR("int, const double*", 4, m_0);
C0 stiff_dev = 2.0*PI_*(((~B) * ((mu_*(D_0-2.0/3.0*(m%m))) * B) * R) | dV); m = [1, 1, 1, 0]T
Quadrature qp1(2, 1);
H1 z(2, (double*)0, qp1), zai, eta, 2 × 2 integration on
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 2, qp1);
k dev= e iT ∫ B aT µ  D0 – --- m ⊗ m B b dΩ e j
zai &= z[0]; eta &= z[1]; 2
n[0] = (1-zai)*(1-eta)/4; n[1] = (1+zai)*(1-eta)/4;  3 
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());
H0 w_x= INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, nx), wr, wz, b, r;
wr &= w_x[0][0]; wz &= w_x[0][1]; r &= (H0)x[0];
b &= (C0(0.0) || ~wz ) &
(~wr || C0(0.0)) &
(~((H0)n)/r || C0(0.0)) & 1-point integration on
(~wz|| ~wr );
C0 stiff_vol = 2.0*PI_*(((~b) * ((K_*(m%m)) * b) * r) | dv); k vol = e iT ∫ BaT K ( m ⊗ m )B b dΩe j
stiff &= stiff_vol + stiff_dev;
} Ω
Element_Formulation* Element_Formulation::type_list = 0;
Element_Type_Register element_type_register_instance;
static ElasticAxisymmetricQ4 elasticaxisymmetricq4_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() << endl;
return 0;
}

Listing 4•18 Axisymmetrical patch test (project workspace file “fe.dsw”, project
“axisymmetrical_patch_test” with Macro definition

Workbook of Applications in VectorSpace C++ Library 421


Chapter 4 Finite Element Method Primer
Higher-Order Patch Test
In the patch Test A, B, and C, the assumed solution is linear. We now study when the assumed solution is
quadratic, which may reveal additional problems. In the first set of problem, the element shape sensitivity effect
is considered for eight-nodes serendipity element and nine-nodes Lagrangian element. This is followed by
robustness of an element formulation when the material becomes incompressible. The successfulness of the
selective reduced integration will be evident.

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.

Distortion Integration Points 8-nodes Quadrilateral 9-nodes Quadrilateral


d=0 3×3 0.750000 0.750000
d=0 2×2 0.750000 0.750000
d=1 3×3 0.744849 0.750000
d=1 2×2 0.750000 0.788531
d=2 3×3 0.666839 0.750000
d=2 2×2 0.750000 0.676616
TABLE 4•6. Tip deflection of 8-nodes and 9-nodes quadrilaterals.

422 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
The exact solution is “0.75”. The standard integration scheme (3 × 3) for 8-nodes and 9-nodes elements with no
distortion both match the exact solution. When the distortion occurs, the accuracy of the 8-nodes element deteri-
orates fast when 9-nodes element is still capable of producing the exact solution. This is because the 9-nodes ele-
ment is capable of reproducing arbitrary quadratic displacement of straight-edged quadrilateral while the 8-nodes
element is not.1
With uniform reduced integration (2 × 2), the reverse is true. Each integration point contribute to 3 indepen-
dent relations from the definitions of 3 strain equations. For the present case 2 × 2 uniform reduced integration
gives 3 × 8 = 24 independent relations. The 9-nodes element has “total degree of freedom” = 15(nodes) × 2(dofs)-
4(constraints) = 26 > 24. Therefore, under this integration scheme the 9-nodes element is rank deficient. The
accuracy of the solution collapses fast with the increasing amount of the distortion. For the 8-nodes element, the
total degree of freedoms is 13 × 2-4 = 22 < 24, which is not only rank sufficient, but also less stiff compared to 8-
nodes element with standard integration scheme. Therefore, it produces better result. The displacement formula-
tion usually leads to over-estimated stiffness. The lowest order of numerical integration required for convergence
relaxes the stiffness and produces improved results.2

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.

Workbook of Applications in VectorSpace C++ Library 423


Chapter 4 Finite Element Method Primer

8 elements

32 elements

128 elements

512 elements

Figure 4•53 Mesh refinement of 4-nodes quadrilateral element.

Number of ν = 0.3 ν = 0.3 ν = 0.4999 ν = 0.4999 ν = 0.4999


E.lements (standard) (selective reduced (standard) (selective reduced (selective reduced on
on shear) on dilatation) shear & dilatation)
8 0.6920159 1.097860 0.00271635 0.666701 0.964387
32 0.7295910 0.839392 0.00799228 0.595018 0.655087
128 0.7443220 0.772274 0.02823080 0.570704 0.584802
512 0.7485400 0.755571 0.09621180 0.564732 0.568095
TABLE 4•7. Convergence of four node bi-linear element with selective reduced integration on
offending shear terms to prevent shear-locking ν = 0.3 in plane stress case, and selective reduced
integration on volumetric terms when the Possion ratios ν = 0.4999 in plane strain case to prevent
dilatational locking.

424 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
4.3.5 Stokes Flow
For a fluid particle with density ρ and velocity u relative to an inertial frame of reference. The Newton’s sec-
ond law of motion requires the linear momentum of the fluid particle is equal to the forces applied to it.

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

τ = 2µ def u + λ' I div u Eq. 4•238

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

p = – λ div u Eq. 4•239

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

λ = (107~1010) µ Eq. 4•240

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.

Workbook of Applications in VectorSpace C++ Library 425


Chapter 4 Finite Element Method Primer
∂u
ρ ------ + ρu • grad u + grad p = div ( 2µ def u ) + f Eq. 4•241
∂t

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

ρu • grad u + grad p = div ( 2µ def u ) + f Eq. 4•242

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

grad p = div ( 2µ def u ) + f Eq. 4•244

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

k dev= e iT ∫ B aT µ  D 0 – --- m ⊗ m B b dΩ e j , and k vol = e iT ∫ B aT K ( m ⊗ m )B b dΩe j


2
 
Eq. 4•245
3
Ω Ω

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.

426 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
Plane Couette-Poiseuille Flow
Consider a plane uni-directional flow (v = w = 0) drives by both pressure gradient (the Poiseuille flow) and
relative motion (U) of two bounding plates (the Couette flow) as shown in Figure 4•54 . The distance between
two rigid plates is “d” with the pressure gradient from the entrance of the flow to the exit as -∇p = G. The viscos-
ity of the fluid is µ. The velocity profile can be expressed as a function of y coordinate1

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

p = GL = 40. µ=1 p=0 d=1

L = 10

Figure 4•54 Plane Couette-Poiseuille flow problem.

1. p. 182 in Batchelor, G.K., 1967, “An introduction to fluid dynamics”, Cambridge University Press, UK.

Workbook of Applications in VectorSpace C++ Library 427


Chapter 4 Finite Element Method Primer

1 1

0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.2 0.4 0.6 0.8 1 0.1 0.2 0.3 0.4 0.5

Plane Couette Flow


+ Plane Poiseuille Flow

0.8

0.6

0.4

0.2

0.2 0.4 0.6 0.8 1


Plane Couette-Poiseuille Flow
Figure 4•55 Plane Couette flow and plane Poiseuille flow. The exact solutions are shown in
solid curves the finie element solutions are shown in dashed curves with arrows. The finite
element solutions are velocity profiles taken from the middle of the channel to avoid
entrance and exit effect.

428 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
#include "include\fe.h" a separate source file “elasticq9.cpp”
static const double mu_ = 1.0; static const double lambda_ = 1.0e8*mu_;
class ElasticQ9 : public Element_Formulation { public:
taken from plane elasticity problem
ElasticQ9(Element_Type_Register); penalty parameter is λ = 108 µ
Element_Formulation *make(int, Global_Discretization&);
ElasticQ9(int, Global_Discretization&); };
ElasticQ9::ElasticQ9(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation* ElasticQ9::make(int en, Global_Discretization& gd) {
return new ElasticQ9(en,gd); }
ElasticQ9::ElasticQ9(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp), Zai, Eta, 3x3 integration
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 9, 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; 9-node Lagrangian shape functions
N[8] = (1-Zai.pow(2))*(1-Eta.pow(2));
N[0] -= N[8]/4; N[1] -= N[8]/4; N[2] -= N[8]/4; N[3] -= N[8]/4;
N[4] = ((1-Zai.pow(2))*(1-Eta)-N[8])/2; N[5] = ((1-Eta.pow(2))*(1+Zai)-N[8])/2;
N[6] = ((1-Zai.pow(2))*(1+Eta)-N[8])/2; N[7] = ((1-Eta.pow(2))*(1-Zai)-N[8])/2;
N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2; N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;
Quadrature qp1(2, 4);
H1 z(2, (double*)0, qp1), zai, eta,
2x2 reduced integration
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 9, 2, qp1);
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;
9-node Lagrangian shape functions
n[8] = (1-zai.pow(2))*(1-eta.pow(2)); n[0] -= n[8]/4; n[1] -= n[8]/4; n[2] -= n[8]/4; n[3] -= n[8]/4;
n[4] = ((1-zai.pow(2))*(1-eta)-n[8])/2; n[5] = ((1-eta.pow(2))*(1+zai)-n[8])/2;
n[6] = ((1-zai.pow(2))*(1+eta)-n[8])/2; n[7] = ((1-eta.pow(2))*(1-zai)-n[8])/2;
n[0] -= (n[4]+n[7])/2; n[1] -= (n[4]+n[5])/2; n[2] -= (n[5]+n[6])/2; n[3] -= (n[6]+n[7])/2;
B-matrix formulation for incompressib-
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dV(d(X).det()); lility
H1 x = n*xl; H0 nx = d(n) * d(x).inverse(); J dv(d(x).det());
#if defined(__TEST_B_MATRIX_FORMULATION) λλ0 2µ 0 0
double d_lambda[3][3] = { {lambda_, lambda_, 0.0}, {lambda_, lambda_, 0.0}, {0.0, 0.0, 0.0} }; Dλ = λ λ 0 , Dµ = 0 2µ 0
C0 D_lambda = MATRIX("int, int, const double*", 3, 3, d_lambda[0]);
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, nx), wx, wy, b;
0 0 0 0 0 µ

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;
}

Workbook of Applications in VectorSpace C++ Library 429


Chapter 4 Finite Element Method Primer
#include "include\fe.h"
static const int row_node_no = 7; 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 int row_segment_no = row_node_no-1;
static const double L_ = 10.0; static const double c_ = 0.125;
static const double h_e_ = L_/((double)row_segment_no); static const double mu_ = 1.0;
static const double lambda_ = 1.0e8*mu_; static const double p_ = 40.0;
Omega_h::Omega_h() { double v[2]; int ena[9]; Omega_eh *elem;
for(int i = 0; i < col_node_no; i++) for(int j = 0; j < row_node_no; j++) {
int nn = i*row_node_no+j; v[0] = ((double)j)*h_e_; v[1] = ((double)i)*c_;
define nodes
Node* node = new Node(nn, 2, v); the_node_array.add(node); }
for(int i = 0; i < col_element_no; i++) for(int j = 0; j < row_element_no; j++) { define elements
int en = i * row_element_no + j, fnn = i * 2 * row_node_no + j * 2;
ena[0] = fnn; ena[1] = fnn + 2; ena[2] = ena[1] + 2*row_node_no;
ena[3] = ena[0] + 2*row_node_no; ena[4] = ena[0] + 1; ena[5] = ena[1] + row_node_no;
ena[6] = ena[3] + 1; ena[7] = ena[0] + row_node_no; ena[8] = ena[4]+row_node_no;
elem = new Omega_eh(en, 0, 0, 9, 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);
B.C.
#if defined(__TEST_PLANE_POISEUILLE_FLOW) Poiseuille flow only
for(int i = 0; i < row_node_no; i++) {
the_gh_array[node_order(i)](0) = the_gh_array[node_order(i)](1) =
the_gh_array[node_order((col_node_no-1)*row_node_no+i)](0) =
the_gh_array[node_order((col_node_no-1)*row_node_no+i)](1)=gh_on_Gamma_h::Dirichlet;}
double weight[9] = { 0.0, 3.0/2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 3.0/2.0, 0.0 }; open integration rule (see Numerical
for(int i = 1; i < col_node_no -1; i++) {
the_gh_array[node_order(i*row_node_no)](0) = gh_on_Gamma_h::Neumann;
Reciepe)
the_gh_array[node_order(i*row_node_no)][0] = p_*weight[i]*c_; }
#elif defined(__TEST_PLANE_COUETTE_FLOW) Couette flow only
for(int i = 0; i < row_node_no; i++) {
the_gh_array[node_order(i)](0) = the_gh_array[node_order(i)](1) =
the_gh_array[node_order((col_node_no-1)*row_node_no+i)](0) =
the_gh_array[node_order((col_node_no-1)*row_node_no+i)](1) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order((col_node_no-1)*row_node_no+i)][0] = 1.0; }
#else
for(int i = 0; i < row_node_no; i++) { Poiseuille & Couette flow
the_gh_array[node_order(i)](0) = the_gh_array[node_order(i)](1) =
the_gh_array[node_order((col_node_no-1)*row_node_no+i)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order((col_node_no-1)*row_node_no+i)](1) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order((col_node_no-1)*row_node_no+i)][0] = 1.0; }
double weight[9] = { 0.0, 3.0/2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 3.0/2.0, 0.0 };
for(int i = 1; i < col_node_no -1; i++) {
the_gh_array[node_order(i*row_node_no)](0) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(i*row_node_no)][0] = p_*weight[i]*c_; }
#endif
}
class ElasticQ9 : public Element_Formulation {public:
declare “ElasticQ9” class
ElasticQ9(Element_Type_Register); Element_Formulation *make(int, Global_Discretization&);
ElasticQ9(int, Global_Discretization&); };
Element_Formulation* Element_Formulation::type_list = 0;
Element_Type_Register element_type_register_instance;
static ElasticQ9 stokesq9_instance(element_type_register_instance); register “ElasticQ9” as element # 0
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);
solution phase
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•19 Plane Couette-Poiseuille flow in project “plane_couette_poiseuille_flow”

430 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
Driven Cavity Flow1
The stokes flow in a square cavity is shown in Figure 4•56a. The bottom and the two sides are rigid-walls.
The top is a boundary with velocity given as u(x) = 4(1-x)x. This velocity boundary condition causes a convect-
ing current in the cavity, which is known as forced convection. The top horizontal velocity boundary conditions
vanish at the two top corners, which are to avoid the difficulty in defining boundary conditions at these two cor-
ner nodes.2 Program Listing 4•20, the project “square_cavity_flow” in project workspace file “fe.dsw”, is imple-
mented for this computation. Again, the “elasticq9.cpp” is a separate compilation unit for this project. The
penalty method (λ = 108 µ) is used with selective reduced integration and the 9-nodes Lagrangian quadrilateral
element. The velocity vectors are shown in Figure 4•56b.
y

(0, 1) u = 4(1-x)x (1, 1)

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.

Workbook of Applications in VectorSpace C++ Library 431


Chapter 4 Finite Element Method Primer

#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”.).

432 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
4.3.6 Plate Bending Problems
The plate theory probably is only to interest the structural engineers. However, it has often been argued that
since the plate bending is the subject in the fourth-order differential equation that has been most extensively stud-
ied in finite element. The experiences gained in the plate finite element analysis may serve as an important exam-
ple for solving other fourth-order partial differential equation, such as the biharmonic equation of general
physical interests.

Basic Plate Theory


The basic assumption of the plate is that the plane sections, “fiber”, remain plane under deformation (see Fig-
ure 4•57a). Each lamina, which is parallel to the mid-surface, is assumed to be under plane stress; e.g., σz = 0.
We also assumed, inconsistent to the plane stress assumption, that εz is almost negligible, so w(xα, xβ) does not
vary with thickness (z = [-t/2, t/2]). The displacements can be expressed as.

uα = uα0-θαz, uβ = uβ0-θβz, w = w0, or


u = u0-θxz, v = v0-θyz, w = w0 Eq. 4•248

The membrane bending strains can be expressed as1


------ 0
εx ∂x
ε = εy = – z 0 ----- ∂ θ x = – zL θ Eq. 4•249
-
∂y θ y
γ xy ∂ ∂
------ ------
∂y ∂x

γα: shear strain


(a) (b)

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.

Workbook of Applications in VectorSpace C++ Library 433


Chapter 4 Finite Element Method Primer
and the transverse shear strains [γx, γy] as

∂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

where D, assumed plane stress, is defined as

1 ν 0
Et 3 ν 1 0
D = ------------------------- Eq. 4•252
12 ( 1 – ν 2 ) 1–ν
0 0 ------------
2

The shear forces [Sx, Sy] are

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

L T M + S = 0, and ∇ T S + q = 0 Eq. 4•254

or we can express their components explicitly in matrix form as

∂ ∂
------ 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

434 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
Kirchhoff (Thin-) Plate Theory and Finite Element Formulation—C1 Continuity Requirement
In thin plate theory, we assume that the fiber remains normal to the mid-surface during deformation.There-
fore, the transverse shear strains are all zero. That is, γ = 0 , and from Eq. 4•250, we identify

θ = ∇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

( L∇ ) T D L∇w – q = 0 Eq. 4•258

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

∂4w ∂4w ∂4w 12 ( 1 – ν 2 )


---------- + 2 -----------------
- + ---------- – q ------------------------- = 0 Eq. 4•260
∂x 4 ∂x ∂y 2 2 ∂y 4 Et 3

The homogeneous solution for a simply supported rectangular plate with lengths of “a” and “b” has the simple
form of

w = cos  ----------- cos  ---------- , where m, n = 1, 3, 5, …


mπx nπy
Eq. 4•261
a b
The finite element formulation is obtained by substituting element shape function (Na) into Eq. 4•256 to Eq.
4•259. The B-matrix is defined as

B a = ( L∇ )Na Eq. 4•262

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.

Workbook of Applications in VectorSpace C++ Library 435


Chapter 4 Finite Element Method Primer
From Eq. 4•251 and Eq. 4•256, we have the moment vector as

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

Nonconforming Rectangular Plate Element (12 degrees of freedom)


The nodal variables for this four-node rectangular element is

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

436 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
where Ca is defined as

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

Therefore, the B-matrix is defined as

B = ( L∇ )PC – 1 Eq. 4•272

where the shape function is

N = PC-1 Eq. 4•273

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.

Mesh Center Deflection


2×2 251691
4×4 234449
8×8 229464
16 × 16 228186
Exact 226800
TABLE 4•8. Center deflection.

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.

Workbook of Applications in VectorSpace C++ Library 437


Chapter 4 Finite Element Method Primer

#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());

438 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
{
H2 Z(2, (double*)0, qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE( Shape functions N
"int, int, Quadrature", nen*ndf/*nenxndf*/, 2/*nsd*/, qp),
Zai, Eta;
Zai &= Z[0]; Eta &= Z[1];
C0 C(12, 12, (double*)0),
C_sub = SUBMATRIX("int, int, C0&", 3, 12, C);
for(int i = 0; i < nen; i++) {
C0 x(xl[i][0]), y(xl[i][1]), x2 = x.pow(2), x3 = x.pow(3), y2 = y.pow(2), y3 = y.pow(3),
zero = C0(0.0), one = C0(1.0); Eq. 4•270 for C-matrix
C_sub(i) =
( one | x | y | x2 | (x*y) | y2 | x3 |(x2*y) | (x*y2) | y3 | (x3*y) | (x*y3) ) &
(zero|zero|-one|zero |(-x)|(-2.0*y)|zero |(-x2) |(-2*x*y)|(-3*y2)|(-x3) |(-3*x*y2) ) &
(zero|one |zero|(2*x)|y |zero |(3*x2)|(2*x*y) |y2 |zero |(3*x2*y)|y3 );
}
C0 C_inv = C.inverse();
H2 P = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE( C-1
"int, int, Quadrature", 12/*nenxndf*/, 2/*nsd*/, qp);
{
H2 x = X[0], y = X[1];
P[0] = 1.0; P[1] = x; P[2] = y; P[3] = x.pow(2); P=
P[4] = x*y; P[5] = y.pow(2); P[6] = x.pow(3);
2
P[7] = x.pow(2)*y; P[8] = x*y.pow(2); P[9] = y.pow(3); 1 x y x 2 xy y x 3 x 2 y xy 2 y 3 x 3 y xy 3
P[10] = x.pow(3)*y; P[11] = x*y.pow(3);
}
for(int i = 0; i < 12; i++) N[i] = P * C_inv(i);
H0 Nxx = INTEGRABLE_MATRIX("int, int, Quadrature", 2*nen*ndf, 2, qp);
N = PC-1
H0 w_xx = INTEGRABLE_SUBMATRIX("int, int, H0&", 2, 2, Nxx); ∂2
for(int i = 0; i < nen*ndf; i++) w_xx(i) = (~dx_inv) * dd(N)(i) * dx_inv; --------
H0 B = (~w_xx[0][0]) & ∂x 2
(~w_xx[1][1]) &
B a = ( L∇ )Na, and L∇ = ∂2
(2.0*(~w_xx[0][1])); --------
stiff &= ((~B) * (D * B)) | dv;
∂y 2
double f_0 = 1.0; k eiajb = e iT ∫ B aT D B b dΩe j ∂2
2 -------------
force &= (((H0)N)*f_0) | dv;
Ω ∂x∂y
}
}
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static PlateR4 plate_r4_instance(element_type_register_instance);
int main() {
int ndf = 3;
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 << "[w, -dw/dy, dw/dx]:" << endl;
for(int i = 0; i < row_node_no; i++)
for(int j = 0; j < row_node_no; j++)
cout << "#" << (i*row_node_no+j) << ": " << gd.u_h()[i*row_node_no+j] << endl;
return 0;
}

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).

Workbook of Applications in VectorSpace C++ Library 439


Chapter 4 Finite Element Method Primer

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 H2 Z(2, (double*)0, qp),


2 N = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(
3 "int, int, Quadrature", nen*ndf/*nenxndf*/, 2/*nsd*/, qp),
4 Zai, Eta;
5 Zai &= Z[0]; Eta &= Z[1];
6 double a = fabs( ((double)(xl[0][0]-xl[1][0])) )/2.0,

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.

440 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
7 b = fabs( ((double)(xl[2][1]-xl[1][1])) )/2.0,
8 zai[4] = {-1.0, 1.0, 1.0, -1.0}, eta[4] = {-1.0, -1.0, 1.0, 1.0};
9 for(int i = 0; i < nen; i++) {
10 N[i*ndf] = (Zai*zai[i]+1)*(Eta*eta[i]+1)*(2+Zai*zai[i]+Eta*eta[i]-Zai.pow(2)-Eta.pow(2))/8.0;
11 N[i*ndf+2] = a*zai[i]*(Zai*zai[i]+1).pow(2)*(Zai*zai[i]-1)*(Eta*eta[i]+1)/8.0;
12 N[i*ndf+1] = -b*eta[i]*(Zai*zai[i]+1)*(Eta*eta[i]+1).pow(2)*(Eta*eta[i]-1)/8.0;
13 }

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.

Conforming Rectangular Plate Element (16 degrees of freedom)


Instead of Eq. 4•265, we can extend the nodal variables to

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

and parallel to the derivation of Eq. 4•269, we have

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

Workbook of Applications in VectorSpace C++ Library 441


Chapter 4 Finite Element Method Primer

( ξ + ξ 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. .

Mesh Center Deflection


2×2 363735
4×4 275114
8×8 242597
16 × 16 232124
Exact 226800
TABLE 4•9. Center deflection.

Triangular Plate Element (9 degrees of freedom)


For triangular element we use the area coordinates L0, L1, and L2. The polynomial has 9-terms to match the
9-dof on the three corner nodes. Therefore, P can be defined as3

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.

442 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

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

c 2  L 02 L 1 + --- L 0 L 1 L 2 – c 1  L 2 L 02 + --- L 0 L 1 L 2


1 1
2 2

where b0 = y1- y2, and c0 = x2-x1. The explicit shape function for the triangular element can be implemented as

1 H2 L(2, (double*)0, qp), // area coordinates


2 N = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(
3 "int, int, Quadrature", 9, 2, qp), // shape functions
4 L0 = L[0], L1 = L[1], L2 = 1.0 - L0 - L1;
5 double b0 = (double)(xl[1][1]-xl[2][1]), c0 = (double)(xl[2][0]-xl[1][0]),
6 b1 = (double)(xl[2][1]-xl[0][1]), c1 = (double)(xl[0][0]-xl[2][0]),
7 b2 = (double)(xl[0][1]-xl[1][1]), c2 = (double)(xl[1][0]-xl[0][0]);
8 N[0] = L0+L0.pow(2)*L1+L0.pow(2)*L2-L0*L1.pow(2)-L0*L2.pow(2); // first node
9 N[1] = b2*(L0.pow(2)*L1+L0*L1*L2/2.0)-b1*(L2*L0.pow(2)+L0*L1*L2/2.0);
10 N[2] = c2*(L0.pow(2)*L1+L0*L1*L2/2.0)-c1*(L2*L0.pow(2)+L0*L1*L2/2.0);
11 N[3] = L1+L1.pow(2)*L2+L1.pow(2)*L0-L1*L2.pow(2)-L1*L0.pow(2); // second node
12 N[4] = b0*(L1.pow(2)*L2+L0*L1*L2/2.0)-b2*(L0*L1.pow(2)+L0*L1*L2/2.0);
13 N[5] = c0*(L1.pow(2)*L2+L0*L1*L2/2.0)-c2*(L0*L1.pow(2)+L0*L1*L2/2.0);
14 N[6] = L2+L2.pow(2)*L0+L2.pow(2)*L1-L2*L0.pow(2)-L2*L1.pow(2); // third node
15 N[7] = b1*(L2.pow(2)*L0+L0*L1*L2/2.0)-b0*(L1*L2.pow(2)+L0*L1*L2/2.0);
16 N[8] = c1*(L2.pow(2)*L0+L0*L1*L2/2.0)-c0*(L1*L2.pow(2)+L0*L1*L2/2.0);
Program Listing 4•22 implements the 9-dof triangular plate bending problem. The project
“triangle_plate_bending” in project workspace file “fe.dsw” implements the 9-dofs triangular element for plate
bending problem. The maximum deflection, for a (4 × 4) × 2 triangular mesh, is 205175.

Workbook of Applications in VectorSpace C++ Library 443


Chapter 4 Finite Element Method Primer

#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());

444 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
{
H2 L(2, (double*)0, qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE( shape functions N
"int, int, Quadrature", 9, 2, qp),
L0 = L[0],
L1 = L[1],
L2 = 1.0 - L0 - L1;
double b0 = (double)(xl[1][1]-xl[2][1]),
c0 = (double)(xl[2][0]-xl[1][0]),
b0 = y1- y2, and c0 = x2-x1.
b1 = (double)(xl[2][1]-xl[0][1]),
c1 = (double)(xl[0][0]-xl[2][0]),
b2 = (double)(xl[0][1]-xl[1][1]),
c2 = (double)(xl[1][0]-xl[0][0]);
N[0] = L0+L0.pow(2)*L1+L0.pow(2)*L2-L0*L1.pow(2)-L0*L2.pow(2); explicit shape functions
N[1] = b2*(L0.pow(2)*L1+L0*L1*L2/2.0)-b1*(L2*L0.pow(2)+L0*L1*L2/2.0);
N[2] = c2*(L0.pow(2)*L1+L0*L1*L2/2.0)-c1*(L2*L0.pow(2)+L0*L1*L2/2.0);
N[3] = L1+L1.pow(2)*L2+L1.pow(2)*L0-L1*L2.pow(2)-L1*L0.pow(2);
N[4] = b0*(L1.pow(2)*L2+L0*L1*L2/2.0)-b2*(L0*L1.pow(2)+L0*L1*L2/2.0);
N[5] = c0*(L1.pow(2)*L2+L0*L1*L2/2.0)-c2*(L0*L1.pow(2)+L0*L1*L2/2.0);
N[6] = L2+L2.pow(2)*L0+L2.pow(2)*L1-L2*L0.pow(2)-L2*L1.pow(2); ∂2
--------
N[7] = b1*(L2.pow(2)*L0+L0*L1*L2/2.0)-b0*(L1*L2.pow(2)+L0*L1*L2/2.0); ∂x 2
N[8] = c1*(L2.pow(2)*L0+L0*L1*L2/2.0)-c0*(L1*L2.pow(2)+L0*L1*L2/2.0);
H0 Nxx = INTEGRABLE_MATRIX("int, int, Quadrature", 2*nen*ndf, 2, qp); Ba = ( L∇ )N a , and L∇ = ∂2
--------
H0 w_xx = INTEGRABLE_SUBMATRIX("int, int, H0&", 2, 2, Nxx); ∂y 2
for(int i = 0; i < nen*ndf; i++)
w_xx(i) = (~dx_inv) * dd(N)(i) * dx_inv; k eiajb = e iT ∫ BaT D Bb dΩe j ∂2
2 -------------
H0 B = (~w_xx[0][0]) & Ω ∂x∂y
(~w_xx[1][1]) &
(2.0*(~w_xx[0][1]));
stiff &= ((~B) * (D * B)) | dv;
double f_0 = 1.0;
force &= (((H0)N)*f_0) | dv;
}
}
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static PlateT3 plate_t3_instance(element_type_register_instance);
int main() {
int ndf = 3;
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 << "[w, -dw/dy, dw/dx]:" << endl;
for(int i = 0; i < row_node_no; i++)
for(int j = 0; j < row_node_no; j++)
cout << "#" << (i*row_node_no+j) << ": " << gd.u_h()[i*row_node_no+j] << endl;
return 0;
}

Listing 4•22 9 dof triangular plate bending using nonconformming rectangular element (project workspace
file “fe.dsw”, project “triangular_plate_bending”).

Workbook of Applications in VectorSpace C++ Library 445


Chapter 4 Finite Element Method Primer
Morley’s Triangular Plate Element (6 degrees of freedom)
A complete quadratic polynomial has only six terms as

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

Figure 4•59 Morley’s six degrees of freedom triangular element.

Parallel to the derivation of Eq. 4•269 for a generic shape function, we have

w0 = α0, w1 = α1, w2 = α2 Eq. 4•282

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.

446 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems
∂(P α)
----------------- = α 0 + α 3 L 1 + α 5 L 2
∂L 0
∂(P α)
----------------- = α 1 + α 3 L 0 + α 4 L 2
∂L 1
∂( Pα)
----------------- = α 2 + α 4 L 1 + α 5 L 0 Eq. 4•285
∂L 2

Therefore, using Eq. 4•283, we have

 ∂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∆

Therefore, C can be expressed as

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,

k epq = k eiajb = e iT ∫ BaT D B b dΩe j Eq. 4•288


Recall it has been defined with a particular choice

∂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

Workbook of Applications in VectorSpace C++ Library 447


Chapter 4 Finite Element Method Primer
where n = [nx, ny]T is the outward unit surface normal on the mid-side node. Therefore, the B-matrix corre-
sponding to θ n -dof is multiplied with a factor “(nx - ny)” to take care of the conventional choice in Eq. 4•289.
Program Listing 4•23 implements the Morley’s 6-dof triangular plate element. The result of the computation are
shown in TABLE 4•10..

No. of Elements Center Deflection


(4 × 4) × 2 125704
(8 × 8) × 2 165518
(16 × 16) × 2 192789
Exact 226800
TABLE 4•10. Center Deflection of Morley’s
triangular plate element.

448 Workbook of Applications in VectorSpace C++ Library


Two Dimensional Problems

#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]);

Workbook of Applications in VectorSpace C++ Library 449


Chapter 4 Finite Element Method Primer

PlateMorley6::PlateMorley6(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {


Quadrature qp(2, 16); coordinate transformation rule
H1 l(2, (double*)0, qp),
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 3, 2, qp), shape functions N
l0 = l[0], l1 = l[1], l2 = 1.0 - l0 - l1; n[0] = l0; n[1] = l1; n[2] = l2; natural coordinate L0, L1, L2
C0 x = MATRIX("int, int, C0&, int, int", 3, 2, xl, 0, 0);
H1 X = n*x; H0 dx_inv = d(X).inverse(); J dv(d(X).det()/2.0);
{
H2 L(2, (double*)0, qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 6, 2, qp),
L0 = L[0], L1 = L[1], L2 = 1.0 - L0 - L1;
H0 unit(qp); unit = 1.0; double area = (double)(unit | dv);
double l_0 = (double)norm(xl[1]-xl[2]), l_1 = (double)norm(xl[2]-xl[0]),
l_2 = (double)norm(xl[0]-xl[1]), mu_0 = (pow(l_2,2)-pow(l_1,2))/pow(l_0,2),
mu_1 = (pow(l_0,2)-pow(l_2,2))/pow(l_1,2), mu_2 = (pow(l_1,2)-pow(l_0,2))/pow(l_2,2),
d3 = l_0/(4.0*area), d4 = l_1/(4.0*area), d5 = l_2/(4.0*area);
C0 C = ( C0(1.0) | C0(0.0) | C0(0.0) | C0(0.0) | C0(0.0) | C0(0.0) ) & C
( C0(0.0) | C0(1.0) | C0(0.0) | C0(0.0) | C0(0.0) | C0(0.0) ) &
( C0(0.0) | C0(0.0) | C0(1.0) | C0(0.0) | C0(0.0) | C0(0.0) ) &
(d3*( C0(-2.0) | C0(1.0-mu_0) | C0(1.0+mu_0) | C0(-1.0) | C0(1.0) | C0(-1.0) )) &
(d4*( C0(1.0+mu_1) | C0(-2.0) | C0(1.0-mu_1) | C0(-1.0) | C0(-1.0) | C0( 1.0) )) &
(d5*( C0(1.0-mu_2) | C0(1.0+mu_2) | C0(-2.0) | C0( 1.0) | C0(-1.0) | C0(-1.0) ));
C0 C_inv = C.inverse(); C-1
H2 P = INTEGRABLE_VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 6/*nenxndf*/, 2/*nsd*/, qp); ∂2
--------
P[0] = L0; P[1] = L1; P[2] = L2; P[3] = L0*L1; P[4] = L1*L2; P[5] = L2*L0; ∂x 2
for(int i = 0; i < 6; i++) N[i] = P * C_inv(i); N = PC-1
H0 Nxx = INTEGRABLE_MATRIX("int, int, Quadrature", 2*nen*ndf, 2, qp); B a = ( L∇ )N a , and L∇ = ∂2
--------
H0 w_xx = INTEGRABLE_SUBMATRIX("int, int, H0&", 2, 2, Nxx); ∂y 2
for(int i = 0; i < nen*ndf; i++) w_xx(i) = (~dx_inv) * dd(N)(i) * dx_inv;
∂2
H0 B = (~w_xx[0][0]) & (~w_xx[1][1]) & (2.0*(~w_xx[0][1])); 2 -------------
for(int i = 0; i < 3; i++) { ∂x∂y
int next = ((i == 2)? 0 : i+1);
C0 t = xl[next]-xl[i]; t = t/norm(t);
C0 nx = t[1], ny = -t[0];
B(i+3) = B(i+3)*(nx-ny); θ n = ( – n y )θ̂ x + n x θ̂ y
} B (nx-ny) for θn , to be compatible with
stiff &= ((~B) * (D * B)) | dv;
double f_0 = 1.0; stiffness matrix that is defined for
force &= (((H0)N)*f_0) | dv;
∂w ∂w
} θ̂ x = – -------, and θ̂ y = -------
} ∂y ∂x
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance; k eiajb = e iT ∫ B aT D B b dΩe j
static PlateMorley6 plate_m6_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()));
gd.u_h() = u; gd.u_h() = gd.gh_on_gamma_h();
for(int i = 0; i < row_node_no; i++)
for(int j = 0; j < row_node_no; j++)
cout << "#" << (i*row_node_no+j) << ": " << gd.u_h()[i*row_node_no+j] << endl;
return 0; }

Listing 4•23 Morley’s 6-dof triangular plate bending(project workspace file “fe.dsw”, project
“morley_plate_bending”).

450 Workbook of Applications in VectorSpace C++ Library


CHAPTER

Five Advanced Finite Element Methods

5.1 Mixed and Hybrid Finite Element Methods


C1-continuity requires complicated shape function as for the plate bending problem in the end of the last
chapter. Mixed formulation reduced the order of differential equation and therefore its continuity requirement.
The mixed formulation makes the shape function much easier to be constructed. Mixed method is also important
in theoretical consideration that sometimes highly efficient but simple tricks in finite elements, for example, the
selective reduced integration method, can be better understood in their equivalent mixed methods.

5.1.1 Heat Conduction

Mixed Formulation
Eq. 4•96 to Eq. 4•98 from Chapter 4 is re-written for convenience as

∇•q = f Eq. 5•1

q = – κ ∇T Eq. 5•2

T = g on Γ g , and q • n = h on Γ h , Eq. 5•3

Eq. 5•1 states that the divergence of the heat flux, “q”, is equal to the internal heat source, f. Eq. 5•2 is the Fourier
law of heat conduction which assumed that the heat flux is linearly related to the negative gradient of the temper-

Workbook of Applications in VectorSpace C++ Library 451


Chapter 5 Advanced Finite Element Methods
ature, “T”. The κ (= κ δij for isotropy) is the thermal diffusivity matrix. These two equations are subjected to the
essential and natural boundary conditions T = g on Γ g , and –q • n = h on Γ h , respectively (in Eq. 5•3). n is
the outward unit surface normal at Γh. The inner product of the heat flux, “q”, with the surface normal, “n”, pro-
duces the projected component of heat flux in the direction of the outward unit surface normal, “n”. The
weighted residual statement for Eq. 5•1 with the natural boundary conditions is

∫ wT ( ∇•q – f ) dΩ – ∫ wT ( q • n – h ) dΓ = 0 Eq. 5•4


Ω Γh

Integrating by parts and applying divergence theorem on the first term yields

∫ ∇wT q dΩ + ∫ f dΩ – ∫ wΓ h dΓ = 0 Eq. 5•5


Ω Ω Γh

The weighted residual statement for Eq. 5•2 is

∫ wq ( κ –1 q + ∇T ) dΩ = 0 Eq. 5•6

Finite element approximation of (1) temperature field, “T”, and (2) heat flux, “q”, are defined as

T ≅ T h ≡ φ a Tˆ a and q ≅ q h ≡ φ qa q̂ a Eq. 5•7


T

a
where Tˆ and q̂ a are nodal variables (bases), and “a” is nodal index for temperature and heat flux nodes. φTa
and φ qa are two shape functions which can be different from each other. Substituting Eq. 5•7 into Eq. 5•5 and Eq.
5•6 we have the mixed finite element formulation for the heat conduction problem as1

T f
AC q̂
= 1 Eq. 5•8
C 0 Tˆ f2

where

A = ∫ φq ⊗ ( κ–1 φq ) dΩ Eq. 5•9


Ωe

C = ∫ ∇φT ⊗ φq dΩ Eq. 5•10


Ωe

T
f 1 = – Aĥ Γ eh
– C ĝ Γ eg
Eq. 5•11

1. p.319-324 in Zienkiewicz, O.C. and R.L. Taylor, 1991, “The finite element method”, 4th ed., vol. 1. McGraw-Hill, Inc.,
UK.

452 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

f2 = – ∫ φT f dΩ + ∫ φT h dΓ – Cĥ e
Γh
Eq. 5•12
Ωe e
Γh

where h and g with “over-bar” are fixed nodal flux boundary conditions and fixed nodal temperature boundary
conditions, respectively, while “h” in Eq. 5•12 can be specified as a function on an element boundary. The fixed
nodal boundary conditions, h and g , in Eq. 5•11 and Eq. 5•12 are encountered frequently in finite element
method and are taken care of by “fe.lib” as default behaviors behind the scene. This is consistent with the treat-
ment of the result of the displacement boundary conditions as reaction, and substrates out of the nodal force term
as “ f -= K u ”.
By inspecting on Eq. 5•9 and Eq. 5•10, the derivatives of temperature field exist. C0-continuity of “T” on the
element interior and boundaries is required. This is needed to guarantees that Eq. 5•10 over the entire problem
domain is integrable. Otherwise, the integration goes to infinity at the discontinuities. Two triangular elements
are considered in the following computations. Figure 5•1a&b shows that two triangular elements with linear tem-
perature field with three corner nodes and either a constant heat flux with one node at the center of the element or
linear heat flux with three nodes at the Gaussian integration points. We notice that the temperature nodes on the
three corners is necessary to ensure the C0-continuity on the element boundaries, while the heat flux, with no
derivatives of heat flux in Eq. 5•9 and Eq. 5•10, can be discontinuous at the element boundaries. Therefore, the
constant heat flux element has one node at the center of the element (Figure 5•1a), and the linear heat flux ele-
ment has three nodes at the Gaussian integration points (Figure 5•1b). Actually, requiring heat flux to be C0-con-
tinuity on the element boundaries, had we use three corner nodes for the heat flux, may cause physically
incorrect conditions.1
Recall in Section 4.2.5, the mixed formulation for one dimensional beam bending problem, we have extended
“fe.lib” with the object-oriented modeling for developing matrix substructuring to solve system of equations in
submatrices similar to Eq. 5•8.

T = 30oC

q=0 q=0

(a) Constant (b) Linear (c)


heat flux heat flux
q-nodes T-nodes T = 0oC
Figure 5•1 Triangluar elements with linear temperature with three corner nodes, and either
constant heat flux with one node at the center or linear heat flux with three nodes at three Gauss
integration points.

1. e.g. as discussed in p.327 in Zienkiewicz, O.C. and R.L. Taylor, 1991, “The finite element method”, 4th ed., vol. 1.
McGraw-Hill, Inc., UK.

Workbook of Applications in VectorSpace C++ Library 453


Chapter 5 Advanced Finite Element Methods
The Program Listing 5•1 implements the constant heat flux triangular element for the mixed formulation. For
global discretization {Ωh, qh}, with its element discretization as Ωqe, the heat flux is constant over entire domain
of Ωqe. Only one node at the center of the element is needed. However we still need to specify the three corner
nodes in order to (1) compute the coordinates of the center node, (2) define coordinate transformation rule, and
(3) perform integration. Therefore, the three additional corner nodes of Ωqe are defined as geometrical nodes.
The geometrical nodes has no variable associated with them. A simple trick using “fe.lib” is to disable all geo-
metrical nodes by specifying all these nodes to have Dirichlet type boundary condition, so they will be left out of
the global stiffness matrix. The A-submatrix are defined by

1 double k_x = 1.0, k_y = 1.0, –1


// κ = κ δ ij = κ 0
–1 –1
2 k_inv[2][2] = { {1.0/k_x, 0.0 }, , for isotropy
–1
3 {0.0, 1.0/k_y }}; 0 κ
4 C0 K_inv = MATRIX("int, int, const double*", 2, 2, k_inv[0]);
5 Heat_Mixed_Formulation::Heat_Mixed_Formulation(int en, Global_Discretization& gd) :
6 Element_Formulation_Couple(en, gd) {
7 Quadrature qp(2, 4);
8 H1 L(2, (double*)0, qp),
9 n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 3, 2, qp),
10 L0 = L[0], L1 = L[1], L2 = 1.0 - L0 - L1; // “area coordinates” for a triangle
11 n[0] = L0; n[1] = L1; n[2] = L2;
12 C0 x = MATRIX("int, int, C0&, int, int", 3, 2, xl, 0, 0);
13 H1 X = n*x;
14 J dv(d(X).det()/2.0);
15 H0 N = INTEGRABLE_VECTOR("int, Quadrature", 4, qp);
16 N[0] = N[1] = N[2] = 0.0; N[3] = 1.0;
17 H0 N_q = ((~N) || C0(0.0)) &
18 (C0(0.0) || (~N) ); // A = ∫ φq ⊗ ( κ – 1 φ q ) dΩ
19 stiff &= ((~N_q) * (K_inv * N_q)) | dv; Ωe
20 }

The area coordinates for triangle, L0, L1, and L2, are used for coordinate transformation and integration. The first
three nodes are geometrical nodes at three corners and the fourth-node is the q-node at the center. A reference
matrix “x” (line 12) is constructed to refer to the first three coordinates of “xl”, which leaves out the q-node. To
compute the Jacobian we notice that there is a factor of 1/2 for a triangular element comparing to the a quadrilat-
eral element where the factor = 1 (note that a factor of 1/6 is to be used for a 3-D tetrahedra element). The q-
shape function, “N” (line 16), is defined that the first three shape functions corresponding to three geometrical
nodes are zero; i.e., “N[0] = N[1] = N[2] = 0”. And the fourth shape function is one; i.e., “N[3] = 1”. The ele-
ment stiffness matrix so constructed will have the size of 8 × 8, with only the 2 × 2 submatrix at the lower-right
corner, corresponding to the center q-node, contains no zero components. All the geometrical nodes are to be
specified with Dirichlet type boundary condition. Dummy variables corresponding to these geometrical nodes
will all be eliminated from the global matrix and the global vector. Therefore, the no trivial 2 × 2 element subma-
trix will enter the global stiffness matrix.
The C-submatrix can be defined as

454 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#include "include\fe.h"
#include "include\omega_h_n.h"
Matrix_Representation_Couple::assembly_switch
Matrix_Representation_Couple::Assembly_Switch = Matrix_Representation_Couple::ALL;
static const int row_t_node_no = 4; static const double h_e = 1.0;
Omega_h_i::Omega_h_i(int i) : Omega_h(0) {
if(i == 0) {
double xl[3][2], v[2]; Node *node;
Ωq
for(int j = 0; j < row_t_node_no-1; j++) make center q-nodes
for(int k = 0; k < row_t_node_no-1; k++) {
int node_no = (j*(row_t_node_no-1) + k) *2;
xl[0][0] = (double)k; xl[0][1] = (double)j;
corner coordinates
xl[1][0] = (double)(k+1); xl[1][1] = (double)j;
xl[2][0] = (double)(k+1); xl[2][1] = (double)(j+1); geometrical center coordinates
for(int l = 0; l < 2; l++) v[l] = (xl[0][l] + xl[1][l] + xl[2][l])/3.0;
node = new Node(node_no, 2, v); node_array().add(node);
xl[0][0] = (double)k; xl[0][1] = (double)j;
xl[1][0] = (double)(k+1); xl[1][1] = (double)(j+1);
xl[2][0] = (double)k; xl[2][1] = (double)(j+1);
or(int l = 0; l < 2; l++) v[l] = (xl[0][l] + xl[1][l] + xl[2][l])/3.0;
node = new Node(node_no+1, 2, v); node_array().add(node);
}
for(int j = 0; j < row_t_node_no; j++)
for(int k = 0; k < row_t_node_no; k++) {
corner geometrical nodes
int nn = j*row_t_node_no+k+(row_t_node_no-1)*2*(row_t_node_no-1);
v[0] = ((double)k)*h_e; v[1] = ((double)j)*h_e;
node = new Node(nn, 2, v); node_array().add(node);
}
for(int j = 0; j < row_t_node_no-1; j++) make heat flux elements, Ωqe
for(int k = 0; k < row_t_node_no-1; k++) {
int element_no = ((row_t_node_no-1)*j + k)*2,
center_node_no = element_no,
first_corner_node_no = j*row_t_node_no+k +(row_t_node_no-1)*2*(row_t_node_no-1);
int ena[4];
ena[0] = first_corner_node_no+1+row_t_node_no; ena[1] = first_corner_node_no;
ena[2] = first_corner_node_no+1; ena[3] = center_node_no;
Omega_eh* elem = new Omega_eh(element_no, 0, 0, 4, ena);
omega_eh_array().add(elem);
ena[0] = first_corner_node_no; ena[1] =
first_corner_node_no+1+row_t_node_no;
ena[2] = first_corner_node_no+row_t_node_no; ena[3] = center_node_no+1;
elem = new Omega_eh(element_no+1, 0, 0, 4, ena); omega_eh_array().add(elem); ΩΤ
}
} else if(i == 1) {
double v[2]; make T-corner nodes
for(int j = 0; j < row_t_node_no; j++)
for(int k = 0; k < row_t_node_no; k++) {
int nn = j*row_t_node_no+k; v[0] = ((double)k)*h_e; v[1] = ((double)j)*h_e;
Node* node = new Node(nn, 2, v); node_array().add(node);
} make temperature elements, ΩTe
for(int j = 0; j < row_t_node_no-1; j++)
for(int k = 0; k < row_t_node_no-1; k++) {
int element_no = ((row_t_node_no-1)*j + k)*2,
first_corner_node_no = j*row_t_node_no+k, ena[3];
ena[0] = first_corner_node_no+1+row_t_node_no;
ena[1] = first_corner_node_no; ena[2] = first_corner_node_no+1;
Omega_eh* elem = new Omega_eh(element_no, 0, 0, 3, ena);
omega_eh_array().add(elem);

Workbook of Applications in VectorSpace C++ Library 455


Chapter 5 Advanced Finite Element Methods
ena[0] = first_corner_node_no; ena[1] = first_corner_node_no+1+row_t_node_no;
ena[2] = first_corner_node_no+row_t_node_no;
elem = new Omega_eh(element_no+1, 0, 0, 3, ena); 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() {
gh_on_Gamma_h::__initialization(df, omega_h);
if(i == 0) { Γh
for(int j = (row_t_node_no-1)*6*(row_t_node_no-1);
j < (row_t_node_no*row_t_node_no+(row_t_node_no-1)*6*(row_t_node_no-1)); j++)
disable all geometrical nodes on Ωq
for(int k = 0; k < 2; k++) {
the_gh_array[node_order(j)](k) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(j)][k] = 0.0;
}
} else if(i == 1) { Γg
for(int j = 0; j < row_t_node_no; j++) { top boundary: T = 30o
the_gh_array[node_order(row_t_node_no*(row_t_node_no-1)+j)](0) =
gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(row_t_node_no*(row_t_node_no-1)+j)][0] =
((double)(row_t_node_no-1))*10.0; bottom boundary: T = 0o
the_gh_array[node_order(j)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(j)][0] = 0.0;
}
}
}
static const int q_ndf = 2; static Omega_h_i oh_q(0);
static gh_on_Gamma_h_i q_gh(0, q_ndf, oh_q); static U_h q_h(q_ndf, oh_q);
static Global_Discretization q_gd(oh_q, q_gh, q_h);
static const int T_ndf = 1; static Omega_h_i oh_T(1);
static gh_on_Gamma_h_i T_gh(1, T_ndf, oh_T); static U_h T_h(T_ndf, oh_T);
static Global_Discretization T_gd(oh_T, T_gh, T_h);
static Global_Discretization_Couple gdc(T_gd, q_gd);

class Heat_Mixed_Formulation : public Element_Formulation_Couple {


public:
Heat_Mixed_Formulation(Element_Type_Register a) : Element_Formulation_Couple(a) {} for diagonal submatrix A
Element_Formulation *make(int, Global_Discretization&);
Heat_Mixed_Formulation(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&); for off-diagonal submatrix C
Heat_Mixed_Formulation(int, Global_Discretization_Couple&);
};
Element_Formulation* Heat_Mixed_Formulation::make(int en, Global_Discretization& gd) {
A-submatrix element formulation
return new Heat_Mixed_Formulation(en,gd);
}
Heat_Mixed_Formulation::Heat_Mixed_Formulation(int en, Global_Discretization& gd) :
Element_Formulation_Couple(en, gd) {
Quadrature qp(2, 4);
H1 L(2, (double*)0, qp), area coordinates for triangle
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 3, 2, qp),
L0 = L[0], L1 = L[1], L2 = 1.0 - L0 - L1;
linear triangular shape function for
n[0] = L0; n[1] = L1; n[2] = L2; coordinate transformation
C0 x = MATRIX("int, int, C0&, int, int", 3, 2, xl, 0, 0);
H1 X = n*x;
N ≡ φ q= 1.0 (constant over element)
J dv(d(X).det()/2.0);
H0 N = INTEGRABLE_VECTOR("int, Quadrature", 4, qp); N[0], N[1], N[2] are geometrical nodes
N[0] = N[1] = N[2] = 0.0; N[3] is constant over element domain
N[3] = 1.0;

456 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
double k_x = 1.0, k_y = 1.0, k_inv[2][2] = { {1.0/k_x, 0.0}, { 0.0, 1.0/k_y}};
C0 K_inv = MATRIX("int, int, const double*", 2, 2, k_inv[0]);

∫ φ q ⊗ ( κ – 1 φ q ) dΩ
H0 N_q = ((~N) || C0(0.0)) &
(C0(0.0)|| (~N)); A =
stiff &= ((~N_q) * (K_inv * N_q)) | dv; Ωe
}
Element_Formulation_Couple* Heat_Mixed_Formulation::make(
int en, Global_Discretization_Couple& gdc) { C-submatrix element formulation
return new Heat_Mixed_Formulation(en,gdc);
}
Heat_Mixed_Formulation::Heat_Mixed_Formulation(int en, Global_Discretization_Couple& gdc)
: Element_Formulation_Couple(en, gdc) {
Quadrature qp(2, 4);
H1 L(2, (double*)0, qp),
n ≡ φ T linear triangular shape function
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 3/*nen*/, 2/*nsd*/, qp), for both coordinate transformation and
L0 = L[0], L1 = L[1], L2 = 1.0 - L0 - L1; n[0] = L0; n[1] = L1; n[2] = L2; temperature field shape function
H1 X = n*xl;
H0 nx = d(n) * d(X).inverse();
J dv(d(X).det()/2.0); N ≡ φ q= 1.0 (constant over element)
H0 N = INTEGRABLE_VECTOR("int, Quadrature", 4/*nen*/, qp); for heat flux field shape function
N[0] = N[1] = N[2] = 0.0; N[3] = 1.0;
H0 N_q = ((~N) || C0(0.0)) &
(C0(0.0)|| (~N)); C = ∫ ∇φT ⊗ φq dΩ
stiff &= (nx * N_q) | dv; Ωe
}
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static Heat_Mixed_Formulation
heat_mixed_formulation_instance(element_type_register_instance);

static Matrix_Representation mr(q_gd);


static Matrix_Representation_Couple mrc(gdc, 0, 0, &(mr.rhs()), &mr);
int main() {
mrc.assembly();
mr.assembly();
C0 A = ((C0)(mr.lhs())),
f_1 = ((C0)(mr.rhs())),
C = ((C0)(mrc.lhs())),
f_2 = ((C0)(mrc.rhs()));
Cholesky dA(A); range space method for quadratic
C0 Ainv = dA.inverse(),
CAinvCt = C*Ainv*(~C);
programming problem
Cholesky dCAinvCt(CAinvCt);
C0 T = dCAinvCt*((C*Ainv*f_1)-f_2), Tˆ = (CA-1CT)-1(CA-1f1-f2)
q = dA*(f_1-(~C)*T);
q̂ = A-1 (f1- CT Tˆ )
q_h = q;
q_h = q_gd.gh_on_gamma_h();
cout << "heat flow:" << endl;
for(int i = 0; i < q_h.total_node_no(); i++)
cout << q_h[i] << endl;
T_h = T;
T_h = T_gd.gh_on_gamma_h();
cout << "temperature:" << endl << T_h;
return 0;
}

Listing 5•1 Substructure method for the mixed formulation of the heat conduction problem with constant
heat flux (project: “mixed_heat_conduction” in project workspace file “fe.dsw”).

Workbook of Applications in VectorSpace C++ Library 457


Chapter 5 Advanced Finite Element Methods
1 Heat_Mixed_Formulation::Heat_Mixed_Formulation(int en, Global_Discretization_Couple& gdc)
2 : Element_Formulation_Couple(en, gdc) {
3 Quadrature qp(2, 4);
4 H1 L(2, (double*)0, qp),
5 n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
6 "int, int, Quadrature", 3/*nen*/, 2/*nsd*/, qp),
7 L0 = L[0], L1 = L[1], L2 = 1.0 - L0 - L1;
8 n[0] = L0; n[1] = L1; n[2] = L2;
9 H1 X = n*xl;
10 H0 nx = d(n) * d(X).inverse();
11 J dv(d(X).det()/2.0);
12 H0 N = INTEGRABLE_VECTOR("int, Quadrature", 4/*nen*/, qp);
13 N[0] = N[1] = N[2] = 0.0; N[3] = 1.0;
14 H0 N_q = ( (~N) || C0(0.0)) &
15 (C0(0.0) || (~N)); // C = ∫ ∇φ T ⊗ φ q dΩ
16 stiff &= (nx * N_q) | dv; Ωe
17 }

The element stiffness matrix so generated has the size of 3 × 8. The three rows (number of equations) corre-
sponding to three temperature nodes at the corner. After the element to global mapping, the first 6 columns (cor-
responding to the number of dummy variables) for the geometrical nodes will not enter the global stiffness
matrix. Only the last 2 columns (corresponding to the number of variables) for the center q-node will survive.
After the submatrices have been formed, the solution of system of equation in Eq. 5•8 is the subject of con-
straint optimization problem (see Section 2.3.3 in Chapter 2). In the context of finite element problem, the objec-
tive functional for optimization is quadratic. The problem is further restricted to a quadratic programming
problem, in which only one step along the search path is needed to reach the exact solution (see introduction and
its example in page 145). We discuss the range space method and null space method (see page 149) to solve Eq.
5•8 in the followings.
From first equation of Eq. 5•8 we have

A q̂ + CT Tˆ = f1 Eq. 5•13

Considering A is symmetrical positive definitive, therefore it can be inverted, we can solve for q by

q̂ = A-1 f1 - A-1 CT Tˆ Eq. 5•14

Substituting Eq. 5•14 into second equation of Eq. 5•8, C q̂ = f2, we have

C A-1 f1 - C A-1 CT Tˆ = f2 Eq. 5•15

Therefore, “ Tˆ ” can also be solved considering that the similarity transformation of A-1 as “C A-1 CT” preserves
the symmetrical positive definitiveness of A-1. An alternative view is that “C A-1 CT” is the projection of inverse

458 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
of the Hessian, A-1, into the range of the constraint space, C. Providing that “C A-1 CT” is non-singular we may
invert it to solve for “T” as

Tˆ = (C A-1 CT)-1 (C A-1 f1 - f2) Eq. 5•16

After we obtain the nodal temperature solution “ Tˆ ”, the nodal heat flux solution “ q̂ ” can be solved from Eq.
5•13 as

q̂ = A-1 ( f1 - CT Tˆ ) Eq. 5•17

Eq. 5•16 and Eq. 5•17 is the formula for the range space method. We notice that the size of A-square submatrix
is the number of q̂ -free-variables, denotes as “n q”. The row-size of the C-submatrix is number of Tˆ -free-vari-
ables, denotes as “nT” (C-submatrix has size of nT × nq). Assuming full-row-rank condition for both A and C sub-
matrices. The rank of A-1 is “nq” and the column rank of CT is “nT”. In order to have “C A-1 CT” non-singular for
the inversion of “C A-1 CT” to be possible, we must have

nq ≥ nT Eq. 5•18

Based on the above simplistic linear algebraic discussion, Eq. 5•18 can be used to conceptually performing
“patch test” in the design of “q-T” two-field mixed finite element.1 However, a mathematically more rigorous
theorem on the existence and uniqueness of the constraint optimization problem (in the abstract form of the sad-
dle-point problem) is known as LBB-condition (coined by Ladyzhenskaya-Babuska-Brezzi and is also known as
inf-sup condition).2 The implementation of the range space method is

1 int main() {
2 Matrix_Representation mr(q_gd);
3 Matrix_Representation_Couple mrc(gdc, 0, 0, &(mr.rhs()), &mr);
4 mrc.assembly();
5 mr.assembly();
6 C0 A = ((C0)(mr.lhs())), f_1 = ((C0)(mr.rhs())),
7 C = ((C0)(mrc.lhs())), f_2 = ((C0)(mrc.rhs()));
8 Cholesky dA(A); // Cholesky decomposition on Hessian A
9 C0 Ainv = dA.inverse(),
10 CAinvCt = C*Ainv*(~C);
11 Cholesky dCAinvCt(CAinvCt); // Cholesky decomposition on C A-1 CT
12 C0 T = dCAinvCt*((C*Ainv*f_1)-f_2),
13 q = dA*(f_1-(~C)*T);
14 q_h = q; // update free degree of freedom
15 q_h = q_gd.gh_on_gamma_h(); // update fixed degree of freedom
16 cout << "heat flow:" << endl;

1. p.324-327 in Zienkiewicz, O.C. and R.L. Taylor, 1989, “The finite element method”, 4th ed., vol. 1. McGraw-Hill, Inc.,
UK.
2. see Brezzi, F, and M. Fortin, 1991, “Mixed and hybrid finite element method”, Spring-Berlag New York, Inc.

Workbook of Applications in VectorSpace C++ Library 459


Chapter 5 Advanced Finite Element Methods
17 for(int i = 0; i < q_h.total_node_no(); i++)
18 cout << q_h[i] << endl;
19 T_h = T; // update free degree of freedom
20 T_h = T_gd.gh_on_gamma_h(); // update fixed degree of freedom
21 cout << "temperature:" << endl << T_h;
22 return 0;
23 }
We note by passing that in range space method the multiplication of matrices such as “C A-1 CT” may
severely increase the condition number of the resultant matrix comparing to that of A-1. We would like to have
the condition number kept to be the same as that of the A-1. This can be done by performing QR-decomposition
on C. The collection of orthogonal column vectors, say Y, from the range space of Q decomposed from C, can
be used in place of C. That is to replace every instance of C in Eq. 5•16 and Eq. 5•17 with Y. Such that the con-
dition number of “Y A-1 YT” is no worse than A-1, since column vectors in Y are orthonormal. This treatment is
important when the problem size increases.
For readers who are familiar with the null space method discussed in Chapter 2, the solution to Eq. 5•8 can
be implemented simply as

1 static Matrix_Representation mr(q_gd);


2 static Matrix_Representation_Couple mrc(gdc, 0, 0, &(mr.rhs()), &mr);
3 int main() {
4 mrc.assembly();
5 mr.assembly();
6 C0 A = ((C0)(mr.lhs())), f_1 = ((C0)(mr.rhs())),
7 C = ((C0)(mrc.lhs())), f_2 = ((C0)(mrc.rhs()));
8 QR ct(~C); // QR decomposition on constraint space of CT
9 C0 Q = ct.Q(); // CT = Q R
10 C0 Z(A.row_length(), (A.row_length()-C.row_length()), (double*)0); // null space of C
11 for(int i = 0; i < Z.col_length(); i++) Z(i) = Q(C.row_length()+i);
12 C0 A0 = (~Z)*A*Z; // project A on to the null space of the constraint space
13 Cholesky dA0(A0); // cholesky decomposition on A0
14 C0 A0_inv = dA0.inverse();
15 C0 q = Z*A0_inv*(~Z)*f_1;
16 q_h = q; // update free degree of freedom
17 q_h = q_gd.gh_on_gamma_h(); // update fixed degree of freedom
18 cout << "heat flow:" << endl;
19 for(int i = 0; i < q_h.total_node_no(); i++)
20 cout << q_h[i] << endl;
21 C0 T = (C*(~C)).inverse()*C*(f_1-A*q);
22 T_h = T; // update free degree of freedom
23 T_h = T_gd.gh_on_gamma_h(); // update fixed degree of freedom
24 cout << "temperature:" << endl << T_h;
25 return 0;
26 }

460 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
Both the range space and null space method reproduce the exact solution up to round-off error for the current
problem. The nodal temperature solutions at the second row is Tˆ = 10 oC, and the third row is Tˆ = 20 oC. All
heat flux center nodes have the values of q̂ = [0, -10]T per unit length. The default method in Program Listing
5•1 is the range space method. The null space method can be activated by setting macro definition
“__NULL_SPACE_METHOD” at compile time.
The linear heat flux with three nodes at the Gaussian integration points (see Figure 5•1b) can be activated by
setting macro definition “__TEST_THREE_NODES_DISCONTINUOUS_HEAT_FLUX” at compile time. The
triangular element in this implementation used shape functions degenerated from bilinear 4-node element. The
results of this linear heat flux element is identical to that of the constant heat flux element. Considering that T-
field only for a 3-node triangular element vary linearly. The heat flux, computed from the Fouries law, is the
derivative of the linear T-field, which is constant. Therefore, the linear heat flux element does not give any
improvement in the accuracy of the solution comparing to the constant heat flux element. Actually, the linear
heat flux element produces the same result as the constant heat flux element. This is known as the limitation prin-
ciple by Fraeijs de Veubeke.
With the assistance of object-oriented modeling provided in “fe.lib” for handling the matrix substructuring, it
may seems to be a piece of cake to implement the multiple-field mixed formulation. It is not so for most existing
finite element programs. It has been remarked that the full-scale multiple-field formulations are rarely imple-
mented for practical computation.1
The discontinuous heat flux field, with nodes reside only inside an element, not only avoid physically incor-
rect conditions as mentioned earlier, but it also has the advantage that the heat flux field can be eliminated at the
element level. Every heat flux node belongs only to its containing element; i.e., there are no shared heat flux
nodes with the neighboring elements. Therefore, from Eq. 5•16 that

Tˆ = (C A-1 CT)-1 (C A-1 f1 - f2)

We can redefine element stiffness matrix and element force vector as

ke = Ce Ae-1 CeT, and fe= Ce Ae-1 f1e - f2e Eq. 5•19

respectively, with

f 2e = – ∫ φ T f dΩ Eq. 5•20
Ωe

and,

∫ φT h dΓ – C e ĥ
Γ he
= 0 Eq. 5•21
Γ he

1. see p.206 in Hughes, T. J.R., “The finite element method: linear static and dynamic finite element analysis”, Prentice-Hall,
Inc., Englewood Cliffs, New Jersey.

Workbook of Applications in VectorSpace C++ Library 461


Chapter 5 Advanced Finite Element Methods
Since in this discontinuous heat flux element no heat flux boundary condition can be specified, the temperature
boundary conditions in Eq. 5•8 are simply

T
f1 = - C ĝ Γg
Eq. 5•22

Substituting the element level vector of Eq. 5•22 into the second equation in Eq. 5•19, we have

T T
fe = - Ce Ae-1 C e ĝ e Γ ge
- f2e= - Ce Ae-1 C e ĝ e Γ ge
- f2e

= -ke ĝ e Γ ge
- f2e Eq. 5•23

The first term in the right-hand-side is consistent with standard implementation of finite element on the essential
boundary conditions. The mixed form of Eq. 5•19 can be easily implemented which does not require the mecha-
nism provided in the “fe.lib” to express matrix substructuring. Program Listing 5•2 implements Eq. 5•19 (project
“mixed_T_heat_conduction” in project workspace file “fe.dsw”). The temperature solutions of the computation
is certainly identical to the full-fledged mixed formulation. The program is significant simplified comparing to
the full-scale mixed formulation.

462 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#include "include\fe.h"
static row_node_no = 4;
EP::element_pattern EP::ep = EP::SLASH_TRIANGLES;
Omega_h::Omega_h() {
double coord[4][2] = {{0.0, 0.0}, {3.0, 0.0}, {3.0, 3.0}, {0.0, 3.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 j = 0; j < row_node_no; j++) {
the_gh_array[node_order(row_node_no*(row_node_no-1)+j)](0) =
the_gh_array[node_order(j)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(row_node_no*(row_node_no-1)+j)][0] =
((double)(row_node_no-1))*10.0;
the_gh_array[node_order(j)][0] = 0.0;
}
}
class HeatMixedT3 : public Element_Formulation {
public:
HeatMixedT3(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
HeatMixedT3(int, Global_Discretization&);
};
Element_Formulation* HeatMixedT3::make(int en, Global_Discretization& gd) {
return new HeatMixedT3(en,gd); }
double k_x = 1.0, k_y = 1.0, k_inv[2][2] = { {1.0/k_x, 0.0}, { 0.0, 1.0/k_y}};
C0 K_inv = MATRIX("int, int, const double*", 2, 2, k_inv[0]);
HeatMixedT3::HeatMixedT3(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
Quadrature qp(2, 4);
H1 L(2, (double*)0, qp),
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;
H1 X = n*xl; H0 nx = d(n) * d(X).inverse(); J dv(d(X).det()/2.0);
H0 N = INTEGRABLE_SCALAR("Quadrature", qp); N = 1.0;
C = ∫ ∇φT ⊗ φq dΩ
H0 N_q = ((~N) || C0(0.0)) & Ωe
(C0(0.0)|| (~N));
C0 C = (nx * N_q) | dv,
A = ((~N_q) * (K_inv * N_q)) | dv,
A = ∫ φq ⊗ ( κ –1 φq ) dΩ
Ωe
A_inv = A.inverse();
stiff &= C*A_inv*(~C); k = (CA-1CT)
}
Element_Formulation* Element_Formulation::type_list = 0;
Element_Type_Register element_type_register_instance;
static HeatMixedT3 heatmixedt3_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()));
uh = u; uh = gh;
cout << uh << endl;
return 0;
}

Listing 5•2 Mixed formulation with discontinuous temperature field (C0 continuity dropped) reduces to
temperature field only formulation (project: “mixed_t_field_heat_conduction”).

Workbook of Applications in VectorSpace C++ Library 463


Chapter 5 Advanced Finite Element Methods
Hourglass Element
For the irreducible formulation in Chapter 4, 2 × 2 Gaussian integration points are used to compute element
stiffness matrix for bilinear 4-node element.

ke = ∫ ( ( ∇N )T κ ∇N )dΩ = ∫ ( BT κ B )dΩ Eq. 5•24


Ωe Ωe

where

∂N a
----------
b xa ∂x
Ba ≡ ≡ Eq. 5•25
b ya ∂N a
----------
∂y

and a = 0, 1, 2, 3. A reduced integration (1 Gauss point at the center of the element) will result in rank deficiency
of the element stiffness matrix. The rank of the element stiffness matrix is the number of integration points times
the number of independent relations. In the case of heat conduction, the number of independent relations is the
number of equations relating the heat flux [qx, qy] and the temperature gradients [∂T/∂x, ∂T/∂y] by Fourier law
of heat conduction in 2-D. Therefore, the rank of the stiffness matrix for 1-point Gauss integration rule is 2 (=
2 × 1). For the element stiffness matrix of size 4 × 4 in Eq. 5•24, the 1-point Gauss integration leads to rank defi-
ciency of 2 (= 4-2). We can consider that the 1-point integration stiffness matrix is span by bx = {bxa, bya} in Eq.
5•25, which are bases in 4. The two spurious zero energy modes in the null space are constant solution mode
with nodal solution of sa = [1, 1, 1, 1] and hourglass mode with nodal solution of ha = [-1, 1, -1, 1]. The hour-
glass mode is illustrated in Figure 5•2. The two zero energy modes are orthogonal to bx

b x s = 0 , and b x h = 0 Eq. 5•26


i i

where we also denote x0 = x, and x1 = y. We notice that for isoparametric coordinate transformation xi = Na xia ,
we have the relation

1
T
0.5
0
-0.5 η
-1
1

0.5
.5

-0.5

-1
ξ
-1
-0.5
0
0.5
1

Figure 5•2 The hourglass mode for heat conduction problem.

464 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
∂N
b x x j = ------- x = δ ij Eq. 5•27
i ∂x i j

The constant nodal solution sa is considered “proper”, since a constant temperature field produces no heat flux is
as expected, while the hourglass mode ha has gradient far from zero (see Figure 5•2) but no heat flux production,
which is considered “improper”. Therefore, we expect the 4-node element stiffness to produce a rank 3 matrix.
The strategy is to use a so-called trial hourglass mode, Ψa , to construct a correct-ranked stiffness ke in a way that
is very economical to compute as

ke = ke(1-point) + ke(hourglass) Eq. 5•28

The first term on the right hand side, ke(1-point) is standard stiffness matrix computed with only one Gaussian
integration point. The second term ke(hourglass) is to be constructed with the trial hourglass mode Ψa. The general
form of the trial hourglass mode, Ψa , in 4 is span by the four bases [bxa, bya, sa, ha] as

Ψa = a0 bxa + a1 bya + a2 sa+ ha Eq. 5•29

Ψa is required to be orthogonal with an arbitrary linear temperature field of Ta

Ta = c0 xa + c1ya + c2 sa Eq. 5•30

For Ψa to be always orthogonal with arbitrary coefficients, ci , in Eq. 5•30, we have

Ψa xa = 0 , Ψa ya = 0, and Ψa sa = 0 Eq. 5•31

From last part of Eq. 5•31, no component of Ψa is in sa, so in Eq. 5•29 a2 = 0, and we have this equation re-writ-
ten as

Ψa = a0 bxa + a1 bya + ha Eq. 5•32

Substituting Eq. 5•32 into Ψa xa = 0 in the first part of Eq. 5•31, and using bxa xa = 1, and bya xa = 0 as indicated
in Eq. 5•27, we have

a0 = - h a x a Eq. 5•33

Similarly, substituting Eq. 5•32 into Ψa ya = 0 in the second part of Eq. 5•31, and using bya ya = 1, and bxa ya = 0
as in Eq. 5•27, we have

a1 = - h a y a Eq. 5•34

Therefore, we show that

Ψa = ha - (ha xa) bxa - (ha ya) bya Eq. 5•35

The hourglass stiffness can be defined as 1

Workbook of Applications in VectorSpace C++ Library 465


Chapter 5 Advanced Finite Element Methods
kJ ( b • b )
k e ( hourglass ) ≡ ----------------------- ( Ψ ⊗ Ψ ) Eq. 5•36
12

where Ψa is Ψa normalized to ||Ψa|| = 2, b = [bx, by]T, and J ≡ det ( ∂x ⁄ ∂ ξ ) . Program Listing 5•3 implements the
hourglass element for heat conduction (project “hourglass_heat” in project ˜ workspace file”fe.dsw”).
We measure the time spent in the computation of stiffness matrix for the heat conduction element with irre-
ducible formulation in Chapter 4. It takes 3.5 seconds to assemble the stiffness on an obsolete 166 MHz PC. For
the mixed formulation in the last section, although we gain significant freedom in terms of formulation, the
assemble of diagonal and off-diagonal stiffness matrices (A and C) takes 6.2 seconds on the same computer. The
hourglass element takes only 0.5 second to assemble the stiffness.

1. see similar derivation for elasticity in p.251-254 in Hughes, T. J.R., “The finite element method: linear static and dynami c
finite element analysis”, Prentice-Hall, Inc., Englewood Cliffs, New Jersey, and references therein.

466 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#include "include\fe.h"
EP::element_pattern EP::ep = EP::QUADRILATERALS_4_NODES;
Omega_h::Omega_h() {
double coord[4][2] = {{0.0, 0.0}, {3.0, 0.0}, {3.0, 3.0}, {0.0, 3.0}};
int control_node_flag[4] = {1, 1, 1, 1};
block(this, 4, 4, 4, control_node_flag, coord[0]);
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h);
int row_node_no = 4;
for(int i = 0; i < row_node_no; i++) {
the_gh_array[node_order(i)](0) = gh_on_Gamma_h::Dirichlet; bottom B.C. = 0oC
the_gh_array[node_order(row_node_no*(row_node_no-1)+i)](0) = top B.C. = 30oC
gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(row_node_no*(row_node_no-1)+i)][0] = 30.0;
}
}
class HourGlassHeatQ4 : public Element_Formulation { public:
HourGlassHeatQ4(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
HourGlassHeatQ4(int, Global_Discretization&);
};
Element_Formulation* HourGlassHeatQ4::make(int en, Global_Discretization& gd) {
return new HourGlassHeatQ4(en,gd);
}
HourGlassHeatQ4::HourGlassHeatQ4(int en, Global_Discretization& gd) :
Element_Formulation(en, gd) {
Quadrature qp(2, 1); 1-point Gaussian integration for 2-D
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; H0 Nx = d(N) * d(X).inverse(); J dv(d(X).det());
double k_ = 1.0; C0 K_standard = (Nx * k_ * (~Nx)) | dv;
ke(1-point) from irreducible formulation
C0 h(4, (double*)0); h[3] = 1.0; Ψa = ha - (ha xa) bxa - (ha ya) bya
C0 phi = h - Nx.quadrature_point_value(0)*((~xl)*h); Ψa is Ψa normalized to ||Ψa|| =2
double factor = 2.0/norm(phi); phi *= factor;
H0 b = Nx(0) & Nx(1);
b = [bx, by]T
double j = (double)(d(X).det().quadrature_point_value(0)); J ≡ det ( ∂x ⁄ ∂ ξ )
C0 K_hourglass = (k_*j*((~b)*b) | dv) /12.0 * (phi%phi); kJ ( b • b )
k e ( hourglass ) ≡ ----------------------- ( Ψ ⊗ Ψ )
stiff &= K_standard + K_hourglass; 12
}
Element_Formulation* Element_Formulation::type_list = 0; ke = ke(1-point) + ke(hourglass)
Element_Type_Register element_type_register_instance;
static HourGlassHeatQ4 hourglassheatq4_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()));
uh = u; uh = gh;
cout << uh << endl;
return 0;
}

Listing 5•3 One point Gauss integration stiffness matrix with hourglass stabilizer element (project:
“hourglass_heat” in project workspace file “fe.dsw”).

Workbook of Applications in VectorSpace C++ Library 467


Chapter 5 Advanced Finite Element Methods
5.1.2 Mixed Formulation for Plane Elasticity
In the irreducible formulation for plane elasticity, the displacement field, u, is the only variable that the vari-
ation of the Lagrangian functional ( δ ( u )), derived from the equilibrium equations, is taken as

∫ ( Lδu ) σ dΩ – ∫ δu b dΩ – ∫ δuT h dΓ
T
δ (u) = T
Eq. 5•37
Ω Ω Γh

where the body force is denoted as b, the strain is defined as ε = Lu , and the differential operator L in matrix
form as


------ 0
∂x
L ≡ 0 ----- ∂ Eq. 5•38
-
∂y
∂ ∂
------ ------
∂y ∂x

The traction boundary condition is t = h on Γh. In the mixed formulation, in addition to the variational approxi-
mation on the equilibrium equations. we will also use the variational approximation to both the constitutive
equations and strain-displacement relations, separately. The interpolation functions Φ(x) in finite element
approximation of displacement field is taken as

u e ≡ Φ ea ( x )û ea Eq. 5•39

where Φ ea ( x ) is interpolation functions, and û ea is nodal displacements. The subscript “e” denotes the element
level.

Hellinger-Reissner Variational Principle


In addition to the variational principle based on equilibrium equation in Eq. 5•37, we consider the constitu-
tive equation

σ = D ε = D Lu Eq. 5•40

We also add stress field, σ, as additional variable in the Lagrangian functional such that

∫ δ σ T ( Lu – D –1 σ ) dΩ = 0 Eq. 5•41

Eq. 5•37 and Eq. 5•41 are the Euler-Lagrange equations corresponding to the Lagrangian functional.

( σ, u ) = --- ∫ σ T D –1 σ dΩ + ∫ u T ( L T σ + b ) dΩ – ∫ u T ( n σ – h ) dΓ
1
Eq. 5•42
2
Ω Ω Γh

468 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
where t = nσ (the Cauchy’s formula). The Euler-Lagrange equations are obtained by taking the directional deriv-
atives of the Lagrangian functional with respect to σ and u, then, make them equal to zero. Eq. 5•42 is known as
the Hellinger-Reissner variational principle. The finite element approximation of the stress field has interpola-
tion function Ψ(x) in

σe ≡ Ψea ( x ) σ̂e
a
Eq. 5•43

By inspecting Eq. 5•37 and Eq. 5•41, stress field has no derivatives taken on it. Therefore, the C0-continuity
requirement on the element boundaries, to ensure the integral equation does not give infinity, can be dropped.
The interpolation functions Ψea ( x ), in contrast to Φ ea ( x ) in Eq. 5•39, can be taken as piece-wise continuous func-
tions across the entire problem domain. For example, for four stress nodes taken at Gauss integration points with
the natural coordinates

 1 1 1 1 1 1 1 1 
[ ξ a, η a ] =  – -------, – ------- , -------, – ------- , -------, ------- , – -------, -------  Eq. 5•44
 3 3 3 3 3 3 3 3 

where a = 0, 1, 2, 3. The shape functions for these four nodes are

1
Ψ ea ( x ) ≡ --- ( 1 + 3ξ a ξ ) ( 1 + 3η a η ) Eq. 5•45
4

If such discontinuous (at the element boundaries) interpolation is taken, the stress field can be approximated glo-
bally. Because there is no inter-element dependency. The subscript “e” on Ψ can be dropped. The matrix form of
Eq. 5•37 and Eq. 5•41, at element level, is

AC
T
σ̂ =
f1
Eq. 5•46
C 0 û f2

where

A = – ∫ Ψ ⊗ ( D Ψ ) dΩ
–1
Eq. 5•47
Ωe

C = ∫ B ⊗ Ψ dΩ Eq. 5•48
Ωe

f1 = – A ( n σ )
T
Γ eσ
–C u Γ eu
Eq. 5•49

f2 = ∫Φ b dΩ + ∫Φ ( n σ ) dΓ – C ( n σ ) Eq. 5•50
Γ eσ
Ωe Γ eσ

Workbook of Applications in VectorSpace C++ Library 469


Chapter 5 Advanced Finite Element Methods
where B = LΦ. Again, if discontinuous interpolation on Ψ were taken, all terms involving Γσ dropped out in Eq.
5•11 and Eq. 5•12, which are then significantly simplified. To avoid singular conditions as discussed in Eq. 5•18
we should have the condition to avoid singularity that

nσ ≥ nu Eq. 5•51

Two quadrilateral elements with four σ-nodes and eight u-nodes (Q 4/8)1 are used to compute the beam bending
problem in the higher-order path test in the Chapter 4. The numbers of degree of freedom for the two fields are
nσ = 8 × 3 = 24, nu = 13 × 2-4 = 22, which satisfied the conceptual patch test criterion in Eq. 5•51. The implemen-
tation of Eq. 5•8 to Eq. 5•12 is shown in Program Listing 5•4 (project “hellinger_reissner_variational_principle”
in project workspace file “fe.dsw”). They present no new difficulty from the Program Listing 5•1. The solution
of the tip-deflection is 0.75, which is exact.

1. p.331 in Zienkiewicz, O.C. and R.L. Taylor, 1989, “The finite element method”, 4th ed., vol. 1. McGraw-Hill, Inc., UK.

470 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#include "include\fe.h"
#include "include\omega_h_n.h"
Matrix_Representation_Couple::assembly_switch
Matrix_Representation_Couple::Assembly_Switch = Matrix_Representation_Couple::ALL;
static const int row_node_no = 5; static const int row_segment_no = row_node_no-1;
static const double L_ = 10.0; static const double c_ = 1.0;
static const double h_e_ = L_/((double)row_segment_no);
static const double E_ = 1.e3; static const double v_ = 0.3;
Omega_h_i::Omega_h_i( int i) : Omega_h(0) {
if(i == 0) { Ωσ
double inv_sqrt3 = 1.0/sqrt(3.0), v[2], xl[4][2], zai, eta; Node *node;
xl[0][0] = 0.0; xl[0][1] = 0.0; xl[1][0] = 2.0*h_e_; xl[1][1] = 0.0; 1st element
xl[2][0] = 2.0*h_e_; xl[2][1] = 2.0*c_; xl[3][0] = 0.0; xl[3][1] = 2.0*c_; coordinates of four corner nodes
1 1
zai = - inv_sqrt3; eta = - inv_sqrt3; physcial coordinates at ( – -------, – ------- )
for(int j = 0; j < 2; j++) v[j] =(1.0-zai)*(1.0-eta)/4.0*xl[0][j]+(1.0+zai)*(1.0-eta)/4.0*xl[1][j]+ 3 3
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(0, 2, v); node_array().add(node);
1 1
zai = inv_sqrt3; eta = - inv_sqrt3; physcial coordinates at ( -------, – ------- )
for(int j = 0; j < 2; j++) v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+ 3 3
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(1, 2, v); node_array().add(node);
1 1
zai = inv_sqrt3; eta = inv_sqrt3; physcial coordinates at ( -------, ------- )
for(int j = 0; j < 2; j++) v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+ 3 3
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(2, 2, v); node_array().add(node);
1 1
zai = - inv_sqrt3; eta = inv_sqrt3; physcial coordinates at ( – -------, ------- )
for(int j = 0; j < 2; j++) v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ 1.0+zai)*(1.0-eta)/4.0*xl[1][j]+ 3 3
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(3, 2, v); node_array().add(node);
xl[0][0] = 2.0*h_e_; xl[0][1] = 0.0; xl[1][0] = 4.0*h_e_; xl[1][1] = 0.0; 2nd element
xl[2][0] = 4.0*h_e_; xl[2][1] = 2.0*c_; xl[3][0] = 2.0*h_e_; xl[3][1] = 2.0*c_;
zai = - inv_sqrt3; eta = - inv_sqrt3;
for(int j = 0; j < 2; j++) v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(4, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = - inv_sqrt3;
for(int j = 0; j < 2; j++) v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(5, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = inv_sqrt3;
for(int j = 0; j < 2; j++) v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(6, 2, v); node_array().add(node);
zai = - inv_sqrt3; eta = inv_sqrt3;
for(int j = 0; j < 2; j++) v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(7, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 0.0; node = new Node(8, 2, v); node_array().add(node); node # 8-20 are geometrical nodes
v[0] = 1.0*h_e_; node = new Node(9, 2, v); node_array().add(node); (serendipity)
v[0] = 2.0*h_e_; node = new Node(10, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(11, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(12, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 1.0*c_; node = new Node(13, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(14, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(15, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 2.0*c_; node = new Node(16, 2, v); node_array().add(node);
v[0] = 1.0*h_e_; node = new Node(17, 2, v); node_array().add(node);

Workbook of Applications in VectorSpace C++ Library 471


Chapter 5 Advanced Finite Element Methods
v[0] = 2.0*h_e_; node = new Node(18, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(19, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(20, 2, v); node_array().add(node);
int ena[12]; Omega_eh *elem;
ena[0] = 8; ena[1] = 10; ena[2] = 18; ena[3] = 16; ena[4] = 9; ena[5] = 14; last four nodes are σ-nodes
ena[6] = 17; ena[7] = 13; ena[8] = 0; ena[9] = 1; ena[10] = 2; ena[11] = 3;
elem = new Omega_eh(0, 0, 0, 12, ena); omega_eh_array().add(elem);
ena[0] = 10; ena[1] = 12; ena[2] = 20; ena[3] = 18; ena[4] = 11; ena[5] = 15;
ena[6] = 19; ena[7] = 14; ena[8] = 4; ena[9] = 5; ena[10] = 6; ena[11] = 7;
elem = new Omega_eh(1, 0, 0, 12, ena); omega_eh_array().add(elem);
} else if (i == 1) { // Omega_u
double v[2]; Node *node;
v[0] = 0.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node);
v[0] = h_e_; node = new Node(1, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(2, 2, v); node_array().add(node);
v[0] = 3.0*h_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] = 2.0*h_e_; node = new Node(6, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(7, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 2.0*c_; node = new Node(8, 2, v); node_array().add(node);
v[0] = h_e_; node = new Node(9, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(10, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(11, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(12, 2, v); node_array().add(node);
int ena[8]; Omega_eh *elem;
ena[0] = 0; ena[1] = 2; ena[2] = 10; ena[3] = 8; ena[4] = 1; ena[5] = 6; ena[6] = 9; ena[7] = 5;
elem = new Omega_eh(0, 0, 0, 8, ena); omega_eh_array().add(elem);
ena[0] = 2; ena[1] = 4; ena[2] = 12; ena[3] = 10; ena[4] = 3; ena[5] = 7; ena[6] = 11; ena[7] = 6;
elem = new Omega_eh(1, 0, 0, 8, ena); 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() {
gh_on_Gamma_h::__initialization(df, omega_h);
if(i == 0) {
for(int j = 8; j <= 20; j++) σ B.C.
for(int k = 0; k < df; k++) { disable all geometrical nodes
the_gh_array[node_order(j)](k) = gh_on_Gamma_h::Dirichlet; }
} else if(i == 1) { u B.C.
the_gh_array[node_order(4)](0) = the_gh_array[node_order(7)](0) = fixed B.C. at right-end
the_gh_array[node_order(12)](0) = the_gh_array[node_order(7)](1) =
gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(8)](0) = gh_on_Gamma_h::Neumann; Bending Moment on left-end
the_gh_array[node_order(8)][0] = -5.0;
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(0)][0] = 5.0;
}
}

static const int sigma_ndf = 3; static Omega_h_i oh_sigma(0);


static gh_on_Gamma_h_i sigma_gh(0, sigma_ndf, oh_sigma);
static U_h sigma_h(sigma_ndf, oh_sigma);
static Global_Discretization sigma_gd(oh_sigma, sigma_gh, sigma_h);
static const int u_ndf = 2; static Omega_h_i oh_u(1);
static gh_on_Gamma_h_i u_gh(1, u_ndf, oh_u);
static U_h u_h(u_ndf, oh_u);
static Global_Discretization u_gd(oh_u, u_gh, u_h);
static Global_Discretization_Couple gdc(u_gd, sigma_gd);

472 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
class ElasticQ84_Mixed_Formulation : public Element_Formulation_Couple { Q 4/8 element definition
public:
ElasticQ84_Mixed_Formulation(Element_Type_Register a):Element_Formulation_Couple(a) {}
Element_Formulation *make(int, Global_Discretization&);
ElasticQ84_Mixed_Formulation(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&);
ElasticQ84_Mixed_Formulation(int, Global_Discretization_Couple&);
};

Element_Formulation* ElasticQ84_Mixed_Formulation::make(int en,Global_Discretization& gd){


return new ElasticQ84_Mixed_Formulation(en,gd); }
diagonal A-matrix definition
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]);
ElasticQ84_Mixed_Formulation::ElasticQ84_Mixed_Formulation(
int en, Global_Discretization& gd) : Element_Formulation_Couple(en, gd) {
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(int, int, Quadrature", 8, 2, qp),
Serendipity shape functions for integra-
Zai, Eta; Zai &= Z[0]; Eta &= Z[1]; ion and coordinate transformation
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0; Step 1: initial four corner nodes
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
N[4] = (1.0-Zai.pow(2))*(1.0-Eta)/2.0; N[5] = (1.0-Eta.pow(2))*(1.0+Zai)/2.0;
N[6] = (1.0-Zai.pow(2))*(1.0+Eta)/2.0; N[7] = (1.0-Eta.pow(2))*(1.0-Zai)/2.0; Step 2: add four edge nodes
N[0] = N[0] - (N[4]+N[7])/2.0; N[1] = N[1] - (N[4]+N[5])/2.0; Step 3: correction of four corner nodes
N[2] = N[2] - (N[5]+N[6])/2.0; N[3] = N[3] - (N[6]+N[7])/2.0;
C0 x = MATRIX("int, int, C0&, int, int", 8, 2, xl, 0, 0);
the presence of four edge nodes
H1 X = N*x; J dv(d(X).det());
double sqrt3 = sqrt(3.0); C0 zero(0.0);
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4/*nen*/, qp), n8, n9, n10, n11;
H0 zai, eta;
Ψ with four nodes at four Gauss
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]); integration points
n[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; n[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0; N0-7 =0 (corresponding to geom. nodes)
n[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
n8 &= n[0]; n9 &= n[1]; n10 &= n[2];
n[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
n11 &= n[3];
N8-11 = Ψa
H0 N_sig = ( (n8 | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 | zero | zero ) & Nσ
(zero | n8 | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 | zero ) &
(zero |(zero | n8) | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 ));
Matrix::Decomposition_Method = Matrix::Cholesky_Decomposition;
C0 D_inv = D.inverse(); D-1
Matrix::Decomposition_Method = Matrix::LU_Decomposition; A = – ∫ Ψ ⊗ ( D –1 Ψ ) dΩ
stiff &= MATRIX("int, int", 36, 36); Ωe
C0 stiff_sub = MATRIX("int, int, C0&, int, int", 12, 12, stiff, 24, 24);
stiff_sub = -((~N_sig) * (D_inv * N_sig)) | dv;
}

Element_Formulation_Couple* ElasticQ84_Mixed_Formulation::make(
off-diagonal C-matrix definition
int en, Global_Discretization_Couple& gdc) {
return new ElasticQ84_Mixed_Formulation(en,gdc);
}
ElasticQ84_Mixed_Formulation::ElasticQ84_Mixed_Formulation(
int en, Global_Discretization_Couple& gdc) : Element_Formulation_Couple(en, gdc) {
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 8, 2, qp),
Zai, Eta;
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;

Workbook of Applications in VectorSpace C++ Library 473


Chapter 5 Advanced Finite Element Methods
N[4] = (1.0-Zai.pow(2))*(1.0-Eta)/2.0; N[5] = (1.0-Eta.pow(2))*(1.0+Zai)/2.0;
N[6] = (1.0-Zai.pow(2))*(1.0+Eta)/2.0; N[7] = (1.0-Eta.pow(2))*(1.0-Zai)/2.0;
N[0] = N[0] - (N[4]+N[7])/2.0; N[1] = N[1] - (N[4]+N[5])/2.0;
N[2] = N[2] - (N[5]+N[6])/2.0; N[3] = N[3] - (N[6]+N[7])/2.0;
H1 X = N*xl;
H0 Nx = d(N) * d(X).inverse();
J dv(d(X).det());
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2, Nx),
wx, wy, B; ∂
------ 0
wx &= w_x[0][0]; wy &= w_x[0][1]; ∂x
C0 zero(0.0);
B = 0 ----- ∂ N
B &= (~wx || zero) & -
(zero || ~wy ) & ∂y
(~wy || ~wx ); ∂ ∂
------ ------
double sqrt3 = sqrt(3.0); ∂y ∂x
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 12/*nen*/, qp),
n8, n9, n10, n11, zai, eta;
zai &= (H0)Z[0]; eta &= (H0)Z[1];
n[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; n[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0;
n[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; n[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
n8 &= n[0]; n9 &= n[1]; n10 &= n[2]; n11 &= n[3];
H0 N_sig = ((n8 | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 | zero | zero ) &
(zero | n8 | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 | zero ) &
(zero |(zero | n8) | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 ));
stiff &= MATRIX("int, int", 16, 36);
C0 stiff_sub = MATRIX("int, int, C0&, int, int", 16, 12, stiff, 0, 24);
C = ∫ B ⊗ Ψ dΩ
Ωe
stiff_sub = ((~B)*N_sig) | dv;
}
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static ElasticQ84_Mixed_Formulation
elasticq84_mixed_formulation_instance(element_type_register_instance);
static Matrix_Representation mr(sigma_gd);
static Matrix_Representation_Couple mrc(gdc, 0, 0, &(mr.rhs()), &mr);
int main() {
mr.assembly();
mrc.assembly();
C0 A = ((C0)(mr.lhs())), f_1 = ((C0)(mr.rhs())),
C = ((C0)(mrc.lhs())), f_2 = ((C0)(mrc.rhs()));
Cholesky dnH(-A); take care of negative definitiveness of A
C0 Ainv = -(dnH.inverse());
C0 CAinvCt = C*Ainv*(~C);
LU dCAinvCt(CAinvCt);
cout << "projected Hessian condition number: " << dCAinvCt.cond() << endl; û = (CA-1CT)-1(CA-1f1-f2)
C0 u = dCAinvCt*(C*(-(dnH*f_1)) - f_2);
C0 sigma = -(dnH*(f_1-(~C)*u));
sigma_h = sigma; sigma_h = sigma_gd.gh_on_gamma_h();
cout << "stress(sig-x, sig-y, sig-xy):" << endl; σ̂ = A-1 (f1- CT û )
for(int i = 0; i < sigma_h.total_node_no(); i++)
cout << sigma_h[i] << endl;
u_h = u;
u_h = u_gd.gh_on_gamma_h();
cout << "displacement(u-x, u-y):" << endl << u_h;
return 0;
}

Listing 5•4 Substructure solution for the Hellinger-Reissner variational principle for plane elasticity
(project: “hellinger_reissner_variational_formulation” in project workspace file “fe.dsw”).

474 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
Hu-Washizu Variational Principle
In addition to Eq. 5•37 , the constitutive equations and the strain-displacement relations can be used such as

σ = Dε , and ε = Lu Eq. 5•52

The variational approximation to these two equations are

∫ δ εT ( D ε – σ ) dΩ = 0, and ∫ δ σ T ( Lu – ε ) dΩ = 0 Eq. 5•53


Ω Ω

Eq. 5•53 and Eq. 5•37 are the Euler-Lagange equations of the Lagrangian functional

( ε, σ, u ) = --- ∫ ε T D ε dΩ – ∫ σ T ( ε – Lu ) dΩ – ∫ uT b dΩ – ∫ uT h dΓ
1
Eq. 5•54
2
Ω Ω Ω Γh

The Lagrangian functional in Eq. 5•54 is known as the Hu-Washizu variational principle. The finite element
approximation to the strain field uses the interpolation functions Ξ(x) as

ε e ≡ Ξea ( x ) ε ea Eq. 5•55

The matrix form, at element level, of Eq. 5•37 and Eq. 5•53 is

A CT 0 ε̂ f1
Eq. 5•56
C 0 ET σ̂ = f2
0 E 0 f3

where

A = ∫ Ξ ⊗ ( D Ξ ) dΩ Eq. 5•57
Ωe

E = ∫ B ⊗ Ψ dΩ Eq. 5•58
Ωe

C = – ∫ Ξ ⊗ Ψ dΩ Eq. 5•59
Ωe

f1 = A( n ε ) – CT( nσ) Eq. 5•60


Γ eε Γ eσ

Workbook of Applications in VectorSpace C++ Library 475


Chapter 5 Advanced Finite Element Methods

f2 = –C ( n ε ) – ETu Eq. 5•61


Γ eε
Γ eu

f3 = ∫Φ b dΩ + ∫Φ ( n σ ) dΓ – E ( n σ ) Eq. 5•62
Γ eσ
Ωe Γ eσ

The condition for non-singular matrices is1

n ε + n u ≥ n σ , and n σ ≥ n u Eq. 5•63

The Program Listing 5•5 implements Eq. 5•56 to Eq. 5•62 (project “hu_washizu_variational_principle” in
project workspace file “fe.dsw”). With the same patch test problem for the Hellinger-Reissner variational princi-
ple in the previous section. We use shape functions with four nodes at Gaussian integration points for both stress
(σ) and strain (ε) fields; i.e., Ξ = Ψ. This results in C-matrix in Eq. 5•59 to be symmetrical negative definitive.
Care should be taken, if Cholesky decomposition is used, which is applicable to a symmetrical positive defini-
tive matrix. The displacement (u) shape function Φ is an eight-nodes serendipity element. These choices satisfy
the condition in Eq. 5•63. The coding of matrix substructuring technique supported by “fe.lib” becomes a little
more elaborated with three-fields (ε, σ, u) instead of two-fields (σ, u).
The modification from two-fields problem is minor, however. For the definitions of discretized global
domain and boundary have three index entries as

1 Omega_h_i::Omega_h_i(int i) : Omega_h(0) { // Ωhi


2 if(i == 0 || i == 1) { // Ωhε or Ωhσ
3 ...
4 } else if(i == 2) { // Ωhu
5 ...
6 }
7 gh_on_Gamma_h_i::gh_on_Gamma_h_i(int i, int df, Omega_h& omega_h) : gh_on_Gamma_h() { // Γhi
8 gh_on_Gamma_h::__initialization(df, omega_h);
9 if(i == 0) { // Γhε
10 ...
11 } else if(i == 1) { // Γhσ
12 ...
13 } else if(i == 2) { // Γhu
14 ...
15 }
16 }

The instantiation of global discretized couples are

1. see discussion in p.333-334 in Zienkiewicz, O.C. and R.L. Taylor, 1989, “The finite element method”, 4th ed., vol. 1.
McGraw-Hill, Inc., UK.

476 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#include "include\fe.h"
#include "include\omega_h_n.h"
Matrix_Representation_Couple::assembly_switch Matrix_Representation_Couple::
Assembly_Switch = Matrix_Representation_Couple::ALL;
static const int row_node_no = 5; static const int row_segment_no = row_node_no-1;
static const double L_ = 10.0; static const double c_ = 1.0;
static const double h_e_ = L_/((double)row_segment_no);
static const double E_ = 1.e3; static const double v_ = 0.3;
Omega_h_i::Omega_h_i(int i) : Omega_h(0) {
if(i == 0 || i == 1) { define Ωε, and Ωσ
double inv_sqrt3 = 1.0/sqrt(3.0), v[2], xl[4][2], zai, eta;
Node *node;
xl[0][0] = 0.0; xl[0][1] = 0.0; xl[1][0] = 2.0*h_e_; xl[1][1] = 0.0; elem # 0 nodal coordinates
xl[2][0] = 2.0*h_e_; xl[2][1] = 2.0*c_; xl[3][0] = 0.0; xl[3][1] = 2.0*c_;
zai = - inv_sqrt3; eta = - inv_sqrt3;
for(int j = 0; j < 2; j++)
1st Gauss point natural coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+ 1st Gauss point physical coordinates
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(0, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = - inv_sqrt3;
2nd Gauss point natural coordinates
for(int j = 0; j < 2; j++) 2nd Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+(1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+(1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(1, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = inv_sqrt3; 3rd Gauss point natural coordinates
for(int j = 0; j < 2; j++) 3rd Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(2, 2, v); node_array().add(node);
zai = - inv_sqrt3; eta = inv_sqrt3; 4th Gauss point natural coordinates
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
4th Gauss point physical coordinates
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(3, 2, v); node_array().add(node);
xl[0][0] = 2.0*h_e_; xl[0][1] = 0.0; xl[1][0] = 4.0*h_e_; xl[1][1] = 0.0;
xl[2][0] = 4.0*h_e_; xl[2][1] = 2.0*c_; xl[3][0] = 2.0*h_e_; xl[3][1] = 2.0*c_;
elem # 1 nodal coordinates
zai = - inv_sqrt3; eta = - inv_sqrt3; 1st Gauss point natural coordinates
for(int j = 0; j < 2; j++) 1st Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(4, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = - inv_sqrt3; 2nd Gauss point natural coordinates
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
2nd Gauss point physical coordinates
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(5, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = inv_sqrt3;
for(int j = 0; j < 2; j++)
3rd Gauss point natural coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+ 3rd Gauss point physical coordinates
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(6, 2, v); node_array().add(node);
zai = - inv_sqrt3; eta = inv_sqrt3;
4th Gauss point natural coordinates
for(int j = 0; j < 2; j++) 4th Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(7, 2, v); node_array().add(node);

Workbook of Applications in VectorSpace C++ Library 477


Chapter 5 Advanced Finite Element Methods
v[0] = 0.0; v[1] = 0.0; node = new Node(8, 2, v); node_array().add(node); “geometrical nodes” supply four corner
v[0] = 1.0*h_e_; node = new Node(9, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(10, 2, v); node_array().add(node); nodes coordinates to the element level
v[0] = 3.0*h_e_; node = new Node(11, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(12, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 1.0*c_; node = new Node(13, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(14, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(15, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 2.0*c_; node = new Node(16, 2, v); node_array().add(node);
v[0] = 1.0*h_e_; node = new Node(17, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(18, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(19, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(20, 2, v); node_array().add(node);
int ena[12]; Omega_eh *elem;
ena[0] = 8; ena[1] = 10; ena[2] = 18; ena[3] = 16; ena[4] = 9; ena[5] = 14; define elements
ena[6] = 17; ena[7] = 13; ena[8] = 0; ena[9] = 1; ena[10] = 2; ena[11] = 3; first 8 nodes are geometrical nodes
elem = new Omega_eh(0, 0, 0, 12, ena); omega_eh_array().add(elem); last four nodes are real nodes
ena[0] = 10; ena[1] = 12; ena[2] = 20; ena[3] = 18; ena[4] = 11; ena[5] = 15;
ena[6] = 19; ena[7] = 14; ena[8] = 4; ena[9] = 5; ena[10] = 6; ena[11] = 7;
elem = new Omega_eh(1, 0, 0, 12, ena); omega_eh_array().add(elem);
} else if(i == 2) { Ωu
double v[2]; Node *node;
v[0] = 0.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node); 8-node serendipity element
v[0] = h_e_; node = new Node(1, 2, v); node_array().add(node); define nodes
v[0] = 2.0*h_e_; node = new Node(2, 2, v); node_array().add(node);
v[0] = 3.0*h_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] = 2.0*h_e_; node = new Node(6, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(7, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 2.0*c_; node = new Node(8, 2, v); node_array().add(node);
v[0] = h_e_; node = new Node(9, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(10, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(11, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(12, 2, v); node_array().add(node);
int ena[8]; Omega_eh *elem; define elements
ena[0] = 0; ena[1] = 2; ena[2] = 10; ena[3] = 8; ena[4] = 1; ena[5] = 6; ena[6] = 9; ena[7] = 5;
elem = new Omega_eh(0, 0, 0, 8, ena); omega_eh_array().add(elem);
ena[0] = 2; ena[1] = 4; ena[2] = 12; ena[3] = 10; ena[4] = 3; ena[5] = 7; ena[6] = 11; ena[7] = 6;
elem = new Omega_eh(1, 0, 0, 8, ena); 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() {
gh_on_Gamma_h::__initialization(df, omega_h); Ωε B.C.
if(i == 0) { disable all geometrical nodes
for(int j = 8; j <= 20; j++)
for(int k = 0; k < df; k++) the_gh_array[node_order(j)](k) = gh_on_Gamma_h::Dirichlet;
} else if(i == 1) { Ωσ B.C.
for(int j = 8; j <= 20; j++) disable all geometrical nodes
for(int k = 0; k < df; k++) the_gh_array[node_order(j)](k) = gh_on_Gamma_h::Dirichlet;
} else if(i == 2) {
the_gh_array[node_order(4)](0) = the_gh_array[node_order(7)](0) = Wu boundary condition
the_gh_array[node_order(12)](0) = the_gh_array[node_order(7)](1) =
gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(8)](0) = the_gh_array[node_order(0)](0) =
gh_on_Gamma_h::Neumann; Simpson’s integration rule:
the_gh_array[node_order(8)][0] = -5.0; {-15 *2/3, 0*4/3, 15*2./3}
the_gh_array[node_order(0)][0] = 5.0;
} = {-5, 0, 5}
}

478 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
static const int epsilon_ndf = 3; static Omega_h_i oh_epsilon(0); Ωε
static gh_on_Gamma_h_i epsilon_gh(0, epsilon_ndf, oh_epsilon);
static U_h epsilon_h(epsilon_ndf, oh_epsilon);
g.d. ε for defining A matrix
static Global_Discretization *epsilon_type = new Global_Discretization; εh = {εx, εy, γ}T
static Global_Discretization epsilon_gd(oh_epsilon, epsilon_gh, epsilon_h, epsilon_type);
static const int sigma_ndf = 3; static Omega_h_i oh_sigma(1);
static gh_on_Gamma_h_i sigma_gh(1, sigma_ndf, oh_sigma);
Ωσ
static U_h sigma_h(sigma_ndf, oh_sigma); σh = {σx, σy, τ}T
static Global_Discretization sigma_gd(oh_sigma, sigma_gh, sigma_h);
static const int u_ndf = 2; static Omega_h_i oh_u(2);
static gh_on_Gamma_h_i u_gh(2, u_ndf, oh_u); static U_h u_h(u_ndf, oh_u);
Ωu , uh = {u, v}T
static Global_Discretization *u_type = new Global_Discretization; g.d. u for defining irreducible K matrix
static Global_Discretization u_gd(oh_u, u_gh, u_h, u_type);
static Global_Discretization_Couple *sigma_epsilon = new Global_Discretization_Couple();
static Global_Discretization_Couple gdc_sigma_epsilon(sigma_gd, epsilon_gd, sigma_epsilon);
g.d.couple {σ-ε} for defining C matrix
static Global_Discretization_Couple *u_sigma = new Global_Discretization_Couple();
static Global_Discretization_Couple gdc_u_sigma(u_gd, sigma_gd, u_sigma); g.d.couple {u-σ} for defining E matrix
class ElasticQ84_Mixed_Formulation : public Element_Formulation_Couple {
public:
ElasticQ84_Mixed_Formulation(Element_Type_Register a):Element_Formulation_Couple(a) {}
Element_Formulation *make(int, Global_Discretization&); diagonals A & K matrices
ElasticQ84_Mixed_Formulation(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&);
ElasticQ84_Mixed_Formulation(int, Global_Discretization_Couple&); off-diagonals C & E matrices
};
Element_Formulation* ElasticQ84_Mixed_Formulation::make(
int en,Global_Discretization& gd) { return new ElasticQ84_Mixed_Formulation(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]);
ElasticQ84_Mixed_Formulation::ElasticQ84_Mixed_Formulation(
int en, Global_Discretization& gd) : Element_Formulation_Couple(en, gd) { diagonals matrices
if(gd.type() == epsilon_type) { A matrix formulation
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp), Zai, Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 8, 2, qp);
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
eight nodes serendipity shape function
N[4] = (1.0-Zai.pow(2))*(1.0-Eta)/2.0; N[5] = (1.0-Eta.pow(2))*(1.0+Zai)/2.0;
N[6] = (1.0-Zai.pow(2))*(1.0+Eta)/2.0; N[7] = (1.0-Eta.pow(2))*(1.0-Zai)/2.0;
N[0] = N[0] - (N[4]+N[7])/2.0; N[1] = N[1] - (N[4]+N[5])/2.0;
N[2] = N[2] - (N[5]+N[6])/2.0; N[3] = N[3] - (N[6]+N[7])/2.0;
C0 x = MATRIX("int, int, C0&, int, int", 8, 2, xl, 0, 0);
H1 X = N*x; J dv(d(X).det()); double sqrt3 = sqrt(3.0); C0 zero(0.0);
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4, qp), n8, n9, n10, n11, zai, eta;
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]);
n[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; n[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0;
n[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; n[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
n8 &= n[0]; n9 &= n[1]; n10 &= n[2]; n11 &= n[3];
H0 N_epsilon = ((n8 | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 | zero | zero ) &
εh = {εx, εy, γ}T
(zero | n8 | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 | zero ) &
(zero |(zero | n8) | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 ));

∫ Ξ ⊗ ( D Ξ ) dΩ
stiff &= MATRIX("int, int", 36, 36);
C0 stiff_sub = MATRIX("int, int, C0&, int, int", 12, 12, stiff, 24, 24);
A =
stiff_sub = ((~N_epsilon) * (D * N_epsilon)) | dv; Ωe
} else { K matrix; for iterative method to esti-
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp), , Zai, Eta
mate initial u values
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 8, 2, qp);

Workbook of Applications in VectorSpace C++ Library 479


Chapter 5 Advanced Finite Element Methods
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
N[4] = (1.0-Zai.pow(2))*(1.0-Eta)/2.0; N[5] = (1.0-Eta.pow(2))*(1.0+Zai)/2.0;
N[6] = (1.0-Zai.pow(2))*(1.0+Eta)/2.0; N[7] = (1.0-Eta.pow(2))*(1.0-Zai)/2.0;
N[0] -= (N[4]+N[7])/2.0; N[1] -= (N[4]+N[5])/2.0;
N[2] -= (N[5]+N[6])/2.0; N[3] -= (N[6]+N[7])/2.0;
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dv(d(X).det());
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2, Nx), wx, wy, B;
wx &= w_x[0][0]; wy &= w_x[0][1]; C0 zero(0.0);
B &= (~wx || zero) & (zero || ~wy ) & (~wy || ~wx );
K is a standard stiffness matrix
stiff &= ((~B)*D*B)|dv;
}
}
Element_Formulation_Couple* ElasticQ84_Mixed_Formulation::make(int en,
Global_Discretization_Couple& gdc) { return new ElasticQ84_Mixed_Formulation(en,gdc); } off-diagonal matrices
ElasticQ84_Mixed_Formulation::ElasticQ84_Mixed_Formulation(int en,
Global_Discretization_Couple& gdc) : Element_Formulation_Couple(en, gdc) {
if(gdc.type() == sigma_epsilon) {
C matrux formulation
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp), Zai, Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature",8,2, qp);
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
N[4] = (1.0-Zai.pow(2))*(1.0-Eta)/2.0; N[5] = (1.0-Eta.pow(2))*(1.0+Zai)/2.0;
N[6] = (1.0-Zai.pow(2))*(1.0+Eta)/2.0; N[7] = (1.0-Eta.pow(2))*(1.0-Zai)/2.0;
N[0] = N[0] - (N[4]+N[7])/2.0; N[1] = N[1] - (N[4]+N[5])/2.0;
N[2] = N[2] - (N[5]+N[6])/2.0; N[3] = N[3] - (N[6]+N[7])/2.0;
H1 X = N*xl; J dv(d(X).det());
double sqrt3 = sqrt(3.0); C0 zero(0.0);
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4, qp), n8, n9, n10, n11, zai, eta;
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]);
n[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; n[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0;
n[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; n[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
n8 &= n[0]; n9 &= n[1]; n10 &= n[2]; n11 &= n[3];
H0 N_sig = ((n8 | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 | zero | zero ) & σh = {σx, σy, τ}T
(zero | n8 | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 | zero ) &
(zero |(zero | n8) | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 ));
H0 N_epsilon; N_epsilon &= N_sig;
stiff &= MATRIX("int, int", 36, 36); C = – ∫ Ξ ⊗ Ψ dΩ
C0 stiff_sub = MATRIX("int, int, C0&, int, int", 12, 12, stiff, 24, 24);
Ωe
stiff_sub = -((~N_sig)*N_epsilon) | dv;
} else { E matrux formulation
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp), Zai, Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature",8,2, qp);
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
N[4] = (1.0-Zai.pow(2))*(1.0-Eta)/2.0; N[5] = (1.0-Eta.pow(2))*(1.0+Zai)/2.0;
N[6] = (1.0-Zai.pow(2))*(1.0+Eta)/2.0; N[7] = (1.0-Eta.pow(2))*(1.0-Zai)/2.0;
N[0] = N[0] - (N[4]+N[7])/2.0; N[1] = N[1] - (N[4]+N[5])/2.0;
N[2] = N[2] - (N[5]+N[6])/2.0; N[3] = N[3] - (N[6]+N[7])/2.0;
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dv(d(X).det());
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2, Nx), wx, wy, B;
wx &= w_x[0][0]; wy &= w_x[0][1]; C0 zero(0.0);
B &= (~wx || zero) &
(zero || ~wy ) &
(~wy || ~wx );

480 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
double sqrt3 = sqrt(3.0);
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4, qp), n8, n9, n10, n11, zai, eta;
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]);
n[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; n[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0;
n[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; n[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;

σh = {σx, σy, τ}T


n8 &= n[0]; n9 &= n[1]; n10 &= n[2]; n11 &= n[3];
H0 N_sig = ((n8 | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 | zero | zero ) &
(zero | n8 | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 | zero ) &
(zero |(zero | n8) | zero | zero | n9 | zero | zero | n10 | zero | zero | n11 ));
stiff &= MATRIX("int, int", 16, 36);
C0 stiff_sub = MATRIX("int, int, C0&, int, int", 16, 12, stiff, 0, 24); E = ∫ B ⊗ Ψ dΩ
stiff_sub = ((~B)*N_sig) | dv; Ωe
}
}
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static ElasticQ84_Mixed_Formulation
elasticq84_mixed_formulation_instance(element_type_register_instance);
static Matrix_Representation mr(epsilon_gd); for A matrix
static Matrix_Representation_Couple mrcC(gdc_sigma_epsilon, 0, 0, &(mr.rhs()), &mr); for C matrix
static Matrix_Representation_Couple mrcE(gdc_u_sigma, 0, 0, &(mrcC.rhs()), &mrcC); for E matrix
int main() {
mrcC.assembly();
mr.assembly();
mrcE.assembly();
C0 A = ((C0)(mr.lhs())),
f_1 = ((C0)(mr.rhs())),
C = ((C0)(mrcC.lhs())),
f_2 = ((C0)(mrcC.rhs())),
E = ((C0)(mrcE.lhs())),
f_3 = ((C0)(mrcE.rhs()));
Cholesky dnC(-C);
C0 Cinv = -(dnC.inverse()), C-1
CinvA = Cinv*A,
CinvACinv = Cinv*A*Cinv,
ECinvACinv = E*CinvACinv, EC-1 AC-1 ET
ECinvACinvEt = ECinvACinv*(~E); û = (EC-1 AC-1 ET)-1
Cholesky dECinvACinvEt(ECinvACinvEt);
C0 u = dECinvACinvEt * (f_3 - (E*Cinv)*f_1 + ECinvACinv*f_2); (f3 - EC-1 f1 + EC-1 AC-1 f2)
C0 epsilon = -(dnC*(f_2-(~E)*u));
C0 sigma = -(dnC*(f_1-A*epsilon));
epsilon_h = epsilon;
ε̂ = C-1(f2-ET û ) and σ̂ = C-1(f1-Aε̂ )
epsilon_h = epsilon_gd.gh_on_gamma_h();
cout << "strains(epsilon-x, epsilon-y, epsilon-xy):" << endl;
for(int i = 0; i < epsilon_h.total_node_no(); i++)
cout << epsilon_h[i] << endl;
sigma_h = sigma;
sigma_h = sigma_gd.gh_on_gamma_h();
cout << "stress(sig-x, sig-y, sig-xy):" << endl;
for(int i = 0; i < sigma_h.total_node_no(); i++)
cout << sigma_h[i] << endl;
u_h = u; u_h = u_gd.gh_on_gamma_h();
cout << "displacement(u-x, u-y):" << endl<< u_h << endl;
return 0;
}

Listing 5•5 Substructure method for the three-field Hu-Washizu variational principle for plane elasticity
(project: “hu_wahsizu_variational_formulation” in project workspace file “fe.dsw”).

Workbook of Applications in VectorSpace C++ Library 481


Chapter 5 Advanced Finite Element Methods
1 static const int epsilon_ndf = 3; // ndfε
2 static Omega_h_i oh_epsilon(0); // Ωhε
3 static gh_on_Gamma_h_i epsilon_gh(0, epsilon_ndf, oh_epsilon); // Γhε
4 static U_h epsilon_h(epsilon_ndf, oh_epsilon); // εx, εy εxy
5 static Global_Discretization *epsilon_type = new Global_Discretization;
6 static Global_Discretization epsilon_gd(oh_epsilon, epsilon_gh, epsilon_h, epsilon_type);
7 static const int sigma_ndf = 3; // ndfσ
8 static Omega_h_i oh_sigma(1); // Ωhσ
9 static gh_on_Gamma_h_i sigma_gh(1, sigma_ndf, oh_sigma); // Γhσ
10 static U_h sigma_h(sigma_ndf, oh_sigma); // σx, σy σxy
11 static Global_Discretization sigma_gd(oh_sigma, sigma_gh, sigma_h);
12 static const int u_ndf = 2; // ndfu
13 static Omega_h_i oh_u(2); // Ωhu
14 static gh_on_Gamma_h_i u_gh(2, u_ndf, oh_u); // Γhu
15 static U_h u_h(u_ndf, oh_u); // ux, uy
16 static Global_Discretization *u_type = new Global_Discretization;
17 static Global_Discretization u_gd(oh_u, u_gh, u_h, u_type);
18 static Global_Discretization_Couple *sigma_epsilon = new Global_Discretization_Couple(); // σ−ε
19 static Global_Discretization_Couple gdc_sigma_epsilon(sigma_gd, epsilon_gd, sigma_epsilon);
20 static Global_Discretization_Couple *u_sigma = new Global_Discretization_Couple(); // u-σ
21 static Global_Discretization_Couple gdc_u_sigma(u_gd, sigma_gd, u_sigma);

Three pointers to Global_Discretization and Global_Discretization_Coupe, epsilon_type, u_type, and


sigma_epsilon, are to be used in Element_Formulation class definitions to switch to corresponding segments of
submatrices definitions.

1 class ElasticQ84_Mixed_Formulation : public Element_Formulation_Couple {


2 public:
3 ElasticQ84_Mixed_Formulation(Element_Type_Register a) : Element_Formulation_Couple(a) {}
4 Element_Formulation *make(int, Global_Discretization&); // diagonal submatrices
5 ElasticQ84_Mixed_Formulation(int, Global_Discretization&);
6 Element_Formulation_Couple *make(int, Global_Discretization_Couple&);// off-diagonal submatrices
7 ElasticQ84_Mixed_Formulation(int, Global_Discretization_Couple&);
8 };
9 Element_Formulation* ElasticQ84_Mixed_Formulation::make(int en, Global_Discretization& gd) {
10 return new ElasticQ84_Mixed_Formulation(en,gd);
11 }
12 ElasticQ84_Mixed_Formulation::ElasticQ84_Mixed_Formulation(int en, Global_Discretization& gd) :
13 Element_Formulation_Couple(en, gd) {
14 if(gd.type() == epsilon_type) { // define A-Matrix; ε field
15 ...
16 } else { // K-matrix; u-field, for iterative method to estimate initial u values, Z&T p. 361, Eq. 12.99
17 ...
18 }
19 }

482 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
20 Element_Formulation_Couple* ElasticQ84_Mixed_Formulation::make(
21 int en, Global_Discretization_Couple& gdc) { return new ElasticQ84_Mixed_Formulation(en,gdc); }
22 ElasticQ84_Mixed_Formulation::ElasticQ84_Mixed_Formulation(
23 int en, Global_Discretization_Couple& gdc) : Element_Formulation_Couple(en, gdc) {
24 if(gdc.type() == sigma_epsilon) { // define C-Matrix; σ−ε fields
25 ...
26 } else { // define E-matrix; u-σ fields
27 ...
28 }
29 }

Line 16 is to construct the “convergence acceleration matrix” if iterative method is used.1 The global solution is
proceeded with û to be solved first as

û = (EC-1 AC-1 ET)-1 (f3 - EC-1 f1 + EC-1 AC-1 f2) Eq. 5•64

After û is obtained, we can substituting û in the following equation

ε̂ = C-1 (f2 - ET û ) Eq. 5•65

Then, σ̂ is solved by substituting ε̂ in

σ̂ = C-1 (f1 - A ε̂ ) Eq. 5•66

The Matrix_Representations are in instantiated in the followings with the global substructuring solution

1 int main() {
2 Matrix_Representation mr(epsilon_gd);
3 Matrix_Representation_Couple mrcC(gdc_sigma_epsilon, 0, 0, &(mr.rhs()), &mr);
4 Matrix_Representation_Couple mrcE(gdc_u_sigma, 0, 0, &(mrcC.rhs()), &mrcC);
5 mrcC.assembly();
6 mr.assembly();
7 mrcE.assembly();
8 C0 A = ((C0)(mr.lhs())), f_1 = ((C0)(mr.rhs())),
9 C = ((C0)(mrcC.lhs())), f_2 = ((C0)(mrcC.rhs())),
10 E = ((C0)(mrcE.lhs())), f_3 = ((C0)(mrcE.rhs()));
11 Cholesky dnC(-C); // decomposition; C is symmetrical negative definite
12 C0 Cinv = -(dnC.inverse()); // û = (EC-1AC-1ET)-1 (f3-EC-1f1+ EC-1AC-1f2)
13 C0 CinvA = Cinv*A;
14 C0 CinvACinv = CinvA*Cinv;

1. see next section and p. 361 in Zienkiewicz, O.C. and R.L. Taylor, 1989, “The finite element method”, 4th ed., vol. 1.
McGraw-Hill, Inc., UK.

Workbook of Applications in VectorSpace C++ Library 483


Chapter 5 Advanced Finite Element Methods
15 C0 ECinvACinv = E*CinvACinv;
16 C0 ECinvACinvEt = ECinvACinv*(~E);
17 Cholesky dECinvACinvEt(ECinvACinvEt);
18 C0 u = dECinvACinvEt * (f_3 - (E*Cinv)*f_1 + ECinvACinv*f_2);
19 C0 epsilon = -(dnC*(f_2-(~E)*u)); // ε̂ = C-1(f2-ET û )
20 C0 sigma = -(dnC*(f_1-A*epsilon)); // σ̂ = C-1(f1-A ε̂ )
21 epsilon_h = epsilon; // update free degree of freedom
22 epsilon_h = epsilon_gd.gh_on_gamma_h(); // update fixed degree of freedom
23 cout << "strains(epsilon-x, epsilon-y, epsilon-xy):" << endl;
24 for(int i = 0; i < epsilon_h.total_node_no(); i++)
25 cout << epsilon_h[i] << endl;
26 sigma_h = sigma; // update free degree of freedom
27 sigma_h = sigma_gd.gh_on_gamma_h(); // update fixed degree of freedom
28 cout << "stress(sig-x, sig-y, sig-xy):" << endl;
29 for(int i = 0; i < sigma_h.total_node_no(); i++)
30 cout << sigma_h[i] << endl;
31 u_h = u; // update free degree of freedom
32 u_h = u_gd.gh_on_gamma_h(); // update fixed degree of freedom
33 cout << "displacement(u-x, u-y):" << endl << u_h;
34 return 0;
35}

The tip-deflection solution for the bending problem is the same as the one solved in the higher-order patch test
case. They are both exact.

Iterative Method for Hu-Washizu Mixed Formulation


Starting from zero initial values

σ̂ ε̂
0 0
= = û 0 = 0 Eq. 5•67

The irreducible stiffness matrix K is defined as

K = ∫ B T DBdΩ Eq. 5•68


The iterative procedure starts from using the stiffness matrix from the irreducible formulation as the conver-
gence acceleration matrix

Kδû = r n Eq. 5•69

where δ û = û n+1 - û n, therefore, the next iteration solution û n+1 can be obtained. The computation of û 1 from
û 0(=0) is the same as the solution from the standard irreducible formulation. rn is defined as the residual of the
third equation in Eq. 5•56 as

484 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

r n ≡ E T σ̂ – f 3
n
Eq. 5•70

the next iterative solutions for strain ε̂ n+1, and stress σ̂ n+1 are computed from

ε̂ σ̂ = D ε̂
n+1 n+1 n+1
= C T Eû n + 1, then Eq. 5•71

We test this iterative procedure by setting the Poisson ratio ν = 0.5-10-12, which is nearly incompressible. In
addition, the plane strain is assumed. The same project “hu_washizu_variational_principle” in project workspace
file “fe.dsw” with macro definitions “__TEST_NEARLY_INCOMPRESSIBLE_PLANE_STRAIN” and
“__TEST_AUGMENTED_LAGRANGIAN_ITERATIVE_METHOD” are set at the compile time. The initial
tip-deflection solution is the same as the one from irreducible formulation, which is expected not to be the final
answer due to the nearly incompressibility under plane strain condition. After several iterations the solution con-
verged and terminated with a energy norm smaller than 10-15 times its initial energy.

Incompressible u-p Formulation


From Eq. 4•216 in Chapter 4, the pressure-volumetric strain constitutive equation for nearly incompressible
material (ν → 0.5) is

p ≡ Kεv = K m • ε Eq. 5•72

A weighted residual statement to Eq. 5•72 is

m • ε – ---- dΩ
p
∫ ( δp ) T K
Eq. 5•73

where δp is the variation of p. From Eq. 4•220 in Chapter 4, we have the element stiffness matrix

k = ∫ ε ( δu ) T σ ( u )dΩ = ∫ ε ( δu )T [ σd ( u ) + mp ( u ) ]dΩ
Ω Ω

∫ ε ( δu ) T µ  D0 – --- m ⊗ m ε ( u ) + mp ( u ) dΩ
2
=
 
Eq. 5•74
3

where the variation of u is denoted as δu. Notice that the equilibrium equation is the equilibrium of internal
forces and external forces. The weak statement of the equilibrium equation gives

∫ ε ( δu ) T µ  D 0 – --- m ⊗ m ε ( u ) + mp ( u ) dΩ =
2
3 ∫ ( δu ) T b dΩ + ∫ ( δu ) T t dΓ Eq. 5•75
Ω Ω Γ

The displacement u and pressure p are approximated as

Workbook of Applications in VectorSpace C++ Library 485


Chapter 5 Advanced Finite Element Methods

u = N u û , and p = N p p̂ Eq. 5•76

Substituting into Eq. 5•75 and Eq. 5•72 gives in matrix form at element level is

A C T û = f 1 Eq. 5•77
C V p̂ f2

where

2
A = ∫ BTµ D 0 – --- m ⊗ m BdΩ
3
Eq. 5•78
Ωe

C = ∫ ( mNp ) T BdΩ Eq. 5•79


Ωe

Np ⊗ Np
V = – ∫ --------------------- dΩ Eq. 5•80
K
Ωe

f1 = ∫ NuT b dΩ + ∫ NuT t dΓ – Au Γ e
– CTp Γe
Eq. 5•81
Ωe Γe

f 2 = – Cu Γe
– Vp Γe
Eq. 5•82

The solution to substructuring of Eq. 5•77 is proceeded from its second equation, using symmetrical negative
definitiveness of V,

p̂ = V – 1 ( f2 – Cû ) Eq. 5•83

Substituting into the first equation of Eq. 5•77 gives

Aû + C T V –1 ( f 2 – Cû ) = f 1 Eq. 5•84

That is,

û = [ A – C T V –1 C ] – 1 [ f1 – C T V –1 f 2 ] Eq. 5•85

Therefore, we first solve û , and then substituting û back to Eq. 5•83 for recovering p̂ . Program Listing 5•6
(project “incompressible_u_p_formulation” in project workspace file “fe.dsw”) implements the Q 4/9 element
(with an additional center u-node) and test problem in the higher-order patch test.

486 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
#include "include\fe.h"
#include "include\omega_h_n.h"
Matrix_Representation_Couple::assembly_switch
Matrix_Representation_Couple::Assembly_Switch = Matrix_Representation_Couple::ALL;
static const int row_node_no = 5; static const int row_segment_no = row_node_no-1;
static const double L_ = 10.0; static const double c_ = 1.0;
static const double h_e_ = L_/((double)row_segment_no); // half element size
static const double E_ = 1.e3; 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_); plane stress modification
static const double K_ = lambda_bar+2.0/3.0*mu_;
Omega_h_i::Omega_h_i(int i) : Omega_h(0) {
if(i == 0) { double v[2]; Node *node; define Ωu
v[0] = 0.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node); 1st row
v[0] = h_e_; node = new Node(1, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(2, 2, v); node_array().add(node);
v[0] = 3.0*h_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);
2nd row
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_; node = new Node(11, 2, v); node_array().add(node); 3rd row
v[0] = 2.0*h_e_; node = new Node(12, 2, v); node_array().add(node);
v[0] = 3.0*h_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);
int ena[9]; Omega_eh *elem; Lagrangian 9-nodes element
ena[0] = 0; ena[1] = 2; ena[2] = 12; ena[3] = 10; ena[4] = 1;
ena[5] = 7; ena[6] = 11; ena[7] = 5; ena[8] = 6;
elem = new Omega_eh(0, 0, 0, 9, ena); omega_eh_array().add(elem);
ena[0] = 2; ena[1] = 4; ena[2] = 14; ena[3] = 12; ena[4] = 3;
ena[5] = 9; ena[6] = 13; ena[7] = 7; ena[8] = 8;
elem = new Omega_eh(1, 0, 0, 9, ena); omega_eh_array().add(elem);
} else if(i == 1) { double inv_sqrt3 = 1.0/sqrt(3.0), v[2], xl[4][2], zai, eta; Node *node;
define Ωp
xl[0][0] = 0.0; xl[0][1] = 0.0; xl[1][0] = 2.0*h_e_; xl[1][1] = 0.0; elem # 0 nodal coordinates
xl[2][0] = 2.0*h_e_; xl[2][1] = 2.0*c_; xl[3][0] = 0.0; xl[3][1] = 2.0*c_;
zai = - inv_sqrt3; eta = - inv_sqrt3;
for(int j = 0; j < 2; j++)
1st Gauss point natural coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+ 1st Gauss point physical coordinates
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(0, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = - inv_sqrt3;
2nd Gauss point natural coordinates
for(int j = 0; j < 2; j++) 2nd Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(1, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = inv_sqrt3; 3rd Gauss point natural coordinates
for(int j = 0; j < 2; j++) 3rd Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(2, 2, v); node_array().add(node);
zai = - inv_sqrt3; eta = inv_sqrt3; 4th Gauss point natural coordinates
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
4th Gauss point physical coordinates
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(3, 2, v); node_array().add(node);

Workbook of Applications in VectorSpace C++ Library 487


Chapter 5 Advanced Finite Element Methods
xl[0][0] = 2.0*h_e_; xl[0][1] = 0.0; xl[1][0] = 4.0*h_e_; xl[1][1] = 0.0; elem # 1 nodal coordinates
xl[2][0] = 4.0*h_e_; xl[2][1] = 2.0*c_; xl[3][0] = 2.0*h_e_; xl[3][1] = 2.0*c_;
zai = - inv_sqrt3; eta = - inv_sqrt3;
1st Gauss point natural coordinates
for(int j = 0; j < 2; j++) 1st Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(4, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = - inv_sqrt3; 2nd Gauss point natural coordinates
for(int j = 0; j < 2; j++) 2nd Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(5, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = inv_sqrt3; 3rd Gauss point natural coordinates
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
3rd Gauss point physical coordinates
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(6, 2, v); node_array().add(node); 4th Gauss point natural coordinates
zai = - inv_sqrt3; eta = inv_sqrt3;
for(int j = 0; j < 2; j++)
4th Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(7, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 0.0; node = new Node(8, 2, v); node_array().add(node);
v[0] = 1.0*h_e_; node = new Node(9, 2, v); node_array().add(node); geometrical nodes
v[0] = 2.0*h_e_; node = new Node(10, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(11, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(12, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 1.0*c_; node = new Node(13, 2, v); node_array().add(node);
v[0] = 1.0*h_e_; node = new Node(14, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(15, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(16, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(17, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 2.0*c_; node = new Node(18, 2, v); node_array().add(node);
v[0] = 1.0*h_e_; node = new Node(19, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(20, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(21, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(22, 2, v); node_array().add(node);
int ena[13]; Omega_eh *elem;
ena[0] = 8; ena[1] = 10; ena[2] = 20; ena[3] = 18; ena[4] = 9; ena[5] = 15;
last four nodes are real pressure-field
ena[6] = 19; ena[7] = 13; ena[8] = 14; ena[9] = 0; ena[10] = 1; ena[11] = 2; ena[12] = 3; nodes
elem = new Omega_eh(0, 0, 0, 13, ena); omega_eh_array().add(elem);
ena[0] = 10; ena[1] = 12; ena[2] = 22; ena[3] = 20; ena[4] = 11; ena[5] = 17;
ena[6] = 21; ena[7] = 15; ena[8] = 16; ena[9] = 4; ena[10] = 5; ena[11] = 6; ena[12] = 7;
elem = new Omega_eh(1, 0, 0, 13, ena); 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() {
gh_on_Gamma_h::__initialization(df, omega_h);
if(i == 0) {
the_gh_array[node_order(4)](0) = the_gh_array[node_order(9)](0) =
the_gh_array[node_order(14)](0)=the_gh_array[node_order(9)](1)=
Dirichlet B.C.
gh_on_Gamma_h::Dirichlet; Nuemann B.C.
the_gh_array[node_order(10)][0] = -5.0; the_gh_array[node_order(0)][0] = 5.0;
} else if(i == 1) {
for(int j = 8; j <= 22; j++) for(int k = 0; k < df; k++) {
disable all geometrical nodes
the_gh_array[node_order(j)](k) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(j)][k] = 0.0;
}
}
}

488 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
static const int u_ndf = 2; static Omega_h_i oh_u(0); Ωu
static gh_on_Gamma_h_i u_gh(0, u_ndf, oh_u); static U_h u_h(u_ndf, oh_u);
static const int p_ndf = 1; static Omega_h_i oh_p(1);
u = [u, v]T
static gh_on_Gamma_h_i p_gh(1, p_ndf, oh_p); static U_h p_h(p_ndf, oh_p); Ωp
static Global_Discretization *u_type = new Global_Discretization;
static Global_Discretization u_gd(oh_u, u_gh, u_h, u_type);
static Global_Discretization *p_type = new Global_Discretization;
static Global_Discretization p_gd(oh_p, p_gh, p_h, p_type);
static Global_Discretization_Couple *p_u_type = new Global_Discretization_Couple; {p-u} global discretization couple
static Global_Discretization_Couple gdc(p_gd, u_gd, p_u_type);
class Incompressible_ElasticQ94_Mixed_Formulation : public Element_Formulation_Couple {
public:
Incompressible_ElasticQ94_Mixed_Formulation(Element_Type_Register a) :
Element_Formulation_Couple(a) {}
Element_Formulation *make(int, Global_Discretization&);
Incompressible_ElasticQ94_Mixed_Formulation(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&);
Incompressible_ElasticQ94_Mixed_Formulation(int, Global_Discretization_Couple&);
};
Element_Formulation*
Incompressible_ElasticQ94_Mixed_Formulation::make(int en, Global_Discretization& gd) { diagonal submatrices
return new Incompressible_ElasticQ94_Mixed_Formulation(en,gd);
}
Incompressible_ElasticQ94_Mixed_Formulation::Incompressible_ElasticQ94_Mixed_Formulation
(int en, Global_Discretization& gd) : Element_Formulation_Couple(en, gd) {
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp), Zai, Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 9, 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;
Lagragian 9-node element
N[8] = (1-Zai.pow(2))*(1-Eta.pow(2));
N[0] -= N[8]/4; N[1] -= N[8]/4; N[2] -= N[8]/4; N[3] -= N[8]/4;
N[4] = ((1-Zai.pow(2))*(1-Eta)-N[8])/2; N[5] = ((1-Eta.pow(2))*(1+Zai)-N[8])/2;
N[6] = ((1-Zai.pow(2))*(1+Eta)-N[8])/2; N[7] = ((1-Eta.pow(2))*(1-Zai)-N[8])/2;
N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2; N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;
C0 x = MATRIX("int, int, C0&, int, int", 9, 2, xl, 0, 0);
H1 X = N*x; J dv(d(X).det());
if(gd.type() == u_type) {
H0 Nx = d(N) * d(X).inverse();
double d_0[3][3] = { {2.0, 0.0, 0.0}, {0.0, 2.0, 0.0}, {0.0, 0.0, 1.0}};
C0 D_0 = MATRIX("int, int, const double*", 3, 3, d_0[0]);
double m_0[3] = {1.0, 1.0, 0.0}; C0 m = VECTOR("int, const double*", 3, m_0);
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), Wx, Wy, B;
Wx &= W_x[0][0]; Wy &= W_x[0][1];
2
∫ BTµ
B &= (~Wx || C0(0.0) ) &
A = D 0 – --- m ⊗ m BdΩ
(C0(0.0) || ~Wy )& 3
(~Wy || ~Wx ); Ωe
stiff &= ((~B) * (mu_*(D_0-2.0/3.0*(m%m)) * B)) | dv;
} else if(gd.type() == p_type) {
double sqrt3 = sqrt(3.0);
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4/*nen*/, qp), zai, eta;
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]);
n[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; n[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0;
n[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; n[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
stiff &= MATRIX("int, int", 13, 13);
C0 stiff_sub = MATRIX("int, int, C0&, int, int", 4, 4, stiff, 9, 9); N p ⊗ Np
stiff_sub = -((n * (~n)) | dv)/K_; V = – ∫ --------------------- dΩ
K
} Ωe
}

Workbook of Applications in VectorSpace C++ Library 489


Chapter 5 Advanced Finite Element Methods
Element_Formulation_Couple* Incompressible_ElasticQ94_Mixed_Formulation::make(
int en, Global_Discretization_Couple& gdc) {
return new Incompressible_ElasticQ94_Mixed_Formulation(en,gdc);
}
Incompressible_ElasticQ94_Mixed_Formulation::Incompressible_ElasticQ94_Mixed_Formulation
(int en, Global_Discretization_Couple& gdc) : Element_Formulation_Couple(en, gdc) {
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp), Zai, Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 9, 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;
N[8] = (1-Zai.pow(2))*(1-Eta.pow(2));
N[0] -= N[8]/4; N[1] -= N[8]/4; N[2] -= N[8]/4; N[3] -= N[8]/4;
N[4] = ((1-Zai.pow(2))*(1-Eta)-N[8])/2; N[5] = ((1-Eta.pow(2))*(1+Zai)-N[8])/2;
N[6] = ((1-Zai.pow(2))*(1+Eta)-N[8])/2; N[7] = ((1-Eta.pow(2))*(1-Zai)-N[8])/2;
N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2; N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;
C0 x = MATRIX("int, int, C0&, int, int", 9, 2, xl, 0, 0);
H1 X = N*x; H0 Nx = d(N) * d(X).inverse(); J dv(d(X).det());
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2, Nx), wx, wy, B;
wx &= w_x[0][0]; wy &= w_x[0][1];
C0 zero(0.0);
B &= (~wx || zero) & (zero || ~wy ) & (~wy || ~wx );
double sqrt3 = sqrt(3.0);
H0 Zero(qp); Zero = 0.0;
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4/*nen*/, qp), n9, n10, n11, n12, zai, eta;
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]);
n[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; n[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0;
n[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; n[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
n9 &= n[0]; n10 &= n[1]; n11 &= n[2]; n12 &= n[3];
H0 mN_p = ((n9 |n10 |n11 |n12 ) & (n9 |n10 |n11 |n12 ) & (Zero |Zero |Zero |Zero ) );
stiff &= MATRIX("int, int", 13, 18);
C0 stiff_sub = MATRIX("int, int, C0&, int, int", 4, 18, stiff, 9, 0);
C = ∫ ( mNp )T BdΩ
Ωe
stiff_sub = ((~mN_p)*B) | dv;
}
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static Incompressible_ElasticQ94_Mixed_Formulation
incompressible_elasticq94_mixed_formulation_instance(element_type_register_instance);
static Matrix_Representation mr_u(u_gd); static Matrix_Representation mr_p(p_gd);
static Matrix_Representation_Couple mrc(gdc, 0, &(mr_p.rhs()), &(mr_u.rhs()) , &mr_u);
int main() {
mr_u.assembly(); mr_p.assembly(); mrc.assembly();
C0 A = ((C0)(mr_u.lhs())), f_1 = ((C0)(mr_u.rhs())), C = ((C0)(mrc.lhs())),
V = ((C0)(mr_p.lhs())), f_2 = ((C0)(mr_p.rhs()));
Cholesky dnV(-V);
C0 V_inv = -(dnV.inverse()), CtV_invC = (~C)*(V_inv*C),
K = A-CtV_invC, f = f_1-(~C)*(-(dnV*f_2)); û = [ A – C T V –1 C ] –1 [ f 1 – C T V –1 f 2 ]
QR dK(K); C0 u = dK*f;
u_h = u; u_h = u_gd.gh_on_gamma_h();
cout << "displacement(u-x, u-y):" << endl << u_h << endl;
C0 p = -(dnV*(f_2-C*u)); p̂ = V –1 ( f 2 – Cû )
p_h = p; p_h = p_gd.gh_on_gamma_h();
cout << "pressure:" << endl << p_h << endl;
return 0;
}

Listing 5•6 Incompressible u-p formulation for plane elasticity (project :


“incompressible_u_p_formulation” in project workspace file “fe.dsw”).

490 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
Incompressible Formulation: Eq. 5•77 is for the nearly incompressible formulation ( ν → 0.5). For ν = 0.5 in Eq.
5•80, V = 0, because K = ∞. Therefore, we have, for incompressible formulation, 1

A C T û = f 1 Eq. 5•86
C 0 p̂ f2

Due to the zero diagonals, the solution procedure is different from the nearly incompressible case; i.e., the inver-
sion of V in Eq. 5•83 to Eq. 5•85 is not permissible now. From first equation of Eq. 5•86, we have

Aû + C T p̂ = f1 ⇒ û = A –1 [ f1 – C T p̂ ] Eq. 5•87

Substituting into second equation of Eq. 5•86

CA –1 [ f 1 – C T p̂ ] = f2 ⇒ p̂ = [ CA –1 C T ] – 1 [ CA –1 f 1 – f2 ] Eq. 5•88

After p̂ is solved by the second part of Eq. 5•88, û can be recovered from second part of Eq. 5•87. The tip-
deflection is “-0.5625”. This incompressible formulation is computed with macro definition “__TEST_INCOM
PRESSIBLE_PLANE_STRAIN” set at the compile time for the same project
“incompressible_u_p_formulation” in project workspace file “fe.dsw”.

Displacement-Only Mixed Formulation: If pressure field is taken as discontinuous field, the pressure can be elim-
inated at the element level for both the nearly incompressible and incompressible cases, since no element shares
pressure node with the other element, and therefore no inter-element dependency. For example, in the nearly
incompressible case Eq. 5•85 can be written as

k e û e = f e Eq. 5•89

where the re-defined element stiffness matrix element force vector are

k e ≡ a e – ( c e ) T ( v e ) –1 c e, and f e = f 1e – ( c e ) T ( v e ) –1 f 2e Eq. 5•90

The Program Listing 5•7 implements the present u-only simplified mixed formulation (project “mixed_u_only”
in project workspace file “fe.dsw”). The element stiffness matrix of Q 4/9 mixed formulation is equivalent to that
of the selective reduced integration Lagrangian 9-node element (in page 406 of Chapter 4 when “ElasticQ9” ele-
ment with selective reduced integration is used. see Figure 5•3). This is known as the equivalence theorem in a
more general context.2 Notice that due to the ill-condition number in the nearly incompressible plain strain case,
the full-scale mixed solution and the displacement-only solution may differ numerically.

1. see p. 202-203 in 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. 221-223 in Hughes, T.J.R., 1987, “The finite element method: linear static and dynamic finite element analysis”,
Prentice-Hall, Inc., Englewood Cliffs, New Jersey.

Workbook of Applications in VectorSpace C++ Library 491


Chapter 5 Advanced Finite Element Methods

selective reduced integration


for standard irreducible formulation mixed formulation Q4/9

volumetric stiffness deviatoric stiffness


Gauss point locations u nodes
u nodes p nodes

Figure 5•3 The equivalence of the selective reduced integration and the mixed formulation.

492 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#include "include\fe.h"
static const double E_ = 1.e3;
static const double v_ = 0.5-1.e-12;
static const double K_ = E_/3.0/(1-2.0*v_);
static const double a_ = E_*(1-v_)/(1+v_)/(1-2*v_);
static const double Dv[3][3]= {{a_, a_*v_/(1-v_), 0.0 },
{a_*v_/(1-v_), a_, 0.0 },
{0.0, 0.0, a_*(1-2*v_)/2.0/(1-v_)} };
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
static const double lambda_ = v_*E_/((1+v_)*(1-2*v_));
static const double mu_ = E_/(2*(1+v_));
EP::element_pattern EP::ep = EP::LAGRANGIAN_9_NODES;
Omega_h::Omega_h() {
int control_node_flag[4] = {1, 1, 1, 1};
double x[4][2] = {{0.0, 0.0}, {10.0, 0.0}, {10.0, 2.0}, {0.0, 2.0}};
block(this, 3, 5, 4, control_node_flag, x[0]);
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h);
double f_ = 15.0;
for(int i = 0; i < row_node_no; i++) {
the_gh_array[node_order((i+1)*5-1)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order((i+1)*5-1)][0] = 0.0;
the_gh_array[node_order(5)](1)= gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(5)](1)= 0.0;
double h_ = 1.0;
the_gh_array[node_order(10)](0) = gh_on_Gamma_h::Nuemann;
the_gh_array[node_order(10)][0] = f_*(1.0/3.0)*h_;
the_gh_array[node_order(0)](0)= gh_on_Gamma_h::Nuemann;
the_gh_array[node_order(0)][0] = -f_*(1.0/3.0)*h_;

}
class ElasticQ9 : public Element_Formulation {
public:
ElasticQ9(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
ElasticQ9(int, Global_Discretization&);
};
Element_Formulation* ElasticQ9::make(int en, Global_Discretization& gd) {
return new ElasticQ9(en,gd);
}
ElasticQ9::ElasticQ9(int en, Global_Discretization& gd) : Element_Formulation(en, gd) {
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 9, 2, qp),
Zai, Eta;
9-nodes Lagrangian shape functions
Zai &= Z[0]; Eta &= Z[1];
N[0]=(1-Zai)*(1-Eta)/4;
N[1]=(1+Zai)*(1-Eta)/4; Step1: initial four corner nodes
N[2]=(1+Zai)*(1+Eta)/4;
N[3] =(1-Zai)*(1+Eta)/4;
N[8] = (1-Zai.pow(2))*(1-Eta.pow(2)); Step 2: add center nodes
N[0] -= N[8]/4; N[1] -= N[8]/4; Step 3:modification of four corner
N[2] -= N[8]/4; N[3] -= N[8]/4;
nodes
N[4] = ((1-Zai.pow(2))*(1-Eta)-N[8])/2;
N[5] = ((1-Eta.pow(2))*(1+Zai)-N[8])/2; due to the presence of the center node
N[6] = ((1-Zai.pow(2))*(1+Eta)-N[8])/2; Step 4:add four edge nodes and correct
N[7] = ((1-Eta.pow(2))*(1-Zai)-N[8])/2;
for the presence of the center node

Workbook of Applications in VectorSpace C++ Library 493


Chapter 5 Advanced Finite Element Methods
N[0] -= (N[4]+N[7])/2; Step 5: modification of the four corner
N[1] -= (N[4]+N[5])/2; nodes due to the presence of four
N[2] -= (N[5]+N[6])/2;
N[3] -= (N[6]+N[7])/2; edge nodes
H1 X = N*xl;
H0 Nx = d(N) * d(X).inverse();
J dV(d(X).det());
double d_0[3][3] = { {2.0, 0.0, 0.0},
{0.0, 2.0, 0.0},
{0.0, 0.0, 1.0}};
C0 D_0 = MATRIX("int, int, const double*", 3, 3, d_0[0]);
double m_0[3] = {1.0, 1.0, 0.0};
C0 m = VECTOR("int, const double*", 3, m_0);
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx),
Wx, Wy, B;
Wx &= W_x[0][0]; Wy &= W_x[0][1];
double sqrt3 = sqrt(3.0);
B &= (~Wx || C0(0.0)) &
(C0(0.0) || ~Wy ) &
(~Wy || ~Wx ); 2
C0 a = ((~B) * (mu_*(D_0-2.0/3.0*(m%m)) * B)) | dV;
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4/*nen*/, qp),
ae = ∫ BT µ D 0 – --- m ⊗ m BdΩ
3
Ωe
zai, eta;
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]);
n[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; shape functions with four nodes
n[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0; at Gauss-points
n[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
n[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
Np ⊗ Np
C0 v = -((n * (~n)) | dV)/K_;
v e = – ∫ --------------------- dΩ
Cholesky dnv(-v); K
C0 v_inv = -(dnv.inverse()); Ωe
H0 Zero(qp);
Zero = 0.0;
H0 mN_p = ((n[0] | n[1] | n[2] | n[3] ) &

∫ ( mNp ) T BdΩ
(n[0] | n[1] | n[2] | n[3] ) &
(Zero | Zero | Zero | Zero ) ); ce =
C0 c = ((~mN_p)*B) | dV; Ωe
stiff &= a-(~c)*v_inv*c;
}
Element_Formulation* Element_Formulation::type_list = 0; ke ≡ a e – ( c e ) T ( v e ) –1 c e
Element_Type_Register element_type_register_instance;
static ElasticQ9 elasticq9_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() << endl;
return 0;
}

Listing 5•7 Displacement only mixed formulation with discontinuous pressure field (project:
“mixed_u_only” in project workspace file “fe.dsw”).

494 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
Incompressible u-p-εv Formulation
The volumetric strain-displacement relation Eq. 4•215 in Chapter 4 is

εv = m T ε = m T Lu Eq. 5•91

where ε = [εx, εy, γxy]T, m = [1, 1, 0]T, and


------ 0
∂x
L = 0 ----- ∂ Eq. 5•92
-
∂y
∂ ∂
------ ------
∂y ∂x

The pressure-volumetric strain constitutive equation is defined in Eq. 5•72 as

Kε v – p = 0 Eq. 5•93

Weighted residual statements can be obtained by applying pressure variation δp to Eq. 5•91 and volumetric strain
variation δεv to Eq. 5•93 together with the weak statement (displacement variation δu) to the equilibrium equa-
tion (Eq. 5•75) we have

∫ ε ( δu ) T µ  D 0 – --- m ⊗ m ε ( δu ) + mp ( u ) dΩ =
2
 3  ∫ δu T b dΩ + ∫ δuT t dΓ Eq. 5•94
Ω Ω Γ

∫ δp [ εv – m T Lu ] dΩ = 0 Eq. 5•95

∫ δεv [ Kεv – p ] dΩ = 0 Eq. 5•96


The volumetric strain field is approximated as

ε v = N v ε̂v Eq. 5•97

Eq. 5•94 to Eq. 5•96 can be rewritten in matrix form as

Workbook of Applications in VectorSpace C++ Library 495


Chapter 5 Advanced Finite Element Methods

A CT 0 û f1
C 0 E T p̂ = f 2 Eq. 5•98
0 E H ε̂v f3

where

2
A = ∫ BT µ D 0 – --- m ⊗ m BdΩ
3
Eq. 5•99
Ωe

C = ∫ ( mNp )T BdΩ Eq. 5•100


Ωe

E = – ∫ [ N v ⊗ N p ]dΩ Eq. 5•101


Ωe

H = ∫ K [ Nv ⊗ Nv ]dΩ Eq. 5•102


Ωe

f1 = ∫ NuT b dΩ + ∫ NuT t dΓ – Au Γ e
– CTp Γe
Eq. 5•103
Ωe Γe

f 2 = – Cu Γe
–E T εv Eq. 5•104
Γe

f3 = – Ep Γe
– Hε v Eq. 5•105
Γe

In the process of solution, E-matrix will need to be invertible. If we take Np = Nv = Ψe(x), “-E” becomes

–E = ∫ [ Ψe ⊗ Ψe ]dΩ Eq. 5•106


Ωe

which is symmetrical positive definitive and the inversion of “-E” can be computed by Cholesky decomposition.
With such choice of Np = Nv, from the third equation of Eq. 5•98, we have

ε̂ v = H –1 ( f 3 + Ep̂ ) Eq. 5•107

Substituting into second equation of Eq. 5•98

496 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
Cû – E T H –1 ( f 3 + Ep̂ ) = f 2 ⇒ p̂ = ( E T H –1 E ) –1 ( Cû – E T H –1 f3 – f 2 ) Eq. 5•108

Substituting p̂ into the first equation

Aû + C T ( E T H –1 E ) –1 ( Cû – E T H –1 f3 – f 2 ) = f1 Eq. 5•109

That is

û = [ A + C T ( E T H –1 E ) –1 C ] – 1 [ f1 + C T ( E T H –1 E ) –1 ( E T H –1 f3 – f 2 ) ] Eq. 5•110

After we obtain û , p̂ can be recovered from second part of Eq. 5•108. Then ε̂v is computed directly from second
equation of Eq. 5•98. The Program Listing 5•8 implements the u-p-εv three-field incompressible formulation
(project “incompressible_u_p_epsilon_v_formulation” in project workspace file “fe.dsw”).
If Ψe(x) is taken as discontinuous fields, the variables p and εv can be eliminated at the element level and f3 =
0 because no boundary conditions can be imposed for p and εv. Then, we withheld the subtraction of essential
displacement boundary conditions from the second equation in Eq. 5•98, therefore, f2 = 0. From the second equa-
tion we have

ε̂ v = – ( E T ) –1 Cû = Wû, where W ≡ – ( E T ) – 1 C Eq. 5•111

From third equation in Eq. 5•98 we have

p̂ = – E –1 Hε̂ v = E –1 H ( E T ) – 1 Cû Eq. 5•112

Substituting Eq. 5•112 into Eq. 5•98

Aû + C T E –1 H ( E T ) –1 Cû = f 1 Eq. 5•113

or

[ A + C T E –1 H ( E T ) –1 C ]û = Aû = f1 Eq. 5•114

where

A ≡ A + C T E – 1 H ( E T ) –1 C = A + W T HW Eq. 5•115

Therefore a modified stiffness matrix A, based on the Hu-Washizu variational principle can be used to compute
the displacement field directly. The implementation of the A solution procedure is

Workbook of Applications in VectorSpace C++ Library 497


Chapter 5 Advanced Finite Element Methods

#include "include\fe.h"
#include "include\omega_h_n.h"
Matrix_Representation_Couple::assembly_switch
Matrix_Representation_Couple::Assembly_Switch = Matrix_Representation_Couple::ALL;
static const int row_node_no = 5; static const int row_segment_no = row_node_no-1;
static const double L_ = 10.0; static const double c_ = 1.0; half element size
static const double h_e_ = L_/((double)row_segment_no);
static const double E_ = 1.e3; 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_)); plane stress modification
static const double lambda_bar = 2*lambda_*mu_/(lambda_+2*mu_);
static const double K_ = lambda_bar+2.0/3.0*mu_;
Omega_h_i::Omega_h_i(int i) : Omega_h(0) { define Ωu
if(i == 0) { double v[2]; Node *node; 1st row
v[0] = 0.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node);
v[0] = h_e_; node = new Node(1, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(2, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(3, 2, v); node_array().add(node); 2nd row
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] = 2.0*h_e_; node = new Node(6, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(7, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 2.0*c_; node = new Node(8, 2, v); node_array().add(node);
3rd row
v[0] = h_e_; node = new Node(9, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(10, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(11, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(12, 2, v); node_array().add(node);
Serendipity 8-nodes element
int ena[8]; Omega_eh *elem;
ena[0] = 0; ena[1] = 2; ena[2] = 10; ena[3] = 8; ena[4] = 1; ena[5] = 6; ena[6] = 9; ena[7] = 5;
elem = new Omega_eh(0, 0, 0, 8, ena); omega_eh_array().add(elem);
ena[0] = 2; ena[1] = 4; ena[2] = 12; ena[3] = 10; ena[4] = 3; ena[5] = 7; ena[6] = 11; ena[7] = 6; define Ωp and Ωε
elem = new Omega_eh(1, 0, 0, 8, ena); omega_eh_array().add(elem); v
} else if(i == 1 || i == 2) { double inv_sqrt3 = 1.0/sqrt(3.0), v[2], xl[4][2], zai, eta; Node *node; elem # 0 nodal coordinates
xl[0][0] = 0.0; xl[0][1] = 0.0; xl[1][0] = 2.0*h_e_; xl[1][1] = 0.0;
xl[2][0] = 2.0*h_e_; xl[2][1] = 2.0*c_; xl[3][0] = 0.0; xl[3][1] = 2.0*c_;
1st Gauss point natural coordinates
zai = - inv_sqrt3; eta = - inv_sqrt3;
for(int j = 0; j < 2; j++) 1st Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
2nd Gauss point natural coordinates
node = new Node(0, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = - inv_sqrt3; 2nd Gauss point physical coordinates
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(1, 2, v); node_array().add(node); 3rd Gauss point natural coordinates
zai = inv_sqrt3; eta = inv_sqrt3; 3rd Gauss point physical coordinates
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(2, 2, v); node_array().add(node); 4th Gauss point natural coordinates
zai = - inv_sqrt3; eta = inv_sqrt3;
4th Gauss point physical coordinates
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(3, 2, v); node_array().add(node);
elem # 1 nodal coordinates
xl[0][0] = 2.0*h_e_; xl[0][1] = 0.0; xl[1][0] = 4.0*h_e_; xl[1][1] = 0.0;
xl[2][0] = 4.0*h_e_; xl[2][1] = 2.0*c_; xl[3][0] = 2.0*h_e_; xl[3][1] = 2.0*c_; 1st Gauss point natural coordinates
zai = - inv_sqrt3; eta = - inv_sqrt3;

498 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
for(int j = 0; j < 2; j++) 1st Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(4, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = - inv_sqrt3; 2nd Gauss point natural coordinates
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
2nd Gauss point physical coordinates
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(5, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = inv_sqrt3;
for(int j = 0; j < 2; j++)
3rd Gauss point natural coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+ 3rd Gauss point physical coordinates
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(6, 2, v); node_array().add(node);
zai = - inv_sqrt3; eta = inv_sqrt3;
4th Gauss point natural coordinates
for(int j = 0; j < 2; j++) 4th Gauss point physical coordinates
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(7, 2, v); node_array().add(node);
geometrical nodes
v[0] = 0.0; v[1] = 0.0; node = new Node(8, 2, v); node_array().add(node);
v[0] = 1.0*h_e_; node = new Node(9, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(10, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(11, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(12, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 1.0*c_; node = new Node(13, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(14, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(15, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 2.0*c_; node = new Node(16, 2, v); node_array().add(node);
v[0] = 1.0*h_e_; node = new Node(17, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(18, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(19, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(20, 2, v); node_array().add(node);
int ena[12]; Omega_eh *elem; last four nodes are real pressure-field
ena[0] = 8; ena[1] = 10; ena[2] = 18; ena[3] = 16; ena[4] = 9; ena[5] = 14;
ena[6] = 17; ena[7] = 13; ena[8] = 0; ena[9] = 1; ena[10] = 2; ena[11] = 3;
nodes
elem = new Omega_eh(0, 0, 0, 12, ena); omega_eh_array().add(elem);
ena[0] = 10; ena[1] = 12; ena[2] = 20; ena[3] = 18; ena[4] = 11; ena[5] = 15;
ena[6] = 19; ena[7] = 14; ena[8] = 4; ena[9] = 5; ena[10] = 6; ena[11] = 7;
elem = new Omega_eh(1, 0, 0, 12, ena); 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() {
gh_on_Gamma_h::__initialization(df, omega_h);
if(i == 0) { the_gh_array[node_order(4)](0) = the_gh_array[node_order(7)](0) =
the_gh_array[node_order(12)](0) = the_gh_array[node_order(7)](1) =
gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(8)][0] = -5.0; the_gh_array[node_order(0)][0] = 5.0;
Dirichlet B.C.
} else if(i == 1 || i == 2) { Nuemann B.C.
for(int j = 8; j <= 20; j++) for(int k = 0; k < df; k++) {
the_gh_array[node_order(j)](k) = gh_on_Gamma_h::Dirichlet;
}
disable all geometrical nodes
}
}
static const int u_ndf = 2; static Omega_h_i oh_u(0);
static gh_on_Gamma_h_i u_gh(0, u_ndf, oh_u); static U_h u_h(u_ndf, oh_u);
Ωu u = [u, v]T
static const int p_ndf = 1; static Omega_h_i oh_p(1); Ωp , p
static gh_on_Gamma_h_i p_gh(1, p_ndf, oh_p); static U_h p_h(p_ndf, oh_p);
static const int epsilon_v_ndf = 1; static Omega_h_i oh_epsilon_v(2);
static gh_on_Gamma_h_i epsilon_v_gh(2, epsilon_v_ndf, oh_epsilon_v);
Ωε , ε v
v
static U_h epsilon_v_h(epsilon_v_ndf, oh_epsilon_v);

Workbook of Applications in VectorSpace C++ Library 499


Chapter 5 Advanced Finite Element Methods
static Global_Discretization *u_type = new Global_Discretization; Ωu
static Global_Discretization u_gd(oh_u, u_gh, u_h, u_type);
static Global_Discretization *p_type = new Global_Discretization;
u = [u, v]T
static Global_Discretization p_gd(oh_p, p_gh, p_h, p_type); Ωp
static Global_Discretization *epsilon_v_type = new Global_Discretization;
static Global_Discretization
epsilon_v_gd(oh_epsilon_v, epsilon_v_gh, epsilon_v_h, epsilon_v_type);
static Global_Discretization_Couple *p_u = new Global_Discretization_Couple(); {p-u} global discretization couple
static Global_Discretization_Couple gdc_p_u(p_gd, u_gd, p_u); {εv-p} global discretization couple
static Global_Discretization_Couple *epsilon_v_p = new Global_Discretization_Couple();
static Global_Discretization_Couple gdc_epsilon_v_p(epsilon_v_gd, p_gd, epsilon_v_p);
class Incompressible_3_field_ElasticQ84_Mixed_Formulation :
public Element_Formulation_Couple {
public:
Incompressible_3_field_ElasticQ84_Mixed_Formulation(Element_Type_Register a) :
Element_Formulation_Couple(a) {}
Element_Formulation *make(int, Global_Discretization&);
Incompressible_3_field_ElasticQ84_Mixed_Formulation(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&);
Incompressible_3_field_ElasticQ84_Mixed_Formulation(
int, Global_Discretization_Couple&);
};
Element_Formulation* Incompressible_3_field_ElasticQ84_Mixed_Formulation::make(
int en, Global_Discretization& gd) { diagonal submatrices
return new Incompressible_3_field_ElasticQ84_Mixed_Formulation(en,gd); }
static const double d0[3][3] = { {mu_*(2.0-2.0/3.0), mu_*-2.0/3.0, 0.0},
{mu_*-2.0/3.0, mu_*(2.0-2.0/3.0), 0.0},
{0.0, 0.0, mu_} };
C0 D_dev = MATRIX("int, int, const double*", 3, 3, d0[0]);
Incompressible_3_field_ElasticQ84_Mixed_Formulation::
Incompressible_3_field_ElasticQ84_Mixed_Formulation(
int en, Global_Discretization& gd) : Element_Formulation_Couple(en, gd) {
Quadrature qp(2, 9); Lagragian 9-nodes element
H1 Z(2, (double*)0, qp), , Zai, Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature",8,2,qp);
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
N[4] = (1.0-Zai.pow(2))*(1.0-Eta)/2.0; N[5] = (1.0-Eta.pow(2))*(1.0+Zai)/2.0;
N[6] = (1.0-Zai.pow(2))*(1.0+Eta)/2.0; N[7] = (1.0-Eta.pow(2))*(1.0-Zai)/2.0;
N[0] = N[0] - (N[4]+N[7])/2.0; N[1] = N[1] - (N[4]+N[5])/2.0;
N[2] = N[2] - (N[5]+N[6])/2.0; N[3] = N[3] - (N[6]+N[7])/2.0;
if(gd.type() == u_type) {
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dv(d(X).det());
2
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2/*nsd*/, Nx), wx, wy, B;
wx &= w_x[0][0]; wy &= w_x[0][1]; C0 zero(0.0);
A = ∫ BTµ D 0 – --- m ⊗ m BdΩ
3
Ωe
B &= (~wx || zero) & (zero || ~wy ) & (~wy || ~wx );
stiff &= ((~B)*D_dev*B)|dv;
} else {
C0 x = MATRIX("int, int, C0&, int, int", 8, 2, xl, 0, 0);
H1 X = N*x; J dv(d(X).det()); double sqrt3 = sqrt(3.0);
H0 N_v = INTEGRABLE_VECTOR("int, Quadrature", 4/*nen*/, qp), zai, eta;
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]);
N_v[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; N_v[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0;
N_v[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; N_v[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
stiff &= MATRIX("int, int", 12, 12);
C0 stiff_sub = MATRIX("int, int, C0&, int, int", 4, 4, stiff, 8, 8);
stiff_sub = ((N_v*(~N_v))|dv)*K_;
H = ∫ K [ Nv ⊗ Nv ]dΩ
Ωe
}
}

500 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
Element_Formulation_Couple* Incompressible_3_field_ElasticQ84_Mixed_Formulation::
make(int en, Global_Discretization_Couple& gdc) {
return new Incompressible_3_field_ElasticQ84_Mixed_Formulation(en,gdc); }
Incompressible_3_field_ElasticQ84_Mixed_Formulation::Incompressible_3_field_ElasticQ84_Mi
xed_Formulation(int en, Global_Discretization_Couple& gdc) :
Element_Formulation_Couple(en, gdc) { Quadrature qp(2, 9); H1 Z(2, (double*)0, qp),Zai,Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 8, 2, qp);
Zai &= Z[0]; Eta &= Z[1]; N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
N[4] = (1.0-Zai.pow(2))*(1.0-Eta)/2.0; N[5] = (1.0-Eta.pow(2))*(1.0+Zai)/2.0;
N[6] = (1.0-Zai.pow(2))*(1.0+Eta)/2.0; N[7] = (1.0-Eta.pow(2))*(1.0-Zai)/2.0;
N[0] = N[0] - (N[4]+N[7])/2.0; N[1] = N[1] - (N[4]+N[5])/2.0;
N[2] = N[2] - (N[5]+N[6])/2.0; N[3] = N[3] - (N[6]+N[7])/2.0;
C0 x = MATRIX("int, int, C0&, int, int", 8, 2, xl, 0, 0); H1 X = N*x; J dv(d(X).det());
H0 N_p = INTEGRABLE_VECTOR("int, Quadrature", 4/*nen*/, qp), Zero(qp); Zero = 0.0;
H0 zai, eta; double sqrt3 = sqrt(3.0); zai &= ((H0)Z[0]); eta &= ((H0)Z[1]);
N_p[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; N_p[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0;
N_p[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; N_p[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
if(gdc.type() == p_u) { H0 Nx = d(N) * d(X).inverse(); C0 zero(0.0);
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2/*nsd*/, Nx), wx, wy, B;
wx &= w_x[0][0]; wy &= w_x[0][1]; B &= (~wx || zero) & (zero || ~wy ) & (~wy || ~wx );
C = ∫ ( mNp ) T BdΩ
Ωe
H0 mN_p = (~N_p) & (~N_p) & (Zero | Zero | Zero | Zero);
stiff &= MATRIX("int, int", 12, 16);
C0 stiff_sub=MATRIX("int, int, C0&, int, int", 4, 16, stiff, 8, 0); stiff_sub = ((~mN_p)*B) | dv;
} else { H0 N_epsilon_v; N_epsilon_v &= N_p; stiff &= MATRIX("int, int", 12, 12); E = – ∫ [ N v ⊗ N p ]dΩ
C0 stiff_sub = MATRIX("int, int, C0&, int, int", 4, 4, stiff, 8, 8);
stiff_sub = (N_epsilon_v*(~N_p)) | dv; Ωe
}
}
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static Incompressible_3_field_ElasticQ84_Mixed_Formulation
incompressible_3_field_elasticq84_mixed_formulation_instance(element_type_register_instance);
static Matrix_Representation mr_u(u_gd);
static Matrix_Representation mr_epsilon_v(epsilon_v_gd);
static Matrix_Representation_Couple mrc_p_u(gdc_p_u, 0, 0, &(mr_u.rhs()), &mr_u);
static Matrix_Representation_Couple
mrc_epsilon_v_p(gdc_epsilon_v_p, 0, &(mr_epsilon_v.rhs()), &(mrc_p_u.rhs()) , &mrc_p_u);
int main() {
mr_u.assembly(); mr_epsilon_v.assembly();
mrc_p_u.assembly(); mrc_epsilon_v_p.assembly();
C0 A = ((C0)(mr_u.lhs())), H = ((C0)(mr_epsilon_v.lhs())),
C = ((C0)(mrc_p_u.lhs())), E = ((C0)(mrc_epsilon_v_p.lhs())), û = [ A + C T ( E T H –1 E ) –1 C ] –1
f_1 = ((C0)(mr_u.rhs())), f_2 = ((C0)(mrc_p_u.rhs())), f_3 = ((C0)(mr_epsilon_v.rhs()));
Cholesky dH(H); C0 Hinv = dH.inverse(), EtHinv = (~E)*Hinv,EtHinvE = EtHinv*E; [ f 1 + C T ( E T H – 1 E ) –1 ( E T H –1 f 3 – f2 ) ]
Cholesky dEtHinvE(EtHinvE);
C0 EtHinvE_inv = dEtHinvE.inverse(), ApCtEtHinvEC = A + (~C)*EtHinvE_inv*C;
Cholesky dApCtEtHinvEC(ApCtEtHinvEC);
C0 u = dApCtEtHinvEC * (f_1 + (~C)*(dEtHinvE*(EtHinv*f_3 + f_2))); p̂ = ( E T H –1 E ) – 1 ( Cû – E T H – 1 f 3 – f 2 )
Cholesky dE(E); C0 epsilon_v = dE*(C*u-f_2), p = dE*(H*epsilon_v-f_3);
u_h = u; u_h = u_gd.gh_on_gamma_h(); cout << "displacement(u-x, u-y):" << endl << u_h;
p_h = p; p_h = p_gd.gh_on_gamma_h(); cout << "pressure:" << endl << p_h; ε̂ v = E –1 ( Cû – f 2 )
epsilon_v_h = epsilon_v; epsilon_v_h = epsilon_v_gd.gh_on_gamma_h();
cout << "volumetric strain:" << endl << epsilon_v_h;
return 0;
}

Listing 5•8 Incompressible u-p-εv formulation for plane elasticity (project:


“incompressible_u_p_epsilon_v_formulation” in project workspace file “fe.dsw”).

Workbook of Applications in VectorSpace C++ Library 501


Chapter 5 Advanced Finite Element Methods
1 Cholesky dE(E);
2 C0 Einv = dE.inverse(),
// W ≡ –( E ) C
T –1
3 W = - Einv*C,
4 A_bar = A+(~W)*H*W; // A = A + W T HW
5 Cholesky dA_bar(A_bar); –1
6 C0 u = dA_bar * f_1, // û = ( A ) f 1
7 epsilon_v = W*u; // ε̂v = Wû
8 p = dE*(H*epsilon_v); // p̂ = –E –1 Hε̂
v

This computation can be invoked by setting macro definition “__TEST_A_BAR_FORMULATION” at the com-
pile time for the project “incompressible_u_p_epsilon_v_formulation” in project workspace file “fe.dsw”.

B Method
Mixed formulation leads to complicated matrix substructuring problem. With the support of “fe.lib” it does
not seems to be too difficult. However, most finite element programs do not have the capability to deal with the
matrix substructuring problem. Engineering simplification need to be made, particularly if such simplification
leads to highly efficient programs. For example, the A formulation in the above simplifies the program to be
compatible with the standard irreducible formulation, if only the displacement field is to be solved. Furthermore,
we may wish to re-define, in place of the strain-displacement matrix (B-matrix), the B-matrix such that A is
expressed in the form conformable to the standard irreducible formulation1

∫B
T
A≡ DB dΩ Eq. 5•116
Ωe

From Eq. 4•220 in Chapter 4, the standard form of D is

2 00
D ≡ µ  D 0 – --- m ⊗ m + K ( m ⊗ m ) where D 0 = 0 2 0
2
Eq. 5•117
3
0 01

This expression can be easily implemented with the aid of VectorSpace C++ Library. A more graphic expression
suitable for a lower-level implementation is

K +  2 – --- µ
2 2
K – --- µ 0
 3 3
D≡ Eq. 5•118
K +  2 – --- µ 0
2 2
K – --- µ
3  3
0 0 µ

1. p. 345 in Zienkiewicz, O.C. and R.L. Taylor, 1989, “The finite element method”, 4th ed., vol. 1. McGraw-Hill, Inc., UK.

502 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
By inspecting Eq. 5•116, the modification to the standard form of stiffness definition is restricted only to the B-
matrix and it can be defined as

B ≡ B dev + B vol Eq. 5•119

That is the deviatoric part of B is just the same as that of B, but its volumetric part, Bvol , needs to be consistent
with the u-p-εv mixed formulation. Bvol and Bdev are defined as

1 1 1
Bvol ≡ --- ( m ⊗ m )B = --- m ( m • B ) and B dev ≡ B – B vol = I – --- ( m ⊗ m ) B Eq. 5•120
3 3 3

Now, we make distinction of discrete approximation to the volumetric strain as ε vh ≡ ε v and the infinite dimen-
sional εv in continuum mechanics. The approximation in Eq. 5•97 can be more precisely written (with over-bar
indicates “average” for certain simplest approximation which will become evident later) as

ε v ≡ Ψ e ε̂ v = Ψ e Wû Eq. 5•121

and,

ε v = div ( û ) = m • B û Eq. 5•122

Comparing Eq. 5•121 and Eq. 5•122, we have

m • B = Ψe W Eq. 5•123

From the first part of Eq. 5•120, Bvol can be defined similarly to Bvol as

1 1
Bvol = --- m ( m • B ) = --- mΨ e W Eq. 5•124
3 3

Substituting into Eq. 5•119, we have

1 1
B ≡ Bdev + B vol = I – --- ( m ⊗ m ) B + --- mΨ e W Eq. 5•125
3 3

Eq. 5•125 can look quite formidable, a step-by-step algorithm for the B formula can be given as1

∂N ∂N
B1 = -------, and B2 = ------- Eq. 5•126
∂x ∂y
˜
and define B as

1. see p.233-236 in Hughes, T.J.R., 1987, “The finite element method: linear static and dynamic finite element analysis”,
Prentice-Hall, Inc., Englewood Cliffs, New Jersey.

Workbook of Applications in VectorSpace C++ Library 503


Chapter 5 Advanced Finite Element Methods
˜
B ≡ m • B = Ψe W Eq. 5•127

and

B 5 B6
B = B 4 B7 Eq. 5•128
B 2 B1

where

˜
B1 – B 1
B 4 ≡ ------------------ Eq. 5•129
3

B 5 ≡ B 1 + B4 Eq. 5•130

B2 – B 2
B 6 ≡ ------------------ Eq. 5•131
3

B 7 ≡ B 2 + B6 Eq. 5•132

It is easily verifiable that Eq. 5•126 to Eq. 5•132 is equivalent to Eq. 5•125. The algorithm given above, Eq.
5•128 to Eq. 5•132, involves only simple arithmatics. The Program Listing 5•9 implements the B formulation in
this section. Due to the importance of this formulation, the post-processing to compute reactions, stresses, strains
are also included. The details of the post-processing have been discussed in Chapter 4.
We note by passing that from Eq. 5•124 and for the case of bilinear element with piece-wise constant pres-
sure and volumetric strain; i.e., Ψe(x) = 1, we have

∫ ( mΨe ) ⊗ B dΩ ∫ Bvol dΩ
1 1 Ωe Ωe
B vol = --- mΨ e W = --- mΨ e ------------------------------------------- = ------------------------
- Eq. 5•133
3 3
∫ e e
[ Ψ ⊗ Ψ ]dΩ ∫ dΩ
Ωe Ωe

The last term shows that the Bvol is the “mean dilatation of B” over the element domain Ωe.1 This special case
adds another perspective to our understanding of Eq. 5•119 that the definition of B ≡ Bdev + B vol is through the
modification of its volumetric part Bvol, which is the mean of Bvol over the element domain, according to Eq.
5•133. By inspecting this equation, Bvol can also be interpreted as the least squares smoothing of Bvol over the
element domain. Therefore, B is considered an assumed-strain method as oppose to the assumed-displacement

1. see p. 235 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, and the reference therein by Nagtegaal, Parks and Rice.

504 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#include "include\fe.h"
static const int row_node_no = 5; static const int col_node_no = 3;
static const int row_segment_no = row_node_no-1;
static const double L_ = 10.0; static const double c_ = 1.0;
static const double h_e_ = L_/((double)row_segment_no);
static const double E_ = 1.e3; static const double v_ = 0.5-1.e-12;
static const double lambda_ = v_*E_/((1+v_)*(1-2*v_));
static const double mu_ = E_/(2*(1+v_)); static const double K_ = E_/3.0/(1-2.0*v_);
Omega_h::Omega_h() {
double v[2]; Node *node; define Ωu
v[0] = 0.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node);
1st row
v[0] = h_e_; node = new Node(1, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(2, 2, v); node_array().add(node);
v[0] = 3.0*h_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);
2nd row
v[0] = 0.0; v[1] = 1.0*c_; node = new Node(5, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(6, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(7, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 2.0*c_; node = new Node(8, 2, v); node_array().add(node);
3rd row
v[0] = h_e_; node = new Node(9, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(10, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(11, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(12, 2, v); node_array().add(node);
int ena[8]; Omega_eh *elem;
ena[0] = 0; ena[1] = 2; ena[2] = 10; ena[3] = 8; ena[4] = 1; ena[5] = 6; ena[6] = 9; ena[7] = 5; Serendipity 8-nodes element
elem = new Omega_eh(0, 0, 0, 8, ena); omega_eh_array().add(elem);
ena[0] = 2; ena[1] = 4; ena[2] = 12; ena[3] = 10; ena[4] = 3; ena[5] = 7; ena[6] = 11; ena[7] = 6;
elem = new Omega_eh(1, 0, 0, 8, ena); omega_eh_array().add(elem);
}
gh_on_Gamma_h::gh_on_Gamma_h( int df, Omega_h& omega_h) { boundary conditions
__initialization(df, omega_h);
the_gh_array[node_order(4)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(4)][0] = 0.0;
the_gh_array[node_order(7)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(7)][0] = 0.0;
the_gh_array[node_order(12)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(12)][0] = 0.0;
the_gh_array[node_order(7)](1) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(7)][1] = 0.0;
the_gh_array[node_order(8)](0) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(8)][0] = -5.0;
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(0)][0] = 5.0;
}
static int ndf = 2;
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);
class Elastic_B_bar_Q84 : public Element_Formulation {
public:
Elastic_B_bar_Q84(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Elastic_B_bar_Q84(int, Global_Discretization&);
};
Element_Formulation* Elastic_B_bar_Q84::make(int en, Global_Discretization& gd) {
return new Elastic_B_bar_Q84(en,gd);
}

Workbook of Applications in VectorSpace C++ Library 505


Chapter 5 Advanced Finite Element Methods
#if defined(__TEST_HUGHES)
#if defined(__TEST_PLANE_STRAIN_NEARLY_INCOMPRESSIBLE)
static const double a_ = E_*(1-v_)/(1+v_)/(1-2*v_);
static const double Dv[3][3] =
{ {a_, a_*v_/(1-v_), 0.0 }, {a_*v_/(1-v_), a_, 0.0}, {0.0, 0.0, a_*(1-2*v_)/2.0/(1-v_)} };
#else
static const double a_ = E_ / (1.0-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.0-v_)/2.0} };
#endif
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
#else
static const double Dv[3][3] = { {mu_*(2.0-2.0/3.0)+K_,mu_*-2.0/3.0+K_, 0.0},
{mu_*-2.0/3.0+K_, mu_*(2.0-2.0/3.0)+K_,0.0}, {0.0, 0.0, mu_} };
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
static const double i_dev[3][3]={{(1. - 1. / 3.),-1. / 3., .0},{-1. / 3.,(1. - 1. / 3.), .0}, { .0, .0, 1.}};
C0 I_dev = MATRIX("int, int, const double*", 3, 3, i_dev[0]);
#endif
Elastic_B_bar_Q84::Elastic_B_bar_Q84(int en, Global_Discretization& gd) :
Element_Formulation(en, gd) { Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp), Zai, Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 8, 2, qp),
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0; B matrix:
N[4] = (1.0-Zai.pow(2))*(1.0-Eta)/2.0; N[5] = (1.0-Eta.pow(2))*(1.0+Zai)/2.0;
N[6] = (1.0-Zai.pow(2))*(1.0+Eta)/2.0; N[7] = (1.0-Eta.pow(2))*(1.0-Zai)/2.0;
N[0] = N[0] - (N[4]+N[7])/2.0; N[1] = N[1] - (N[4]+N[5])/2.0;
B 5 B6
N[2] = N[2] - (N[5]+N[6])/2.0; N[3] = N[3] - (N[6]+N[7])/2.0; B = B 4 B7
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dv(d(X).det());
#if defined(__TEST_HUGHES) B 2 B1
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2/*nsd*/, Nx), B1, B2, B;
B1 &= ~(w_x[0][0]); B2 &= ~(w_x[0][1]); C0 zero(0.0);
B &= ( B1 || zero) & (zero || B2 ) & ( B2 || B1 ); ˜
B ≡ Ψe W
double sqrt3 = sqrt(3.0);
H0 N_vp = INTEGRABLE_VECTOR("int, Quadrature", 4/*nen*/, qp), zai, eta;
∂N ∂N
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]); B 1 = -------, and B 2 = -------
N_vp[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; N_vp[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0; ∂x ∂y
N_vp[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; N_vp[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
H0 mN_vp = (~N_vp) & (~N_vp) & (zero | zero | zero | zero); ˜
B1 – B1
C0 C = ((~mN_vp)*B) | dv, E = (N_vp*(~N_vp)) | dv, B 4 ≡ ------------------ , B 5 ≡ B1 + B 4
E_inv = Cholesky(E).inverse(), W = E_inv*C; 3
H0 B_mean = (~N_vp)*W;
H0 B_bar_i = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2, B_mean), B_bar1, B_bar2; ˜
B2 – B2
B_bar1 &= B_bar_i[0][0]; B_bar2 &= B_bar_i[0][1]; H0 B4, B5, B6, B7; B 6 ≡ ------------------ , B 7 ≡ B2 + B 6
B4 &= (B_bar1-B1)/3.0; B5 &= B1+B4; B6 &= (B_bar2-B2)/3.0; B7 &= B2+B6; 3
H0 B_bar = ( B5 || B6) & ( B4 || B7) & ( B2 || B1);
#else operator split:
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2/*nsd*/, Nx), wx, wy, B, B_bar;
1
wx &= w_x[0][0]; wy &= w_x[0][1]; C0 zero(0.0); double sqrt3 = sqrt(3.0); B vol ≡ --- ( m ⊗ m )B
B &= (~wx || zero) & (zero || ~wy ) & (~wy || ~wx );
3
H0 N_vp = INTEGRABLE_VECTOR("int, Quadrature", 4/*nen*/, qp), zai, eta;
1
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]); Bdev ≡ B – B vol = I – --- ( m ⊗ m ) B
N_vp[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; N_vp[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0; 3
N_vp[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; N_vp[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0; 1
H0 mN_vp = (~N_vp) & (~N_vp) & (zero | zero | zero | zero); Bvol = --- mΨ e W
3
C0 C = ((~mN_vp)*B) | dv, E = (N_vp*(~N_vp)) | dv,
E_inv = Cholesky(E).inverse(), W = E_inv*C;
B ≡ B dev + Bvol
B_bar &= I_dev * B + (1.0/3.0) * mN_vp * W;
#endif

506 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
if(Matrix_Representation::Assembly_Switch == Matrix_Representation::REACTION) { Post-processing
stiff &= ((~B_bar)*D*B_bar)|dv;
the_element_nodal_value &= stiff * (ul+gl);
reactions
} else if(Matrix_Representation::Assembly_Switch == Matrix_Representation::STRAIN) {
H0 epsilon_hat = INTEGRABLE_VECTOR("int, Quadrature", 3, qp); strains
epsilon_hat = 0.0;
for(int i = 0; i < nen; i++) epsilon_hat +=
B_bar(i*ndf)*(ul[i*ndf]+gl[i*ndf]) + B_bar(i*ndf+1)*(ul[i*ndf+1]+gl[i*ndf+1]);
int nqp = qp.no_of_quadrature_point();
cout.setf(ios::left,ios::adjustfield);
cout << setw(9) << " elem #, " << setw(14) << "x-coor.," << setw(14) << "y-coor.,"
<< setw(14) << "epsilon-x," << setw(14) << "epsilon-y," << setw(14) << "epsilon-xy"
<< endl;
for(int i = 0; i < nqp; i++) {
cout << setw(9) << en
<< setw(14) << ((H0)X[0]).quadrature_point_value(i)
<< setw(14) << ((H0)X[1]).quadrature_point_value(i)
<< setw(14) << (epsilon_hat[0].quadrature_point_value(i))
<< setw(14) << (epsilon_hat[1].quadrature_point_value(i))
<< setw(14) << (epsilon_hat[2].quadrature_point_value(i)) << endl;
} nodal strains by projection
} else if(Matrix_Representation::Assembly_Switch ==
Matrix_Representation::NODAL_STRAIN) {
int strain_no = (ndf+1)*ndf/2; the_element_nodal_value &= C0(nen*strain_no, (double*)0);
C0 projected_nodal_strain = SUBVECTOR("int, C0&", strain_no, the_element_nodal_value);
H0 epsilon_hat = INTEGRABLE_VECTOR("int, Quadrature", 3, qp);
epsilon_hat = 0.0;
for(int i = 0; i < nen; i++) epsilon_hat +=
B_bar(i*ndf)*(ul[i*ndf]+gl[i*ndf]) + B_bar(i*ndf+1)*(ul[i*ndf+1]+gl[i*ndf+1]);
H0 unit(qp); unit = 1.0;
C0 area = unit | dv,
lumped_mass = VECTOR("int", nen),
sum_of_lumped_mass(0.0);
for(int i = 0; i < nen; i++) {
lumped_mass[i] = (((H0)N[i]).pow(2)) | dv; sum_of_lumped_mass += lumped_mass[i];
}
C0 normalized_factor = area/sum_of_lumped_mass;
for(int i = 0; i < nen; i++) {
lumped_mass[i] *= normalized_factor;
projected_nodal_strain(i) = ( (((H0)N[i])*epsilon_hat) | dv ) / lumped_mass[i];
}
} else if(Matrix_Representation::Assembly_Switch == Matrix_Representation::STRESS) {
H0 sigma_hat = INTEGRABLE_VECTOR("int, Quadrature", 3, qp);
stresses
sigma_hat = 0.0;
H0 DB_bar = D*B_bar;
for(int i = 0; i < nen; i++) sigma_hat +=
DB_bar(i*ndf)*(ul[i*ndf]+gl[i*ndf]) + DB_bar(i*ndf+1)*(ul[i*ndf+1]+gl[i*ndf+1]);
int nqp = qp.no_of_quadrature_point();
cout.setf(ios::left,ios::adjustfield);
cout << setw(9) << " elem #, " << setw(14) << "x-coor.," << setw(14) << "y-coor.,"
<< setw(14) << "sigma-x," << setw(14) << "sigma-y," << setw(14) << "sigma-xy"
<< endl;
for(int i = 0; i < nqp; i++) {
cout << setw(9) << en
<< setw(14) << ((H0)X[0]).quadrature_point_value(i)
<< setw(14) << ((H0)X[1]).quadrature_point_value(i)
<< setw(14) << (sigma_hat[0].quadrature_point_value(i))
<< setw(14) << (sigma_hat[1].quadrature_point_value(i))
<< setw(14) << (sigma_hat[2].quadrature_point_value(i)) << endl;
}

Workbook of Applications in VectorSpace C++ Library 507


Chapter 5 Advanced Finite Element Methods
} else if(Matrix_Representation::Assembly_Switch ==
nodal stresses by projection
Matrix_Representation::NODAL_STRESS) {
int stress_no = (ndf+1)*ndf/2;
the_element_nodal_value &= C0(nen*stress_no, (double*)0);
C0 projected_nodal_stress = SUBVECTOR("int, C0&", stress_no, the_element_nodal_value);
H0 sigma_hat = INTEGRABLE_VECTOR("int, Quadrature", 3, qp);
sigma_hat = 0.0;
H0 DB_bar = D*B_bar;
for(int i = 0; i < nen; i++)
sigma_hat +=
DB_bar(i*ndf)*(ul[i*ndf]+gl[i*ndf]) + DB_bar(i*ndf+1)*(ul[i*ndf+1]+gl[i*ndf+1]);
H0 unit(qp); unit = 1.0;
C0 area = unit | dv,
lumped_mass = VECTOR("int", nen),
sum_of_lumped_mass(0.0);
for(int i = 0; i < nen; i++) {
lumped_mass[i] = (((H0)N[i]).pow(2)) | dv;
sum_of_lumped_mass += lumped_mass[i];
}
C0 normalized_factor = area/sum_of_lumped_mass;
for(int i = 0; i < nen; i++) {
lumped_mass[i] *= normalized_factor;
projected_nodal_stress(i) = ( (((H0)N[i])*sigma_hat) | dv ) / lumped_mass[i];
}

∫B
T
} else stiff &= ((~B_bar)*D*B_bar)|dv; K≡ DB dΩ
}
Element_Formulation* Element_Formulation::type_list = 0; Ωe
Element_Type_Register element_type_register_instance;
D ≡ µ  D 0 – --- m ⊗ m + K ( m ⊗ m )
2
static Elastic_B_bar_Q84
elastic_B_bar_Q84_instance(element_type_register_instance);
 3 
int main() {
where
Matrix_Representation mr(gd);
mr.assembly();
20 0
C0 u = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
gd.u_h() = u; D0 = 0 2 0
gd.u_h() = gd.gh_on_gamma_h(); 00 1
cout << gd.u_h() << endl;
#if defined(__TEST_POST_PROCESSING)
Matrix_Representation::Assembly_Switch = Matrix_Representation::REACTION; post-processing
mr.assembly(FALSE);
cout << "reaction:" << endl << (mr.global_nodal_value()) << endl;
Matrix_Representation::Assembly_Switch = Matrix_Representation::STRAIN;
mr.assembly(FALSE);
Matrix_Representation::Assembly_Switch = Matrix_Representation::NODAL_STRAIN;
mr.assembly(FALSE);
cout << "nodal strains:" << endl << (mr.global_nodal_value()) << endl;
Matrix_Representation::Assembly_Switch = Matrix_Representation::STRESS;
mr.assembly(FALSE);
Matrix_Representation::Assembly_Switch = Matrix_Representation::NODAL_STRESS;
mr.assembly(FALSE);
cout << "nodal stresses:" << endl << (mr.global_nodal_value()) << endl;
#endif
return 0;
}

Listing 5•9 B matrix formulation for plane elasticity (project: “b_bar_formulation” in project workspace
file “fe.dsw”).

508 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
method for the standard irreducible formulation. We will also encounter an assumed-stress method, the Pian-
Sumihara element, later in this chapter.

Non-conforming Element
In Chapter 4 Figure 4•42 eight bilinear 4-node elements and two Lagrangian 9-node elements are used to
compute beam bending problem. The tip-deflection solution (see TABLE 4•3. in page 405) with bilinear 4-node
elements (2 × 2 integration) is only 60 % of the exact solution, while it is 98% accurate for the Lagrangian 9-node
elements. The bilinear 4-node element exhibits shear locking and dilatation locking both due to the interpolation
failure to represent x2 and y2 (see aliasing analysis discussed in page 397 and page 406). Therefore these two
quadratic displacement modes are added back (1) to improve the bending behavior, and (2) to overcome the
incompressible limit of the 4-node element. This is the Wilson’s nonconforming element.

N 4 ≡ 1 – ξ 2, and N 5 ≡ 1 – η 2 Eq. 5•134


3 5
∑ Na ( ξ, η )ûea + ∑ Na ( ξ, η ) α̂e
a
u eh ( ξ, η ) ≡ Eq. 5•135
a=0 a=4

where node number “0, 1, 2 , 3” corresponding to four nodes of the element. α̂ e , with a = 4, 5, are known as
a

“nodeless variables” or “generalized displacements”, which is independent from the other element. Since α̂ e are
a

independent from the other elements, they can be eliminated at the element level. The element stiffness matrix is
of the form corresponding to variables a = [u0, u1, u2, u3, α4, α5]T as

T
k uu k αu û f
= Eq. 5•136
k αu k αα α̂ 0

From second equation

α̂ – 1 k û
= – k αα αu Eq. 5•137

Substituting into the first equation

kû ≡ [ k uu – k αu
T k – 1 k ]û = f
αα αu Eq. 5•138

However, it is found that Wilson’s non-conforming element only works for rectangular or parallelogram element.
The addition of two quadratic shape functions in Eq. 5•134 make the interpolation polynomial not complete up to
the second-order. Therefore, the spatial isotropy is lost; the element is not invariant with respect to an arbitrary
coordinate axes rotation. The integration of B-matrix components corresponding to the nodeless variables are

Workbook of Applications in VectorSpace C++ Library 509


Chapter 5 Advanced Finite Element Methods

∂y
– ξ ------ 0
1 1 N 4, x 0 1 1 ∂η
∂x
B4 dΩ = ∫∫ 0 N 4, y J dξ dη = 2 ∫ ∫ 0 ξ ------ dξd
∂η
Eq. 5•139
–1 –1 N 4, y N 4, x –1 –1
∂x ∂y
ξ ------ – ξ ------
∂η ∂η

∂y
η ------ 0
1 1 N 5, x 0 1 1 ∂ξ
∂x
∫ B 5 dΩ = ∫∫ 0 N 5, y J dξ dη = 2 ∫ ∫ 0 – η ------ dξ dη
∂ξ
Eq. 5•140
Ωe –1 – 1 N 5, y N 5, x –1 –1
∂x ∂y
– η ------ η ------
∂ξ ∂ξ

where we have use the relation N,x = N,ξ ξ,x, and the inversion of a 2 × 2 matrix

–1
∂ξ ∂ξ ∂x ∂x ∂y ∂x
------ ------ ------ ------ ------ ------
∂x ∂y ∂ξ ∂η 1 ∂η – ∂η
= = --
- Eq. 5•141
∂η ∂η ∂y ∂y J ∂y ∂x
------ ------ ------ ------ – ------ ------
∂x ∂y ∂ξ ∂η ∂ξ ∂ξ

For rectangular and parallelogram elements, the derivatives in Eq. 5•139 and Eq. 5•140 are constants through
out element domain. The integration of B4 and B5 over ξ and η = [-1, 1] are zero when the derivatives are con-
stant throught out the element domain, since B4 and B5, as defined in Eq. 5•139 and Eq. 5•140, become odd
functions of ξ and η. For element geometry other than rectangular or parallelogram, we can improve the behav-
ior of the non-conforming element. This can be achieved by evaluating the derivatives and the Jacobian for B4
and B5 only at the center [ξ0, η0] of the element. Such element has the improved behavior for element geometry
that are not rectangular or parallelogram (Taylor’s non-conforming element). Recall that the 8 bilinear 4-node
elements in the higher-order patch test (page 422 in Chapter 4) produces tip deflection of “-0.656467”, which is
significantly less than the exact solution of “-0.75”. Program Listing 5•10 implements the non-conforming ele-
ment discussed in this section. The Taylor’s non-conforming element can be invoked by setting macro definition
“__TEST_TAYLOR” at the compile time. The distortion of vertical element boundaries can be set by the macro
definition “__TEST_DISTORTION”. The results of eight non-conforming elements in the same problem in the
higher-order patch test are listed in the TABLE5• 1.

Distortion Wilson Taylor


d=0 -0.75 -0.75
d = 0.125 -0.591791 -0.680276
TABLE5• 1. Non-conforming element tip-deflections.

Without geometrical distortion both Wilson’s and Taylor’s non-conforming elements produce exact solution.
That is they both provide solution superior than the standard bilinear element. When the geometrical distortion

510 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#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_ = 1.e3; static const double v_ = 0.3;
#if defined(__TEST_DISTORTION)
static const double e_ = h_e_/2.0;
#else
static const double e_ = 0.0;
#endif
Omega_h::Omega_h() {
double v[2]; Node *node; define Ωu
v[0] = 0.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node);
1st row
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);
2nd row
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);
3rd row
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); 4-nodes element
int ena[4]; Omega_eh *elem;
ena[0] = 0; ena[1] = 1; ena[2] = 6; ena[3] = 5;
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);
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h); boundary conditions
the_gh_array[node_order(4)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(4)][0] = 0.0;
the_gh_array[node_order(9)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(9)][0] = 0.0;
the_gh_array[node_order(14)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(14)][0] = 0.0;
the_gh_array[node_order(9)](1) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(9)][1] = 0.0;
the_gh_array[node_order(10)](0) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(10)][0] = -5.0;
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(0)][0] = 5.0;
}

Workbook of Applications in VectorSpace C++ Library 511


Chapter 5 Advanced Finite Element Methods
static int ndf = 2;
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);
class Elastic_Nonconforming_Q4 : public Element_Formulation {
public:
Elastic_Nonconforming_Q4(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Elastic_Nonconforming_Q4(int, Global_Discretization&);
};
Element_Formulation* Elastic_Nonconforming_Q4::make(int en, Global_Discretization& gd) {
return new Elastic_Nonconforming_Q4(en,gd);
}
static const double a_ = E_ / (1.0-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.0-v_)/2.0} };
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
Elastic_Nonconforming_Q4::Elastic_Nonconforming_Q4(int en, Global_Discretization& gd) :
Element_Formulation(en, gd) {
#if defined(__TEST_TAYLOR)
Taylor’s non-conforming element
Quadrature qp(2, 4); 2 × 2 Gauss integration
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.0-Zai)*(1.0-Eta)/4.0; bilinear 4-nodes shape functions
N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0;
N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
H1 X = N*xl;
H0 Nx = d(N) * d(X).inverse();
J dv(d(X).det());
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), Wx, Wy, B;
Wx &= W_x[0][0]; Wy &= W_x[0][1];
B &= (~Wx || C0(0.0)) & B = [B0, B1, B2, B3]
(C0(0.0) || ~Wy ) &
(~Wy || ~Wx );
C0 Kdd = ((~B) * (D * B)) | dv;
Quadrature qp1(2, 1); 1-point Gaussian integration
H1 z(2, (double*)0, qp1), zai, eta,
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 2, qp1);
zai &= z[0]; eta &= z[1];
n[0] = (1.0-zai)*(1.0-eta)/4.0;
n[1] = (1.0+zai)*(1.0-eta)/4.0;
n[2] = (1.0+zai)*(1.0+eta)/4.0;
n[3] = (1.0-zai)*(1.0+eta)/4.0;
H1 x = n*xl;
H0 dx = d(x);
#define QPV quadrature_point_value
C0 x_zai = dx[0][0].QPV(0), y_zai = dx[1][0].QPV(0),
x_eta = dx[0][1].QPV(0), y_eta = dx[1][1].QPV(0);
H0 b = ((-((H0)Zai)*y_eta) | C0(0.0) | (((H0)Eta)*y_zai) | C0(0.0) )&
( C0(0.0) | (((H0)Zai)*x_eta) | C0(0.0) | (-((H0)Eta)*x_zai) ) &
b = [B4, B5]
((((H0)Zai)*x_eta) | (-((H0)Zai)*y_eta) | (-((H0)Eta)*x_zai) | (((H0)Eta)*y_zai) );
C0 Kaa = ((~b) * (D * b)) | dv,
Kau = ((~b) * (D * B)) | dv,
kû ≡ [ k uu – k αu
T k – 1 k ]û = f
αα αu
Kaa_inv = Kaa.inverse();
stiff &= Kuu- (~Kau)*Kaa_inv*Kau;

512 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
#else Wilson’s non-conforming elements is
int nonconforming_mode = 2;
Quadrature qp(2, 9); the default
int N_size = 4 + nonconforming_mode;
H1 Z(2, (double*)0, qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature", N_size, 2, qp),
Zai, Eta;
Zai &= Z[0];
Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0;
N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0;
N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
nodeless variable shape functions
H1 X;
N[4] = 1.0-Zai.pow(2); N 4 ≡ 1 – ξ 2, and N 5 ≡ 1 – η 2
N[5] = 1.0-Eta.pow(2);
C0 nodeless_x = MATRIX("int, int", 2, 2); nodeless_x = 0.0;
C0 x = xl & nodeless_x;
X &= N*x;
H0 Nx = d(N) * d(X).inverse();
J dv(d(X).det());
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx),
wx, wy, B;
wx &= w_x[0][0];
wy &= w_x[0][1];
B &= (~wx || C0(0.0)) &
(C0(0.0) || ~wy ) &
(~wy || ~wx );

C0 K = ((~B) * (D * B)) | dv,


Kdd = MATRIX("int, int, C0&, int, int", 8, 8, K, 0, 0),
Kaa = MATRIX("int, int, C0&, int, int", 4, 4, K, 8, 8),
Kad = MATRIX("int, int, C0&, int, int", 4, 8, K, 8, 0),
Kaa_inv = Kaa.inverse();
kû ≡ [ k uu – k αu
T k – 1 k ]û = f
αα αu
stiff &= Kdd - (~Kad)*Kaa_inv*Kad;
#endif
}

Element_Formulation* Element_Formulation::type_list = 0;
Element_Type_Register element_type_register_instance;
static Elastic_Nonconforming_Q4
elastic_nonconforming_q4_instance(element_type_register_instance);

int main() {
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 5•10 Nonconforming element for plane elasticity (project: “nonconforming_element” in project
workspace file “fe.dsw”).

Workbook of Applications in VectorSpace C++ Library 513


Chapter 5 Advanced Finite Element Methods
sets in the Wilson’s non-conforming element deteriorates to “-0.591791”, while Taylor’s non-conforming ele-
ment provides improved solution of “-0.680276”, which is also better than the standard bilinear 4-node element.

514 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
Hourglass Element
The hourglass element for plane elasticity is similar to that for the 2-D heat condition. The modified element
stiffness matrix is still

ke = ke(1-point) + ke(hourglass) Eq. 5•142

The element stiffness matrix is of size 8 × 8 for bilinear 4-node element (i.e., {ndf × nen} × {ndf × nen}). There are
three independent relations provided by three equations of stress-strain relations. Therefore, the one point Gauss
integration produces ke(1-point) of rank 3, which is clearly rank deficient. The correct rank number for ke should
be 5, which is from the full-rank subtracts the three rigid-body-motions (8-3=5). Therefore, two trial hourglass
modes (expanded by Ψ to both u and v), corresponding to the x-hourglass and y-hourglass modes (see Figure
4•50d& e in Chapter 4), are used to define ke(hourglass) as

EJ ( b ⊗ b )
k e ( hourglass ) ≡ -------------------------- ⊗ ( Ψ ⊗ Ψ ) Eq. 5•143
12

where E is the Young’s modulus. We can view Eq. 5•36 for heat conduction as the 1-ndf degenerated version of
Eq. 5•143 (by using κ ( b • b ) , a scalar, in place of E ( b ⊗ b ), which is a 2 × 2 matrix).The hourglass element is
implemented in project “hourglass_element” in project workspace file “fe.dsw” and is shown in the Program
Listing 5•11.
We test the performance of the hourglass element by considering the same problem solved by project
“higher_order_patch_test”. The formulation of 1-point integration stiffness is the same as the stiffness matrix
computed with macro definition “__TEST_B_MATRIX_FORMULATION” set at compile time for project
“higher_order_patch_test”. The hourglass element takes only 0.5 second to assemble the global stiffness matrix
comparing to 4.5 seconds for the standard 2 × 2 integration with project “higher_order_patch_test” (on an obso-
lete 166 MHz PC). However, the tip deflection is -0.82 instead of -0.75 (exact), which is not very accurate.

Workbook of Applications in VectorSpace C++ Library 515


Chapter 5 Advanced Finite Element Methods

#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_ = 1.e3; static const double v_ = 0.3;
#if defined(__TEST_DISTORTION)
static const double e_ = h_e_/2.0;
#else
static const double e_ = 0.0;
#endif
Omega_h::Omega_h() {
double v[2]; Node *node; define Ωu
v[0] = 0.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node);
1st row
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);
2nd row
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);
3rd row
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); 4-nodes element
int ena[4]; Omega_eh *elem;
ena[0] = 0; ena[1] = 1; ena[2] = 6; ena[3] = 5;
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);
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h); boundary conditions
the_gh_array[node_order(4)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(4)][0] = 0.0;
the_gh_array[node_order(9)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(9)][0] = 0.0;
the_gh_array[node_order(14)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(14)][0] = 0.0;
the_gh_array[node_order(9)](1) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(9)][1] = 0.0;
the_gh_array[node_order(10)](0) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(10)][0] = -5.0;
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(0)][0] = 5.0;
}

516 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
static int ndf = 2; 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);
class Elastic_Hourglass_Q4 : public Element_Formulation { public:
Elastic_Hourglass_Q4(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Elastic_Hourglass_Q4(int, Global_Discretization&);
};
Element_Formulation* Elastic_Hourglass_Q4::make(int en, Global_Discretization& gd) {
return new Elastic_Hourglass_Q4(en,gd); }
static const double a_ = E_ / (1.0-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.0-v_)/2.0} };
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
Elastic_Hourglass_Q4::Elastic_Hourglass_Q4(int en, Global_Discretization& gd) :
Element_Formulation(en, gd) { Quadrature qp(2, 1);
H1 Z(2, (double*)0, qp), Zai, Eta, 1-point Gaussian quadrature
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 2, qp);
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dv(d(X).det());
H0 unit(qp); unit = 1.0;
double vol = ((double)(unit | dv));
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), wx, wy, B;
wx &= w_x[0][0]; wy &= w_x[0][1];
B &= (~wx || C0(0.0)) & (C0(0.0) || ~wy ) & (~wy || ~wx );
C0 K_standard = ((~B) * (D * B)) | dv;
#if defined(__TEST_BELYTSCHKO)
double hv[4] = {1.0, -1.0, 1.0, -1.0}; Ψa = ha - (ha xa) bxa - (ha ya) bya
C0 h = VECTOR("int, const double*", 4, hv), phi = h - Nx.quadrature_point_value(0)*((~xl)*h);
#else
double l[4] = {0.0, 0.0, 0.0, 1.0};
C0 rhs = MATRIX("int, int", 4, 4), lhs = VECTOR("int, const double*", 4, l);
rhs[0] = 1.0; rhs[1] = xl(0); rhs[2] = xl(1); rhs[3] = lhs; C0 phi = lhs / rhs;
#endif
double factor = 2.0/norm(phi); phi *= factor; Ψa is Ψa normalized to ||Ψa|| =2
H0 s = INTEGRABLE_MATRIX("int, int, Quadrature", 2, 2, qp);
s[0][0] = Nx(0)*Nx(0); s[0][1] = s[1][0] = Nx(0)*Nx(1); s[1][1] = Nx(1)*Nx(1);
C0 pp = phi*(~phi), S = s|dv;
EJ ( b ⊗ b )
C0 K_hourglass = (E_*vol/48.0) *( k e ( hourglass ) ≡ -------------------------- ( Ψ ⊗ Ψ ) ,
( (S*pp[0][0]) | (S*pp[0][1]) | (S*pp[0][2]) | (S*pp[0][3]) ) & 12
( (S*pp[1][0]) | (S*pp[1][1]) | (S*pp[1][2]) | (S*pp[1][3]) ) &
( (S*pp[2][0]) | (S*pp[2][1]) | (S*pp[2][2]) | (S*pp[2][3]) ) &
( (S*pp[3][0]) | (S*pp[3][1]) | (S*pp[3][2]) | (S*pp[3][3]) ) ke(1-point) for irreducible formulation
); ke = ke(1-point) + ke(hourglass)
stiff &= K_standard + K_hourglass;
}
Element_Formulation* Element_Formulation::type_list = 0;
Element_Type_Register element_type_register_instance;
static Elastic_Hourglass_Q4 elastic_hourglass_q4_instance(element_type_register_instance);
int main() {
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 5•11 Hourglass element for plane elasticity (project: “hourglass_element” in project workspace file
“fe.dsw”).

Workbook of Applications in VectorSpace C++ Library 517


Chapter 5 Advanced Finite Element Methods
5.1.3 Hybrid Formulation for Plane Elasticity
Hybrid formulation has field that is approximated at the interface of an element, also known as incomplete
field, not covering the entire element domain as they were always implicitly assumed in all of previous formula-
tions.
We consider basic physics of a body with internal discontinuities (see Figure 5•4).1 ΓI is the discontinuous
interface in between subdomain Ω0, and Ω1. The traction t0 at a point x0 on subdomain Ω0 is acting on a point x1
on subdomain Ω1. The traction t1 at the point x1 on subdomain Ω1 is acting on the point x0 on subdomain Ω0.
Under equilibrium, Newton’s third law of motion requires that tractions t0 and t1 be equal in magnitudes but
opposite in signs. That is

t0 = - t1 = λ Eq. 5•144

Irreducible Subdomains
The Euler-Lagrange equations applied on each of the two subdomains are

∫ δu0T L T σ0 dΩ – ∫ δu0T b dΩ – ∫ δu0T h 0 dΓ – ∫ δu0T t0 dΓ = 0


Ω0 Ω0 Γ0 ΓI

∫ δu1T L T σ1 dΩ – ∫ δu1T b dΩ – ∫ δu1T h 1 dΓ – ∫ δu1T t1 dΓ = 0 Eq. 5•145


Ω1 Ω1 Γ1 ΓI

The displacement continuity on ΓI is enforced by a constraint equation that

Γ0
Ω0
σ0 , u0 t = nσ (Cauchy’s formula)
n
t1
x0
ΓI tn = t • n
t0

Ω1 tt = t-tn
σ1 , u1
Γ1

Figure 5•4 Traction contact condition in the internal discontinuous surface ΓI. tn
is the normal component of t, and tt is its tangential component.

1. see p. 242 in Malvern, L.E., 1969, “Introduction to the mechanics of a continuous medium”, Prentice-Hall, Inc., Engle-
wood Cliffs, New Jersey.

518 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

∫ δ λ T ( u1 – u0 ) dΓ Eq. 5•146
ΓI

The matrix form of Eq. 5•145 and Eq. 5•146 after finite element approximation is1

K 0 Q 0T 0 û 0 f0
Q 0 0 Q1 λ̂ = fI Eq. 5•147
0 Q 1T K 1 û 1 f1

where for i = 0, 1

Ki = ∫ BiT Di Bi dΩ Eq. 5•148


Ωi

Q i = ( – 1 ) i ∫ N λT N u dΓ Eq. 5•149
i
ΓI

fi = ∫ NuT b dΩ + ∫ NuT h i dΓ
i i
Eq. 5•150
Ωi Γi

In the spirit of the B-method, all internal fields are eliminated at the element level through static condensation
to make a displacement-only formulation that resembles the standard irreducible formulation. In the present case,
the displacement û in Eq. 5•147 can be eliminated, leave only with boundary forces λ̂, provided that the stiff-
ness matrices Ki are all invertible. However, that would require each subdomain be specified so that the rigid-
body-motions are precluded. Therefore, the singularity of the stiffness matrices is avoided. The difficulty in
removing rigid-body-motions for each subdomain limits the practical use of the hybrid method in the present
form.
The Program Listing 5•12 implements the hybrid irreducible domains formulation. The test problem for the
higher-order patch test is now illustrated in Figure 5•5. As stated earlier special difficulty arises that the subdo-
main “0” is not fully constrained that the rigid-body modes can be prevented. The solution procedure is pro-
ceeded as the followings. First the static condensation can still be applied to the subdomain “1”, since its is
constrained sufficiently to suppress the rigid body motions. From third equation of Eq. 5•147, since K1 is not sin-
gular we can have

û 1 = K 1–1 [ f 1 – Q 1T λ̂ ] Eq. 5•151

1. see p.375 in Zienkiewicz, O.C. and R.L. Taylor, 1989, “The finite element method”, 4th ed., vol. 1. McGraw-Hill, Inc.,
UK.

Workbook of Applications in VectorSpace C++ Library 519


Chapter 5 Advanced Finite Element Methods
Substituting Eq. 5•151 into second equation of Eq. 5•147, we have

 
Q 0 û 0 + Q 1  K 1– 1 [ f 1 – Q 1T λ̂ ]  = fI Eq. 5•152
 

Therefore,

λ̂ = [ Q 1 K 1–1 Q 1T ] –1 [ Q 0 û 0 + Q 1 K 1–1 f 1 – f I ] Eq. 5•153

Substituting into the first equation of Eq. 5•147,

K 0 û 0 + Q 0T { [ Q 1 K 1–1 Q 1T ] – 1 [ Q 0 û 0 + Q 1 K 1–1 f 1 – fI ] } = f0 Eq. 5•154

We can solve the displacement on the first subdomain by

û 0 = { K 0 + Q 0T [ Q 1 K 1–1 Q 1T ] –1 Q 0 } – 1 { f 0 + Q 0T [ Q 1 K 1–1 Q 1T ] –1 f I – Q 0T [ Q 1 K 1–1 Q 1T ] –1 Q 1 K 1– 1 f 1 } Eq. 5•155

After û 0 is obtained, λ̂ is computed from Eq. 5•153, then û 1 is computed from Eq. 5•151. The solution of this
computation shows that the tip-deflection is 0.75 (exact), and the horizontal traction λx on top and bottom of the
interface element has the magnitude of “15”, which is also exact.

Γ0 Γ1

15

Ωh0 Ωh 1
-15

ΓI
E =103, ν = 0.3

Figure 5•5 Bean bending problem for the hybrid irreducible domains formulation.

520 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#include "include\fe.h"
#include "include\omega_h_n.h"
Matrix_Representation_Couple::assembly_switch
Matrix_Representation_Couple::Assembly_Switch = Matrix_Representation_Couple::ALL;
#include "include\global_discretization_gamma_h_n.h"
static const double L_ = 10.0;
static const double c_ = 1.0;
static const double h_e_ = L_/((double)4.0);
static const double E_ = 1.e3;
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_);
static const double K_ = lambda_bar+2.0/3.0*mu_;
Omega_h_i::Omega_h_i(int i) : Omega_h(0){
if(i == 0) {
double v[2]; define Ωh0
Node *node;
v[0] = 0.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node);
1st row
v[0] = h_e_; node = new Node(1, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(2, 2, v); node_array().add(node); 2nd row
v[0] = 0.0; v[1] = 1.0*c_; node = new Node(3, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(4, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = 2.0*c_; node = new Node(5, 2, v); node_array().add(node);
v[0] = h_e_; node = new Node(6, 2, v); node_array().add(node); 3rd row
v[0] = 2.0*h_e_; node = new Node(7, 2, v); node_array().add(node);
int ena[8]; Omega_eh *elem;
ena[0] = 0; ena[1] = 2; ena[2] = 7; ena[3] = 5; 4-nodes element
ena[4] = 1; ena[5] = 4; ena[6] = 6; ena[7] = 3;
elem = new Omega_eh(0, 0, 0, 8, ena);
omega_eh_array().add(elem);
} else if(i == 1) { define Ωh1
double v[2];
Node *node;
v[0] = 2.0*h_e_; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(1, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(2, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; v[1] = 1.0*c_; 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] = 2.0*h_e_; v[1] = 2.0*c_; node = new Node(5, 2, v); node_array().add(node);
v[0] = 3.0*h_e_; node = new Node(6, 2, v); node_array().add(node);
v[0] = 4.0*h_e_; node = new Node(7, 2, v); node_array().add(node);
int ena[8]; Omega_eh *elem;
ena[0] = 0; ena[1] = 2; ena[2] = 7; ena[3] = 5;
ena[4] = 1; ena[5] = 4; ena[6] = 6; ena[7] = 3;
elem = new Omega_eh(0, 0, 0, 8, ena);
omega_eh_array().add(elem);
} else if(i == 2) { define ΓI; interface line elements
double v[2];
Node *node;
v[0] = 2.0*h_e_; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node);
v[1] = 1.0*c_; node = new Node(1, 2, v); node_array().add(node);
v[1] = 2.0*c_; node = new Node(2, 2, v); node_array().add(node);
int ena[3]; Omega_eh *elem; ena[0] = 0; ena[1] = 1; ena[2] = 2;
elem = new Omega_eh(0, 0, 0, 3, ena);
omega_eh_array().add(elem);
}
}

Workbook of Applications in VectorSpace C++ Library 521


Chapter 5 Advanced Finite Element Methods
gh_on_Gamma_h_i::gh_on_Gamma_h_i(int i, int df, Omega_h& omega_h) : gh_on_Gamma_h() {
gh_on_Gamma_h::__initialization(df, omega_h);
if(i == 0) {
the_gh_array[node_order(5)](0)= Nuemann B.C. bending moment on
the_gh_array[node_order(0)](0)=gh_on_Gamma_h::Neumann; left end
the_gh_array[node_order(5)][0] = -5.0; the_gh_array[node_order(0)][0] = 5.0;
} else if(i == 1) {
the_gh_array[node_order(2)](0) = the_gh_array[node_order(4)](0) = Dirichlet B.C. on right end
the_gh_array[node_order(7)](0) = the_gh_array[node_order(4)](1) =
gh_on_Gamma_h::Dirichlet;
}
}
Gamma_h_i::Gamma_h_i(int i, Omega_h &oh) : Omega_h(oh), the_index(i) { boundaries of Ωh0 & Ωh1 on ΓI
int ena[3]; Omega_eh *elem;
if(i == 0) { ena[0] = 2; ena[1] = 4; ena[2] = 7;
elem = new Omega_eh(0, 0, 0, 3, ena); the_omega_eh_array.add(elem); Γ0
} else if(i == 1) {
ena[0] = 0; ena[1] = 3; ena[2] = 5;
elem = new Omega_eh(0, 0, 0, 3, ena); the_omega_eh_array.add(elem);
Γ1
}
} First domain:
static int ndf = 2; static Omega_h_i oh_0(0);
static gh_on_Gamma_h_i gh_0(0, ndf, oh_0);
Ωh0 and B.C.
static U_h u_0(ndf, oh_0); u0
static Global_Discretization gd_0(oh_0, gh_0, u_0); Γ0
static Gamma_h_i gamma_h_0(0, oh_0);
static Global_Discretization_Gamma_h_i gd_gamma_0(
global discretization of Γ0
0, gd_0, (Global_Discretization*)0, gamma_h_0); Second domain:
static Omega_h_i oh_1(1); Ωh1 and B.C
static gh_on_Gamma_h_i gh_1(1, ndf, oh_1);
static U_h u_1(ndf, oh_1);
u1
static Global_Discretization gd_1(oh_1, gh_1, u_1); Γ1
static Gamma_h_i gamma_h_1(1, oh_1); global discretization of Γ1
static Global_Discretization_Gamma_h_i gd_gamma_1(
1, gd_1, (Global_Discretization*)0, gamma_h_1);
static Omega_h_i interface(2); Interface ΓI
static gh_on_Gamma_h_i interface_condition(2, ndf, interface); ΓI B.C. (do nothing)
static U_h lambda(ndf, interface);
static Global_Discretization gd_interface(interface, interface_condition, lambda);
λ and global discretization of ΓI
static Global_Discretization_Couple *interface_gamma_0_type = new {Ωh0, Γ0} global discretization couple
Global_Discretization_Couple(); { ΓI, Γ0} global discretization couple
static Global_Discretization_Couple gdc_interface_0(
gd_interface, gd_gamma_0, interface_gamma_0_type);
static Global_Discretization_Couple *interface_gamma_1_type = {Ωh1, Γ1} global discretization couple
new Global_Discretization_Couple(); { ΓI, Γ1} global discretization couple
static Global_Discretization_Couple gdc_interface_1(
gd_interface, gd_gamma_1, interface_gamma_1_type);
class Elastic_Hybrid_Irreducible_Q38 : public Element_Formulation_Couple { hybrid Q 3/8 element
public: 3-nodes line element on interface
Elastic_Hybrid_Irreducible_Q38(Element_Type_Register a) :
Element_Formulation_Couple(a) {}
8-node serendipity element on
Element_Formulation *make(int, Global_Discretization&); irreducible subdomains
Elastic_Hybrid_Irreducible_Q38(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&);
Elastic_Hybrid_Irreducible_Q38(int, Global_Discretization_Couple&);
};
Element_Formulation* Elastic_Hybrid_Irreducible_Q38::make(
int en, Global_Discretization& gd) {
return new Elastic_Hybrid_Irreducible_Q38(en,gd);
}

522 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
static const double a_ = E_ / (1.0-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.0-v_)/2.0}
};
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
selective reduced integration with
Elastic_Hybrid_Irreducible_Q38::Elastic_Hybrid_Irreducible_Q38( λ−µ formulation for
int en, Global_Discretization& gd) : Element_Formulation_Couple(en, gd) { irreducible subdomains
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp),
3 × 3 Gauss quadrature
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 8, 2, qp),
Zai, Eta;
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
serendipity shape function
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
N[4] = (1.0-Zai.pow(2))*(1.0-Eta)/2.0; N[5] = (1.0-Eta.pow(2))*(1.0+Zai)/2.0;
N[6] = (1.0-Zai.pow(2))*(1.0+Eta)/2.0; N[7] = (1.0-Eta.pow(2))*(1.0-Zai)/2.0;
N[0] = N[0] - (N[4]+N[7])/2.0; N[1] = N[1] - (N[4]+N[5])/2.0;
N[2] = N[2] - (N[5]+N[6])/2.0; N[3] = N[3] - (N[6]+N[7])/2.0;
H1 X = N*xl;
H0 Nx = d(N) * d(X).inverse();
J dv(d(X).det());
C0 e = BASIS("int", ndf),
E = BASIS("int", nen),
u = e*E,
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_dev = mu_* (
Kdev
+( ((2.0*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.0*Wy*~Wy) +(Wx*~Wx))*((e[1]%e[1])*(E%E)) )
| dv);
Quadrature qp2(2, 4); 2 × 2 Gauss quadrature
H1 z(2, (double*)0, qp2),
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 8, 2, qp2),
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;
n[4] = (1-zai.pow(2))*(1-eta)/2; n[5] = (1-eta.pow(2))*(1+zai)/2;
n[6] = (1-zai.pow(2))*(1+eta)/2; n[7] = (1-eta.pow(2))*(1-zai)/2;
n[0] -= (n[4]+n[7])/2; n[1] -= (n[4]+n[5])/2;
n[2] -= (n[5]+n[6])/2; n[3] -= (n[6]+n[7])/2;
H1 x = n*xl;
H0 nx = d(n) * d(x).inverse();
J dv2(d(x).det());
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, nx),
wx, wy;
wx &= w_x[0][0]; wy &= w_x[0][1]; Kvol
C0 stiff_vol = lambda_bar* (+( wx*~wx*U[0][0]+
wx*~wy*U[0][1]+
wy*~wx*U[1][0]+

∫ BiT Di Bi dΩ
wy*~wy*U[1][1] )
| dv2 ); Ki =
stiff &= stiff_vol + stiff_dev; Ωi
}

Workbook of Applications in VectorSpace C++ Library 523


Chapter 5 Advanced Finite Element Methods
Element_Formulation_Couple* Elastic_Hybrid_Irreducible_Q38::make(
formulation for interface
int en, Global_Discretization_Couple& gdc) {
return new Elastic_Hybrid_Irreducible_Q38(en,gdc); }
Elastic_Hybrid_Irreducible_Q38::Elastic_Hybrid_Irreducible_Q38(
int en, Global_Discretization_Couple& gdc) : Element_Formulation_Couple(en, gdc) { 1-D (line integral)
Quadrature qp3(1, 3);
H1 Z(qp3), 3-points Gauss quadrature
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 3, 1, qp3);
N[0] = (Z-1.0)*Z/2.0; N[1] = (1.0+Z)*(1.0-Z); N[2] = Z*(1.0+Z)/2.0;
H1 X = N*xl;
J d_l(norm(d(X)(0)));
H0 N_u = ((~(H0)N) || C0(0.0) ) & (C0(0.0) || (~(H0)N)),
N_lambda;
N_lambda &= N_u;
if(gdc.type() == interface_gamma_0_type) stiff &= -(((~N_u)*N_lambda)|d_l);
else stiff &= (((~N_u)*N_lambda)|d_l); Q i = ( – 1 ) i ∫ N λT N u dΓ
i
}
ΓI
Element_Formulation* Element_Formulation::type_list = 0;
Element_Type_Register element_type_register_instance;
static Elastic_Hybrid_Irreducible_Q38
elastic_hybrid_irreducible_Q38_instance(element_type_register_instance);
static Matrix_Representation mr_K_0(gd_0);
static Matrix_Representation mr_K_1(gd_1);
static Matrix_Representation_Couple mrc_Q_0(gdc_interface_0, 0, 0, &(mr_K_0.rhs()) );
static Matrix_Representation_Couple
mrc_Q_1(gdc_interface_1, 0, &(mrc_Q_0.rhs()), &(mr_K_1.rhs()) );
int main() {
mr_K_0.assembly(); mr_K_1.assembly();
mrc_Q_0.assembly(); mrc_Q_1.assembly();
Ki
C0 K_0 = (C0)(mr_K_0.lhs()), K_1 = (C0)(mr_K_1.lhs()),
Q_0 = (C0)(mrc_Q_0.lhs()), Q_1 = (C0)(mrc_Q_1.lhs()), Qi
f_0 = (C0)(mr_K_0.rhs()), f_1 = (C0)(mr_K_1.rhs()), f_i = (C0)(mrc_Q_0.rhs()); f0, f1 , fI
Cholesky dK1(K_1);
C0 K1_inv = dK1.inverse(),
QK_inv_1 = Q_1*K1_inv,
QKQ1 = QK_inv_1*(~Q_1); û 0 = { K 0 + Q 0T [ Q 1 K 1–1 Q 1T ] –1 Q 0 } – 1
Cholesky dQKQ1(QKQ1);
C0 QKQ1_inv = dQKQ1.inverse(),
Q0t_QKQ1_inv = (~Q_0) * QKQ1_inv, { f 0 + Q 0T [ Q 1 K 1–1 Q 1T ] –1 f I
Q0t_QKQ1_inv_Q0 = Q0t_QKQ1_inv * Q_0;
Cholesky dlhs(K_0 + Q0t_QKQ1_inv_Q0);
– Q 0T [ Q 1 K 1–1 Q 1T ] – 1 Q 1 K 1–1 f1 }
C0 u_0 = dlhs*(f_0+Q0t_QKQ1_inv*f_i-Q0t_QKQ1_inv*Q_1*(dK1*f_1)),
lambda = dQKQ1 * (Q_0*u_0+Q_1*(dK1*f_1)-f_i),
u_1 = dK1*(f_1-(~Q_1)*lambda); λ̂ =[ Q1 K1–1 Q1T ] – 1 [ Q0 û0 + Q1 K1–1 f1 – f I ]
gd_interface.u_h() = lambda;

û 1 = K 1–1 [ f 1 – Q 1T λ̂ ]
gd_interface.u_h() = gd_interface.gh_on_gamma_h();
cout << "inteface traction:" << endl << gd_interface.u_h();
gd_0.u_h() = u_0;
gd_0.u_h() = gd_0.gh_on_gamma_h();
cout << "first domain displacement:" << endl << gd_0.u_h();
gd_1.u_h() = u_1;
gd_1.u_h() = gd_1.gh_on_gamma_h();
cout << "second domain displacement:" << endl << gd_1.u_h();
return 0;
}

Listing 5•12 Hybrid irreducible subdomains for plane elasticity (project: “hybrid_irreducible_subdomain”
in project workspace file “fe.dsw”).

524 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
Pian-Sumihara Element
In the previous section, the displacement continuity on the interface ΓI is constrained by Eq. 5•146. We can
use Eq. 5•146 with, instead of the variational principle for the irreducible formulation, the Hellinger-Reissner
variational principle for the mixed formulation (see page 468). Therefore, mixed form subdomains are linking by
the constraint equations with the Lagrangian functional

( σ i, u i, λ ) ≡ --- ∫ σ iT D –1 σ i dΩ + ∫ uiT ( LT ui + b ) dΩ – ∫ uiT ( n σi – h i ) dΓ + ( –1 ) i ∫ uiT λ dΓ


1
Eq. 5•156
2
Ωi Ωi Γh ΓI
i

where i = 0, 1. An alternative form of the Hellinger-Reissner variational principle can be used to develop highly
efficient and accurate element. The Pian-Sumihara element can be easily implemented with the assumed-stress
field as1

a 22 ( ξ – ξ 0 ) a 02 ( η – η 0 )
Nσ ≡ b 22 ( ξ – ξ 0 ) b 02 ( η – η 0 ) Eq. 5•157
a2 b2 ( ξ – ξ0 ) a0 b0 ( η – η0 )

where

3 3 3 3 3 3
a0 ≡ ∑ xa ξa, a1 ≡ ∑ x a ξa ηa, a 2 ≡ ∑ xa ηa, b0 ≡ ∑ y a ξa, b1 ≡ ∑ ya ξa ηa, b2 ≡ ∑ ya ηa Eq. 5•158
a=0 a=0 a=0 a=0 a=0 a=0

{xa, ya}T(a = 0, ..., 3) are nodal coordinate, {ξa, ηa}T = {{-1,-1}, {1,-1},{1,1},{-1,1}}, and

J1 J2 a 0 b 2 – a2 b 0 a0 b1 – a1 b0 a1 b2 – a2 b1
ξ 0 ≡ --------, η 0 ≡ --------, with J 0 ≡ ----------------------------, J 1 ≡ ---------------------------- , J 2 ≡ ---------------------------- Eq. 5•159
3J 0 3J 0 16 16 16

The element stiffness matrix is defined as

ke = ke(1-point) + ke(stabilizer) Eq. 5•160

where ke(1-point) is again the 1-point Gaussian integration on stiffness matrix from standard irreducible formula-
tion, and ke(stabilizer) is defined as

ke(stabilizer) = CTA-1C Eq. 5•161

1. see p.282-285 in Zienkiewicz, O.C. and R.L. Taylor, 1989, “The finite element method”, 4th ed., vol. 1. McGraw-Hill,
Inc., UK.

Workbook of Applications in VectorSpace C++ Library 525


Chapter 5 Advanced Finite Element Methods
with

∫ Nσ D –1 Nσ dΩ, ∫ N σ B dΩ
T T
A= and C = Eq. 5•162
Ωe Ωe

The Program Listing 5•12 implements the Pian-Sumihara element. For the test case in “higher-order patch test”
in nearly incompressible plane strain condition (with ν = 0.5 - 10-12), the tip-deflection of the Pian-Sumihara
element is “-0.566027”, which is comparable to the tip-deflection “-0.5625” in project
“incompressible_u_p_formulation” (with ν = 0.5 & plane strain). For element distortion test1, as shown in Fig-
ure 5•6, the Pian-Sumihara element produces tip-deflection that is 80% of that of the element without distortion,
which is far better than bilinear 4-node element. The Pian-Sumihara element is praised as the most efficient and
accurate four-noded element to date.

E = 75, ν = 0.5 - 10-12


-0.5 1

0.5
10

Figure 5•6 Element distortion test for the Pian-Sumihara element.

1. see p.387 in Zienkiewicz, O.C. and R.L. Taylor, 1989, “The finite element method”, 4th ed., vol. 1. McGraw-Hill, Inc.,
UK.

526 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#include "include\fe.h"
static const double L_ = 10.0; static const double c_ = 2.0; static const double h_e_ = L_/2.0;
#if defined(__TEST_HIGHER_ORDER_PATCH_TEST)
static const double E_ = 1000.0;
static const double v_ = 0.5-1.e-12;
#else
static const double E_ = 1500.0; static const double v_ = 0.25;
#endif
#if defined(__TEST_DISTORTION)
static const double e_ = h_e_/10.0;
#else
static const double e_ = 0.0;
#endif
EP::element_pattern EP::ep = EP::QUADRILATERALS_4_NODES;
Omega_h::Omega_h() {
#if defined(__TEST_HIGHER_ORDER_PATCH_TEST) eight 4-nodes quadrilaterals
double x[4][2] = {{0.0, 0.0}, {10.0, 0.0}, {10.0, 2.0}, {0.0, 2.0}};
int control_node_flag[4] = {1, 1, 1, 1}, col_node_no = 5, row_node_no = 3;
block(this, row_node_no, col_node_no, 4, control_node_flag, x[0]);
#else
double v[2]; Node *node; two 4-nodes quadrilaterals
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_; node = new Node(2, 2, v); node_array().add(node);
v[0] = 0.0; v[1] = c_; node = new Node(3, 2, v); node_array().add(node);
v[0] = h_e_+e_; node = new Node(4, 2, v); node_array().add(node);
v[0] = 2.0*h_e_; node = new Node(5, 2, v); node_array().add(node);
int ena[4]; Omega_eh *elem; ena[0] = 0; ena[1] = 1; ena[2] = 4; ena[3] = 3;
elem = new Omega_eh(0, 0, 0, 4, ena); 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); omega_eh_array().add(elem);
#endif
}
gh_on_Gamma_h::gh_on_Gamma_h(int df, Omega_h& omega_h) {
__initialization(df, omega_h);
#if defined(__TEST_HIGHER_ORDER_PATCH_TEST) test case from p. 301 “Load 2”
int col_node_no = 5, row_node_no = 3; Zienkiewicz & Taylor vol.1
for(int i = 0; i < row_node_no; i++) {
the_gh_array[node_order((i+1)*col_node_no-1)](0) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order((i+1)*col_node_no-1)][0] = 0.0;
}
the_gh_array[node_order(col_node_no*((row_node_no+1)/2)-1)](1) =
gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(col_node_no*((row_node_no+1)/2)-1)][1] = 0.0;
double h_ = 1.0, f_ = 15.0;
the_gh_array[node_order(2*col_node_no)](0) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(2*col_node_no)][0] = -f_*(1.0/3.0)*h_;
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(0)][0] = f_*(1.0/3.0)*h_;
#else
the_gh_array[node_order(2)](0) = the_gh_array[node_order(5)](0) =
test case B.C. #3 from p. 386 , distorted
the_gh_array[node_order(2)](1) = gh_on_Gamma_h::Dirichlet; element configuration in p. 387 of
the_gh_array[node_order(3)](0) = gh_on_Gamma_h::Neumann; Zienkiewicz & Taylor vol.1
the_gh_array[node_order(3)][0] = 1000.0;
the_gh_array[node_order(0)](0) = gh_on_Gamma_h::Neumann;
the_gh_array[node_order(0)][0] = -1000.0;
#endif
}

Workbook of Applications in VectorSpace C++ Library 527


Chapter 5 Advanced Finite Element Methods
class Elastic_Pian_Sumihara_Q4 : public Element_Formulation {
public:
Elastic_Pian_Sumihara_Q4(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Elastic_Pian_Sumihara_Q4(int, Global_Discretization&);
};
Element_Formulation* Elastic_Pian_Sumihara_Q4::make(int en, Global_Discretization& gd) {
return new Elastic_Pian_Sumihara_Q4(en,gd);
}
#if defined(__TEST_PLANE_STRAIN_NEARLY_INCOMPRESSIBLE)
static const double a_ = E_*(1-v_)/(1+v_)/(1-2*v_);
static const double Dv[3][3] = {
{a_, a_*v_/(1-v_), 0 .0},
{a_*v_/(1-v_), a_, 0.0},
{0.0, 0.0, a_*(1-2*v_)/2.0/(1-v_)}};
#else
static const double a_ = E_ / (1.0-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.0-v_)/2.0}};
#endif
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
Elastic_Pian_Sumihara_Q4::Elastic_Pian_Sumihara_Q4(int en, Global_Discretization& gd) :
Element_Formulation(en, gd) {
Quadrature qp(2, 1);
H1 Z(2, (double*)0, qp),
2-D 1-point Gauss integration
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 2, qp),
Zai, Eta;
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
H1 X = N*xl;
H0 Nx = d(N) * d(X).inverse();
J dv(d(X).det());
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx),
Wx, Wy, B;
Wx &= W_x[0][0];
Wy &= W_x[0][1];
B &= (~Wx || C0(0.0)) &
(C0(0.0) || ~Wy ) & 4-node nodal natural coordinates
(~Wy || ~Wx );
C0 K_0 = ((~B) * (D * B)) | dv; 3 3 3
double a[3], b[3], j[3], a 0≡∑ x a ξ a, a 1 ≡∑ x a ξa η a , a 2≡ ∑ x a η a
zai[4] = {-1.0, 1.0, 1.0, -1.0}, a=0 a=0 a=0
eta[4] = {-1.0, -1.0, 1.0, 1.0};
a[0] = xl[0][0]*zai[0]+xl[1][0]*zai[1]+xl[2][0]*zai[2]+xl[3][0]*zai[3]; 3 3 3
a[1] = xl[0][0]*zai[0]*eta[0]+xl[1][0]*zai[1]*eta[1]+ b 0≡ ∑ y a ξ a , b 1 ≡ ∑ y a ξ a η a, b 2≡ ∑ y a η a
xl[2][0]*zai[2]*eta[2]+xl[3][0]*zai[3]*eta[3]; a=0 a=0 a=0
a[2] = xl[0][0]*eta[0]+xl[1][0]*eta[1]+xl[2][0]*eta[2]+xl[3][0]*eta[3];
b[0] = xl[0][1]*zai[0]+xl[1][1]*zai[1]+xl[2][1]*zai[2]+xl[3][1]*zai[3]; a 0 b 2 – a2 b 0 a0 b1 – a1 b0
b[1] =xl[0][1]*zai[0]*eta[0]+xl[1][1]*zai[1]*eta[1]+ J 0 ≡ ----------------------------, J 1 ≡ ----------------------------
16 16
xl[2][1]*zai[2]*eta[2]+xl[3][1]*zai[3]*eta[3];
b[2] = xl[0][1]*eta[0]+xl[1][1]*eta[1]+xl[2][1]*eta[2]+xl[3][1]*eta[3]; a1 b2 – a2 b1
j[0] = a[0]*b[2]-a[2]*b[0]; J 2 ≡----------------------------
j[1] = a[0]*b[1]-a[1]*b[0]; 16
j[2] = a[1]*b[2]-a[2]*b[1];
double zai_0 = j[1]/(j[0]*3.0),
J1 J2
ξ 0 ≡ --------, η 0 ≡ --------
eta_0 = j[2]/(j[0]*3.0); 3J 0 3J 0

528 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
Quadrature qp2(2, 4); 2-D 2 × 2 Gauss quadrature
H1 z(2, (double*)0, qp2),
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 2, qp2);
n[0] = (1.0-z[0])*(1.0-z[1])/4.0;
n[1] = (1.0+z[0])*(1.0-z[1])/4.0;
n[2] = (1.0+z[0])*(1.0+z[1])/4.0;
n[3] = (1.0-z[0])*(1.0+z[1])/4.0;

H1 x = n*xl;
H0 nx = d(n) * d(x).inverse();
J dv2(d(x).det());

H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, nx),


wx, wy, B2, N_sigma_i, N_sigma;
wx &= w_x[0][0];
wy &= w_x[0][1];
B2 &= (~wx || C0(0.0)) &
(C0(0.0) || ~wy ) & a 22 ( ξ – ξ 0 ) a 02 ( η – η 0 )
(~wy || ~wx );
Nσ ≡ b 22 ( ξ – ξ 0 ) b 02 ( η – η 0 )
N_sigma_i &= ( (pow(a[2],2)*(((H0)z[0])-zai_0)) | (pow(a[0],2)*(((H0)z[1])-eta_0)) ) &
( (pow(b[2],2)*(((H0)z[0])-zai_0)) | (pow(b[0],2)*(((H0)z[1])-eta_0)) ) & a2 b2 ( ξ – ξ0 ) a0 b 0 ( η – η 0 )
( (a[2]*b[2]*(((H0)z[0])-zai_0)) | (a[0]*b[0]*(((H0)z[1])-eta_0)) );
N_sigma &= (N_sigma_i | N_sigma_i | N_sigma_i | N_sigma_i);
C0 C = ((~N_sigma)*B2) | dv2,
D_inv = D.inverse(),
∫ Nσ D–1 Nσ dΩ, ∫ N σ B dΩ
T T
A = ((~N_sigma)*D_inv*N_sigma) | dv2, A= C =
A_inv = A.inverse(), Ωe Ωe
K_stabilizer = (~C)*A_inv*C;

stiff &= K_0 + K_stabilizer; ke(stabilizer) = CTA-1C


} ke = ke(1-point) + ke(stabilizer)
Element_Formulation* Element_Formulation::type_list = 0;
Element_Type_Register element_type_register_instance;
static Elastic_Pian_Sumihara_Q4
elastic_pian_sumihara_q4_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 5•13 Hybrid Pian-Sumihara element for plane elasticity (project: “hybrid_pian_sumihara” in
project workspace file “fe.dsw”).

Workbook of Applications in VectorSpace C++ Library 529


Chapter 5 Advanced Finite Element Methods
5.1.4 Reissner-Mindlin (Thick-) Plate Formulations

θ-w Irreducible Formulation


FromEq. 4•250 in Chapter 4, the transverse shear strain γ is defined as

∂w
γx θx -------
∂x
γ = = – + = ( – θ ) + ∇w Eq. 5•163
γy θy ∂w
-------
∂y

In Reissner-Mindlin plate theory, the “fiber” is assumed to remain in plane, but it is not assumed to keep perpen-
dicular to the mid-surface as in thin plate theory. That is the transverse shear, γ, is not assumed to be zero. The
bending moment constitutive equations from Eq. 4•251 in Chapter 4 is

M = DL θ Eq. 5•164

The shear force relations to the bending moments and vertical loads from Eq. 4•254 in Chapter 4 are

L T M + S = 0, and ∇ T S + q = 0 Eq. 5•165

First, M can be eliminated by substituting Eq. 5•164 into first part of Eq. 5•165 as

L T DL θ + S = 0 Eq. 5•166

Then, the constitutive equation for shear force and transverse shear strain is S = αγ (Eq. 4•253 in Chapter 4)
where α is the shear rigidity. Therefore,

--- + θ – ∇w = 0
S
Eq. 5•167
α

from the definition of the transverse shear strain γ ≡ ( – θ ) + ∇w in Eq. 5•163. Eq. 5•167 can be re-arranged as S
= α ( – θ + ∇w ) . Then, this is used to eliminate the shear force S in Eq. 5•166 as

L T DL θ – α ( θ – ∇w ) = 0 Eq. 5•168

and from the second part of Eq. 5•165

∇ T [ α ( θ – ∇w ) ] = q Eq. 5•169

Galerkin approximations to “θ” and “w” are made separately

θ = N θ θ̂ , and w = N w ŵ Eq. 5•170

530 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
Weak forms to Eq. 5•168 and Eq. 5•169 can be obtained by multiplying Nθ to Eq. 5•168 and Nw to Eq. 5•169 as

∫ ( NθT ( L T DLNθ ) – NθT αNθ ) dΩ θ̂ + ∫ NθT α∇Nw dΩŵ = 0


Eq. 5•171
Ω Ω

and

∫ NwT ∇ T αNθ dΩ θ̂ – ∫ NwT ∇ T α∇Nw dΩŵ = ∫ NwT q dΩ Eq. 5•172


Ω Ω Ω

Integrating by parts to first term of Eq. 5•171 and to the left-hand-side terms of Eq. 5•172 and apply Green’s the-
orem

∫ ( ( LNθ ) T DLNθ + NθT αNθ ) dΩ θ̂ – ∫ NθT α∇Nw dΩŵ = ∫ NθT MΓ dΓ Eq. 5•173
Ω Ω Γ

and

– ∫ ( ∇N w ) T αN θ dΩ θ̂ + ∫ ( ∇Nw ) T α∇N w dΩŵ = ∫ NwT q dΩ – ∫ NwT SΓ dΓ Eq. 5•174


Ω Ω Ω Γ

or in matrix form

T ŵ
K s K bs fw
= Eq. 5•175
K bs K b θ̂ fθ

where

Ks = ∫ ( ∇Nw ) T α∇Nw dΩ Eq. 5•176


K bs = – ∫ N θT α∇N w dΩ Eq. 5•177


Kb = ∫ ( ( LNθ ) T DLNθ + NθT αNθ ) dΩ Eq. 5•178


fw = ∫ NwT q dΩ – ∫ NwT SΓ dΓ Eq. 5•179


Ω Γ

fθ = ∫ NθT MΓ dΓ Eq. 5•180


Γ

Workbook of Applications in VectorSpace C++ Library 531


Chapter 5 Advanced Finite Element Methods
In Eq. 5•175 the submatrices involve shear constraints can be separated from the one that only involve bending
energy as

T
K s K bs Ks T
K bs 0 0
= + ≡ KS + KB Eq. 5•181
K bs K b K bs K b ( α ) 0 Kb ( D )

where the submatrices

Kb(α) = ∫ NθT αNθ dΩ, and K b ( D ) = ∫ ( LNθ ) T DLNθ dΩ Eq. 5•182


Ω Ω

The matrix KS involves components that enforce the shear constraints, and KB is the part that has only to do
with the bending energy. This stiffness splitting is useful for selective reduced integration, in which the reduced
integration is applied to all the submatrices in the shear constraint part, KS, to avoid shear locking.
The Program Listing 5•14 implements the “heterosis element” illustrated in Figure 5•7. The deflection
degree of freedom, w, uses eight-node serendipity shape function, and the rotation degrees of freedom, θ = [θx ,
θy]T, use Lagrangian 9-node shape function. All terms except the bending stiffness submatrix Kb(D) (or KB stiff-
ness matrix) use the reduced (2 × 2) integration. We solve the same test problem in the thin plate section. The
maximum deflection is 222358 at center, which is comparing to the exact solution of the thin-plate theory of
226800.

:w

: 2x2 reduced integration on


shear constrinats related terms

Figure 5•7 Heterosis element for irreducible thick plate formulation with Serendipity shape
function for deflection (w) and Lagrangian shape function for rotation (θ). The bending stiffness
submatrix Kb(D) is fully integrated with 3 × 3 integration. All other terms, that involve shear
constraints, are selective reduced integrated (2 × 2).

532 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#include "include\fe.h"
#include "include\omega_h_n.h"
Matrix_Representation_Couple::assembly_switch
Matrix_Representation_Couple::Assembly_Switch = Matrix_Representation_Couple::ALL;
Omega_h_i::Omega_h_i(int i) : Omega_h(0) { Heterosis element
if(i == 0) { int row_segment_no = 2, count=0;
Node *node; double v[2], h = 1.0/((double)(row_segment_no)); Ωw; 8-node serendipity element
for(int j = 0; j < row_segment_no; j++) {
v[1] = ((double)j)*h;
for(int k = 0; k < (2*row_segment_no+1); k++) {
v[0] = ((double)k)*h/2; node = new Node(count++, 2, v); the_node_array.add(node);
}
v[1] += h/2.0;
for(int k = 0; k < row_segment_no+1; k++) {
v[0] = ((double)k)*h; node = new Node(count++, 2, v); the_node_array.add(node);
}
}
v[1] = 1.0;
for(int j = 0; j < (2*row_segment_no+1); j++) {
v[0] = ((double)j)*h/2.0; node = new Node(count++, 2, v); the_node_array.add(node);
}
int ena[8]; Omega_eh *elem; count = 0;
for(int j = 0; j < row_segment_no; j++)
for(int k = 0; k < row_segment_no; k++) {
int first_node = j*(3*row_segment_no+2)+k*2;
ena[0] = first_node; ena[1] = ena[0]+2; ena[2] = ena[1]+(3*row_segment_no+2);
ena[3] = ena[2]-2; ena[4] = ena[0]+1;
ena[5] = (j+1)*(2*row_segment_no+1)+j*(row_segment_no+1)+k+1;
ena[6] = ena[3]+1; ena[7] = ena[5]-1;
elem = new Omega_eh(count++, 0, 0, 8, ena); the_omega_eh_array.add(elem);
}
} else if(i == 1) { Ωθ; Lagrangian 9-node element
int row_segment_no = 2;
int count = 0; Node *node; double v[2], h = 1.0/((double)(row_segment_no));
for(int j = 0; j < row_segment_no; j++) {
v[1] = ((double)j)*h;
for(int k = 0; k < (2*row_segment_no+1); k++) {
v[0] = ((double)k)*h/2; node = new Node(count++, 2, v); the_node_array.add(node);
}
v[1] += h/2.0;
for(int k = 0; k < (2*row_segment_no+1); k++) {
v[0] = ((double)k)*h/2; node = new Node(count++, 2, v); the_node_array.add(node);
}
}
v[1] = 1.0;
for(int j = 0; j < (2*row_segment_no+1); j++) {
v[0] = ((double)j)*h/2.0; node = new Node(count++, 2, v); the_node_array.add(node);
}
int ena[9]; Omega_eh *elem; count = 0;
for(int j = 0; j < row_segment_no; j++) for(int k = 0; k < row_segment_no; k++) {
int row_node_no = 2*row_segment_no+1, first_node = j*2*row_node_no+k*2;
ena[0] = first_node; ena[1] = ena[0]+2; ena[2] = ena[1]+2*row_node_no;
ena[3] = ena[2]-2; ena[4] = ena[0]+1; ena[5] = ena[1]+row_node_no;
ena[6] = ena[2]-1; ena[7] = ena[0]+row_node_no; ena[8] = ena[7] +1;
elem = new Omega_eh(count++, 0, 0, 9, ena); the_omega_eh_array.add(elem);
}
}
}

Workbook of Applications in VectorSpace C++ Library 533


Chapter 5 Advanced Finite Element Methods
gh_on_Gamma_h_i::gh_on_Gamma_h_i(int i, int df, Omega_h& omega_h) : gh_on_Gamma_h() { boundary conditions
gh_on_Gamma_h::__initialization(df, omega_h);
if(i == 0) {
the_gh_array[node_order(4)](0) = the_gh_array[node_order(7)](0) =
w=0
the_gh_array[node_order(12)](0) = the_gh_array[node_order(15)](0)= right
the_gh_array[node_order(16)](0) = the_gh_array[node_order(17)](0)=
the_gh_array[node_order(18)](0) = the_gh_array[node_order(19)](0)=
the_gh_array[node_order(20)](0) = gh_on_Gamma_h::Dirichlet;
top
} else if(i == 1) { θ=0
the_gh_array[node_order(4)](0) = the_gh_array[node_order(4)](1) = right
the_gh_array[node_order(9)](0) = the_gh_array[node_order(9)](1) =
the_gh_array[node_order(14)](0) = the_gh_array[node_order(14)](1) =
the_gh_array[node_order(19)](0) = the_gh_array[node_order(19)](1) =
the_gh_array[node_order(20)](0) = the_gh_array[node_order(20)](1) = top
the_gh_array[node_order(21)](0) = the_gh_array[node_order(21)](1) =
the_gh_array[node_order(22)](0) = the_gh_array[node_order(22)](1) =
the_gh_array[node_order(23)](0) = the_gh_array[node_order(23)](1) =
the_gh_array[node_order(24)](0) = the_gh_array[node_order(24)](1) = bottom
the_gh_array[node_order(0)](1) = the_gh_array[node_order(1)](1) =
the_gh_array[node_order(2)](1) = the_gh_array[node_order(3)](1) =
the_gh_array[node_order(0)](0) = the_gh_array[node_order(5)](0) = left
the_gh_array[node_order(10)](0) = the_gh_array[node_order(15)](0) =
gh_on_Gamma_h::Dirichlet;
}
}
static Global_Discretization *w_type = new Global_Discretization;
static Global_Discretization *theta_type = new Global_Discretization;
class Plate_Heterosis : public Element_Formulation_Couple {
public:
Plate_Heterosis(Element_Type_Register a) : Element_Formulation_Couple(a) {} diagonal formulation
Element_Formulation *make(int, Global_Discretization&);
Plate_Heterosis(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&); off-diagonal formulation
Plate_Heterosis(int, Global_Discretization_Couple&);
};
Element_Formulation* Plate_Heterosis::make(int en, Global_Discretization& gd) {
return new Plate_Heterosis(en,gd);
} 1 ν 0
static const double E_ = 1.0; static const double v_ = 0.25; static const double t_ = 0.01; Et 3 ν 1 0
D = -------------------------
static const double D_ = E_ * pow(t_,3) / (12.0*(1-pow(v_,2))); 12 ( 1 – ν 2 ) 1–ν
static const double Dv[3][3] = { {D_,D_*v_, 0.0 }, {D_*v_, D_, 0.0 }, {0.0,0.0, D_*(1-v_)/2.0} }; 0 0 ------------
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]); 2
static const double mu_ = E_/(2*(1+v_));
static const double alpha_ = (5.0/6.0)*mu_*t_;
Plate_Heterosis::Plate_Heterosis(int en, Global_Discretization& gd) :
Element_Formulation_Couple(en, gd) {
if(gd.type() == w_type) {
Quadrature qp(2, 4);
Ks formulation
H1 Z(2, (double*)0, qp), Zai, Eta, 2 × 2 reduced Gauss integration for
N=INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 8, 2, qp) ; shear constraints
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; serendipity shape functions for w dof
N[4] = (1-Zai.pow(2))*(1-Eta)/2.0; N[5] = (1-Eta.pow(2))*(1+Zai)/2.0;
N[6] = (1-Zai.pow(2))*(1+Eta)/2.0; N[7] = (1-Eta.pow(2))*(1-Zai)/2.0;
N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2;
N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;
J dv(d(X).det()); H0 Nx = d(N) * d(X).inverse();

534 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2/*nsd*/, Nx), Wx, Wy, grad_W;
Wx &= W_x[0][0]; Wy &= W_x[0][1]; Ks = ∫ ( ∇Nw )T α∇Nw dΩ
grad_W &= (~Wx) & (~Wy); Ω
stiff &= ((~grad_W)* (alpha_ * grad_W)) | dv;
double f_0 = 1.0;
force &= (((H0)N)*f_0) | dv;
fw = ∫ NwT q dΩ

} else if (gd.type() == theta_type) {
Quadrature qp(2, 9); Kb formulation
H1 Z(2, (double*)0, qp), Zai, Eta, 3 × 3 Gauss integration for bending
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 9, 2, qp);
Zai &= Z[0]; Eta &= Z[1];
Lagrangian shape functions for θ dof
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;
N[8] = (1-Zai.pow(2))*(1-Eta.pow(2));
N[0] -= N[8]/4; N[1] -= N[8]/4; N[2] -= N[8]/4; N[3] -= N[8]/4;
N[4] = ((1-Zai.pow(2))*(1-Eta)-N[8])/2; N[5] = ((1-Eta.pow(2))*(1+Zai)-N[8])/2;
N[6] = ((1-Zai.pow(2))*(1+Eta)-N[8])/2; N[7] = ((1-Eta.pow(2))*(1-Zai)-N[8])/2;
N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2; N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dV(d(X).det());
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), Wx, Wy, B;
Wx &=W_x[0][0];Wy &=W_x[0][1];
B &=(~Wx|| C0(0.0))&(C0(0.0) || ~Wy)&(~Wy|| ~Wx );
Quadrature qpr(2, 4);
2 × 2 reduced Gauss integration for
H1 z(2, (double*)0, qpr),zai, eta, shear constraints
n =INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 9, 2, qpr); Lagrangian shape functions for θ dof
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;
n[8] = (1-zai.pow(2))*(1-eta.pow(2));
n[0] -= n[8]/4; n[1] -= n[8]/4; n[2] -= n[8]/4; n[3] -= n[8]/4;
n[4] = ((1-zai.pow(2))*(1-eta)-n[8])/2; n[5] = ((1-eta.pow(2))*(1+zai)-n[8])/2;
n[6] = ((1-zai.pow(2))*(1+eta)-n[8])/2; n[7] = ((1-eta.pow(2))*(1-zai)-n[8])/2;
n[0] -= (n[4]+n[7])/2; n[1] -= (n[4]+n[5])/2; n[2] -= (n[5]+n[6])/2; n[3] -= (n[6]+n[7])/2;
H1 x = n*xl; J dv(d(x).det()); C0 zero(0.0);
H0 n0 = (H0)n[0], n1 = (H0)n[1], n2 = (H0)n[2], n3 = (H0)n[3], n4 = (H0)n[4],
n5 = (H0)n[5], n6 = (H0)n[6], n7 = (H0)n[7], n8 = (H0)n[8];
H0 n_theta = (n0|zero|n1| zero|n2 |zero|n3|zero|n4|zero|n5|zero|n6|zero|n7|zero|n8|zero) & Nθ
(zero|n0|zero|n1|zero|n2|zero|n3|zero|n4|zero|n5|zero|n6|zero|n7|zero|n8);
stiff &= ((~B)*(D*B) | dV) + ((~n_theta)*(alpha_*n_theta) | dv);
} Kb = ∫ ( ( LNθ ) T DLNθ + NθT αNθ ) dΩ
} Ω
Element_Formulation_Couple* Plate_Heterosis::make(
int en, Global_Discretization_Couple& gdc) {
return new Plate_Heterosis(en,gdc);
} Kbs formulation
Plate_Heterosis::Plate_Heterosis(int en, Global_Discretization_Couple& gdc) :
Element_Formulation_Couple(en, gdc) {
2 × 2 reduced Gauss integration
Quadrature qp(2, 4);
H1 Z(2, (double*)0, qp), Zai, Eta, Lagrangian shape functions for θ dof
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 9, 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;
N[8] = (1-Zai.pow(2))*(1-Eta.pow(2));
N[0] -= N[8]/4; N[1] -= N[8]/4; N[2] -= N[8]/4; N[3] -= N[8]/4;
N[4] = ((1-Zai.pow(2))*(1-Eta)-N[8])/2; N[5] = ((1-Eta.pow(2))*(1+Zai)-N[8])/2;
N[6] = ((1-Zai.pow(2))*(1+Eta)-N[8])/2; N[7] = ((1-Eta.pow(2))*(1-Zai)-N[8])/2;
N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2;
N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;
H1 X = N*xl; J dv(d(X).det());

Workbook of Applications in VectorSpace C++ Library 535


Chapter 5 Advanced Finite Element Methods
H0 N0 = N[0], N1 = N[1], N2 = N[2], N3 = N[3],
N4 = N[4], N5 = N[5], N6 = N[6], N7 = N[7], N8 = N[8];
H0 zero(qp); zero = 0.0;
H0 N_theta = (N0|zero|N1|zero|N2|zero|N3|zero|N4|zero|N5|zero|N6|zero|N7|zero|N8|zero) & Nθ
(zero|N0|zero|N1|zero|N2|zero|N3|zero|N4|zero|N5|zero|N6|zero|N7|zero|N8);
serendipity shape functions for w dof
H1 n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 8, 2, qp);
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;
n[4] = (1-Zai.pow(2))*(1-Eta)/2; n[5] = (1-Eta.pow(2))*(1+Zai)/2;
n[6] = (1-Zai.pow(2))*(1+Eta)/2; n[7] = (1-Eta.pow(2))*(1-Zai)/2;
n[0] -= (n[4]+n[7])/2; n[1] -= (n[4]+n[5])/2; n[2] -= (n[5]+n[6])/2; n[3] -= (n[6]+n[7])/2;
H0 nx = d(n) * d(X).inverse();
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2/*nsd*/, nx), wx, wy;
∇w
wx &= w_x[0][0]; wy &= w_x[0][1];
H0 grad_w = (~wx) & (~wy);
stiff &= -alpha_*( (~N_theta)*(grad_w) | dv ); K bs = – ∫ N θT α∇N w dΩ
}

Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static Plate_Heterosis plate_heterosis_instance(element_type_register_instance);
int main() {
const int w_ndf = 1;
Omega_h_i oh_w(0); w
gh_on_Gamma_h_i w_gh(0, w_ndf, oh_w);
U_h w_h(w_ndf, oh_w);

θ
Global_Discretization w_gd(oh_w, w_gh, w_h, w_type);
const int theta_ndf = 2;
Omega_h_i oh_theta(1);
gh_on_Gamma_h_i theta_gh(1, theta_ndf, oh_theta);
U_h theta_h(theta_ndf, oh_theta);
Global_Discretization theta_gd(oh_theta, theta_gh, theta_h, theta_type);
Global_Discretization_Couple gdc(theta_gd, w_gd); θ-w
Matrix_Representation mr_w(w_gd);
Matrix_Representation mr_theta(theta_gd);
Matrix_Representation_Couple mrc(gdc, 0, &(mr_theta.rhs()), &(mr_w.rhs()), &mr_w);
mr_w.assembly();
mr_theta.assembly();
mrc.assembly();
C0 Ks = ((C0)(mr_w.lhs())), f_w = ((C0)(mr_w.rhs())), Ks , and fw
Kbs = ((C0)(mrc.lhs())), Kbs
Kb = ((C0)(mr_theta.lhs())), f_theta = ((C0)(mr_theta.rhs()));
Kb, and fθ
Cholesky dKs(Ks);
C0 Ks_inv = dKs.inverse(),
KbsKs_invKbst = Kbs*Ks_inv*(~Kbs),
K = Kb-KbsKs_invKbst,
f = f_theta-Kbs*(dKs*f_w);
θ̂=(Kb-Kbs(Ks)-1KbsT)-1
LU dK(K); (fθ-Kbs(Ks)-1fw)
C0 theta = dK*f,
w = dKs*(f_w-(~Kbs)*theta);
ŵ = (Ks)-1(fw-KbsTθ̂ )
w_h = w; w_h = w_gd.gh_on_gamma_h();
cout << "deflection w:" << endl << w_h << endl;
theta_h = theta; theta_h = theta_gd.gh_on_gamma_h();
cout << "rotation (theta_x, theta_y):" << endl;
for(int i = 0; i < theta_h.total_node_no(); i++) cout << theta_h[i] << endl;
return 0;
}

Listing 5•14 Heterosis element for θ-w irreducible thick plate formulation (project:
“irreducible_thick_plate” in project workspace file “fe.dsw”).

536 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
θ-S-w Mixed Formulation
Eq. 5•166, Eq. 5•167 and second part of Eq. 5•165 give

L T DL θ + S = 0 Eq. 5•183

--- + θ – ∇w = 0
S
Eq. 5•184
α

∇TS + q = 0 Eq. 5•185

Three variables in Eq. 5•183 to Eq. 5•185 [θ, S, w]T are approximated as

θ = N θ θ̂ , S = N S S and w = Nw ŵ
ˆ
Eq. 5•186

The weighted residual statement of Eq. 5•183 to Eq. 5•185 are

∫ NθT LT DLNθ dΩ θ̂ + ∫ NθT NS dΩŜ = 0 Eq. 5•187


Ω Ω

∫ NST Nθ dΩ θ̂ + ∫ NST  --α- NS dΩŜ – ∫ NST ∇Nw dΩŵ


1
= 0 Eq. 5•188
Ω Ω Ω

∫ NwT ∇ T NS dΩŜ + ∫ NwT q dΩ = 0 Eq. 5•189


Ω Ω

Integrating by parts on the first term of Eq. 5•187 and the first term of Eq. 5•189, then apply Green’s theorem,
and simply change sign of Eq. 5•188 yield

∫ ( LNθ )T DLNθ dΩ θ̂ – ∫ NθT NS dΩŜ = ∫ NθT MΓ dΓ Eq. 5•190


Ω Ω Γ

– ∫ N ST N θ dΩ θ̂ – ∫ NST  --- N S dΩŜ + ∫ N ST ∇N w dΩŵ = 0


1
α 
Eq. 5•191
Ω Ω Ω

∫ ( ∇Nw )T NS dΩŜ = – ∫ Nw ∫ w Γ
T q dΩ + N T S dΓ Eq. 5•192
Ω Ω Γ

Eq. 5•190 to Eq. 5•192 can be re-written in matrix form as

Kb C T 0 θ̂ fθ
C H E T Sˆ = 0 Eq. 5•193
0 E 0 ŵ fw

Workbook of Applications in VectorSpace C++ Library 537


Chapter 5 Advanced Finite Element Methods
where

Kb = ∫ ( LNθ ) T DLNθ dΩ Eq. 5•194


C = – ∫ NST N θ dΩ Eq. 5•195


H = – ∫ N ST  --- N S dΩ
1
α 
Eq. 5•196

E = ∫ ( ∇Nw )T NS dΩ Eq. 5•197


fθ = ∫ NθT MΓ dΓ Eq. 5•198


Γ

fw = ∫ NwT q dΩ + ∫ NwT SΓ dΓ Eq. 5•199


Ω Γ

The condition for non-singular matrices parallel to that for the three-fields Hu-Washizu variational principle in
Eq. 5•63 is

n θ + n w ≥ n S , and n S ≥ n w Eq. 5•200

By inspecting Eq. 5•194 to Eq. 5•199, the first derivatives of θ and w exist. Therefore, C0-continuity need to be
satisfied for these two fields. S-field is taken as discontinuous, which has the potential advantage of being elim-
inated at the element level.1 The Program Listing 5•15 implements Heterosis element with θ-S-w mixed formu-
lation. The S-field is 4-node element by taking the four nodes at the 2 × 2 integration points. The results of the
mixed formulation is the same as the one with selective reduced integration on the shear constraint terms. In
plane elasticity, the equivalence theorem, in the previous chapter, was applied to (1) the selective reduced inte-
gration on the pressure constraint for the irreducible formulation, and (2) the pressure node taking on the Gauss
integration points for the mixed u-p formulation. Now, the equivalence theorem is applied to, the thick plate the-
ory, for (1) the θ-w irreducible formulation with the selective reduced integration on the shear constraint terms,
and (2) the θ-S-w mixed formulation where S-field is 4-node element with four nodes at the 2 × 2 integration
points. This equivalence theorem is illustrated in Figure 5•8.
The Program Listing 5•15 implement the mixed thick plate formulation (θ-S-w) for heterosis element. The
deflection is exactly the same as those obtained from the irreducible formulation.

1. p. 75 in Zienkiewicz, O.C. and R.L. Taylor, 1991, “The finite element method”, 4th ed., vol. 2. McGraw-Hill, Inc., UK.

538 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

θ-w irrducible formulation θ-S-w mixed formulation

:w :θ : 2x2 reduced integration on :S


shear constrinats related terms

Figure 5•8 The equivalence of selective reduced integration of the shear constraint
terms of the irreducible formulation and mixed formulation with shear force nodes at
Gauss integration points. Both formulation use the heterosis element which uses
serendipity element for w and Lagrangian element for θ.

Workbook of Applications in VectorSpace C++ Library 539


Chapter 5 Advanced Finite Element Methods

#include "include\fe.h"
#include "include\omega_h_n.h"
Matrix_Representation_Couple::assembly_switch
Matrix_Representation_Couple::Assembly_Switch = Matrix_Representation_Couple::ALL;
Omega_h_i::Omega_h_i(int i) : Omega_h(0) { Heterosis element; see Hughes[1987]
Ωθ; 9-node Lagrangian element
if(i == 0) {
int row_segment_no = 2;
int count = 0; Node *node; double v[2], h = 1.0/((double)(row_segment_no));
for(int j = 0; j < row_segment_no; j++) {
v[1] = ((double)j)*h;
for(int k = 0; k < (2*row_segment_no+1); k++) {
v[0] = ((double)k)*h/2; node = new Node(count++, 2, v); the_node_array.add(node);
}
v[1] += h/2.0;
for(int k = 0; k < (2*row_segment_no+1); k++) {
v[0] = ((double)k)*h/2; node = new Node(count++, 2, v); the_node_array.add(node);
}
}
v[1] = 1.0;
for(int j = 0; j < (2*row_segment_no+1); j++) {
v[0] = ((double)j)*h/2.0; node = new Node(count++, 2, v); the_node_array.add(node);
}
int ena[9]; Omega_eh *elem; count = 0;
for(int j = 0; j < row_segment_no; j++) for(int k = 0; k < row_segment_no; k++) {
int row_node_no = 2*row_segment_no+1, first_node = j*2*row_node_no+k*2;
ena[0] = first_node; ena[1] = ena[0]+2; ena[2] = ena[1]+2*row_node_no;
ena[3] = ena[2]-2; ena[4]=ena[0]+1;ena[5]=ena[1]+row_node_no;
ena[6]=ena[2]-1;ena[7]=ena[0]+row_node_no; ena[8] = ena[7] +1;
elem = new Omega_eh(count++, 0, 0, 9, ena); the_omega_eh_array.add(elem);

ΩS; 2 × 2-Gauss-point-node element


}
} else if(i == 1) {
int row_segment_no = 2; Node *node;
double inv_sqrt3 = 1.0/sqrt(3.0), v[2], xl[4][2],
h = 1.0/((double)(row_segment_no)), zai, eta;
xl[0][0] = 0.0; xl[0][1] = 0.0; xl[1][0] = h; xl[1][1] = 0.0;
four nodal coordinates for element # 0
xl[2][0] = h; xl[2][1] = h; xl[3][0] = 0.0; xl[3][1] = h; node # 0 natural coordinates
zai = - inv_sqrt3; eta = - inv_sqrt3; node # 0 physcal coordinates
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j]; make node and added to the database
node = new Node(0, 2, v); node_array().add(node); node # 1
zai = inv_sqrt3; eta = - inv_sqrt3;
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(1, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = inv_sqrt3;
node # 2
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(2, 2, v); node_array().add(node);
zai = - inv_sqrt3; eta = inv_sqrt3; node # 3
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(3, 2, v); node_array().add(node);
xl[0][0] = h; xl[0][1] = 0.0; xl[1][0] = 2.0*h; xl[1][1] = 0.0; element # 1
xl[2][0] = 2.0*h; xl[2][1] = h; xl[3][0] = h; xl[3][1] = h;

540 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
zai = - inv_sqrt3; eta = - inv_sqrt3; node # 4
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(4, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = - inv_sqrt3;
for(int j = 0; j < 2; j++)
node # 5
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(5, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = inv_sqrt3;
node # 6
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(6, 2, v); node_array().add(node);
zai = - inv_sqrt3; eta = inv_sqrt3; node # 7
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(7, 2, v); node_array().add(node);
xl[0][0] = 0.0; xl[0][1] = h; xl[1][0] = h; xl[1][1] = h; element # 2
xl[2][0] = h; xl[2][1] = 2.0*h; xl[3][0] = 0.0; xl[3][1] = 2.0*h;
zai = - inv_sqrt3; eta = - inv_sqrt3;
node # 8
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(8, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = - inv_sqrt3; node # 9
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(9, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = inv_sqrt3; node # 10
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(10, 2, v); node_array().add(node); node # 11
zai = - inv_sqrt3; eta = inv_sqrt3;
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(11, 2, v); node_array().add(node);
xl[0][0] = h; xl[0][1] = h; xl[1][0] = 2.0*h; xl[1][1] = h;
element # 3
xl[2][0] = 2.0*h; xl[2][1] = 2.0*h; xl[3][0] = h; xl[3][1] = 2.0*h;
zai = - inv_sqrt3; eta = - inv_sqrt3; node # 12
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(12, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = - inv_sqrt3;
for(int j = 0; j < 2; j++)
node # 13
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(13, 2, v); node_array().add(node);
zai = inv_sqrt3; eta = inv_sqrt3;
node # 14
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(14, 2, v); node_array().add(node);

Workbook of Applications in VectorSpace C++ Library 541


Chapter 5 Advanced Finite Element Methods
zai = - inv_sqrt3; eta = inv_sqrt3; node # 15
for(int j = 0; j < 2; j++)
v[j] = (1.0-zai)*(1.0-eta)/4.0*xl[0][j]+ (1.0+zai)*(1.0-eta)/4.0*xl[1][j]+
(1.0+zai)*(1.0+eta)/4.0*xl[2][j]+ (1.0-zai)*(1.0+eta)/4.0*xl[3][j];
node = new Node(15, 2, v); node_array().add(node);
for(int j = 0; j < 3; j++) { geometrical nodes; provide four corners
v[1] = h*(double)j;
for(int k = 0; k < 3; k++) { coordinates
int node_no = 16+j*3+k;
v[0] = h*(double)k;
node = new Node(node_no, 2, v);
node_array().add(node);
}
}
int ena[8]; Omega_eh *elem;
ena[0] = 0; ena[1] = 1; ena[2] = 2; ena[3] = 3;
ena[4] = 16; ena[5] = 17; ena[6] = 20; ena[7] = 19;
elem = new Omega_eh(0, 0, 0, 8, ena); the_omega_eh_array.add(elem);
ena[0] = 4; ena[1] = 5; ena[2] = 6; ena[3] = 7;
ena[4] = 17; ena[5] = 18; ena[6] = 21; ena[7] = 20;
elem = new Omega_eh(1, 0, 0, 8, ena); the_omega_eh_array.add(elem);
ena[0]=8;ena[1]=9;ena[2] = 10; ena[3] = 11;
ena[4] = 19; ena[5] = 20; ena[6] = 23; ena[7] = 22;
elem = new Omega_eh(2, 0, 0, 8, ena); the_omega_eh_array.add(elem);
ena[0]=12;ena[1]=13;ena[2]=14;ena[3]=15;
ena[4] = 20; ena[5] = 21; ena[6] = 24; ena[7] = 23;
elem = new Omega_eh(3, 0, 0, 8, ena); the_omega_eh_array.add(elem);
} else if(i == 2) { Ωw; 8-node serendipity element
int row_segment_no = 2, count = 0; Node *node;
double v[2], h = 1.0/((double)(row_segment_no));
for(int j = 0; j < row_segment_no; j++) {
v[1] = ((double)j)*h;
for(int k = 0; k < (2*row_segment_no+1); k++) {
v[0] = ((double)k)*h/2;
node = new Node(count++, 2, v); the_node_array.add(node);
}
v[1] += h/2.0;
for(int k = 0; k < row_segment_no+1; k++) {
v[0] = ((double)k)*h;
node = new Node(count++, 2, v); the_node_array.add(node);
}
}
v[1] = 1.0;
for(int j = 0; j < (2*row_segment_no+1); j++) {
v[0] = ((double)j)*h/2.0; node = new Node(count++, 2, v); the_node_array.add(node);
}
int ena[8]; Omega_eh *elem; count = 0;
for(int j = 0; j < row_segment_no; j++)
for(int k = 0; k < row_segment_no; k++) {
int first_node = j*(3*row_segment_no+2)+k*2;
ena[0]=first_node;ena[1]=ena[0]+2;
ena[2]=ena[1]+(3*row_segment_no+2); ena[3] = ena[2]-2;
ena[4] = ena[0]+1; ena[5] = (j+1)*(2*row_segment_no+1)+j*(row_segment_no+1)+k+1;
ena[6] = ena[3]+1; ena[7] = ena[5]-1;
elem = new Omega_eh(count++, 0, 0, 8, ena); the_omega_eh_array.add(elem);
}
}
}

542 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
gh_on_Gamma_h_i::gh_on_Gamma_h_i(int i, int df, Omega_h& omega_h) : gh_on_Gamma_h() {
gh_on_Gamma_h::__initialization(df, omega_h);
if(i == 0) {
θ=0
the_gh_array[node_order(4)](0) = the_gh_array[node_order(4)](1) = right
the_gh_array[node_order(9)](0) = the_gh_array[node_order(9)](1) =
the_gh_array[node_order(14)](0) = the_gh_array[node_order(14)](1) =
the_gh_array[node_order(19)](0) = the_gh_array[node_order(19)](1) =
the_gh_array[node_order(20)](0) = the_gh_array[node_order(20)](1) = top
the_gh_array[node_order(21)](0) = the_gh_array[node_order(21)](1) =
the_gh_array[node_order(22)](0) = the_gh_array[node_order(22)](1) =
the_gh_array[node_order(23)](0) = the_gh_array[node_order(23)](1) =
the_gh_array[node_order(24)](0) = the_gh_array[node_order(24)](1) =
the_gh_array[node_order(0)](1) = the_gh_array[node_order(1)](1) = bottom
the_gh_array[node_order(2)](1) = the_gh_array[node_order(3)](1) =
the_gh_array[node_order(0)](0) = the_gh_array[node_order(5)](0) =
the_gh_array[node_order(10)](0) = the_gh_array[node_order(15)](0) = left
gh_on_Gamma_h::Dirichlet;
} else if(i == 1) {
S-field disable all geometrical nodes
for(int j = 16; j <= 24; j++)
for(int k = 0; k < df; k++) {
the_gh_array[node_order(j)](k) = gh_on_Gamma_h::Dirichlet;
}
} else if(i == 2) {
the_gh_array[node_order(4)](0) = w=0
the_gh_array[node_order(7)](0) = right
the_gh_array[node_order(12)](0) =
the_gh_array[node_order(15)](0)=
the_gh_array[node_order(16)](0) =
the_gh_array[node_order(17)](0)= top
the_gh_array[node_order(18)](0) =
the_gh_array[node_order(19)](0)=
the_gh_array[node_order(20)](0) = gh_on_Gamma_h::Dirichlet;
}
}
static Global_Discretization *theta_type = new Global_Discretization;
static Global_Discretization *S_type = new Global_Discretization;
static Global_Discretization_Couple *S_theta_type = new Global_Discretization_Couple;
static Global_Discretization_Couple *w_S_type = new Global_Discretization_Couple;
class Plate_Heterosis : public Element_Formulation_Couple {
public:
Plate_Heterosis(Element_Type_Register a) : Element_Formulation_Couple(a) {}
Element_Formulation *make(int, Global_Discretization&);
Plate_Heterosis(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&);
Plate_Heterosis(int, Global_Discretization_Couple&);
};
Element_Formulation* Plate_Heterosis::make(int en, Global_Discretization& gd) {
return new Plate_Heterosis(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)));
static const double Dv[3][3] = {
{D_, D_*v_, 0.0 },
1 ν 0
{D_*v_, D_, 0.0 }, Et 3 ν 1 0
D = -------------------------
{0.0, 0.0, D_*(1-v_)/2.0 } 12 ( 1 – ν 2 ) 1–ν
}; 0 0 ------------
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
2
static const double mu_ = E_/(2*(1+v_)); static const double alpha_ = (5.0/6.0)*mu_*t_;

Workbook of Applications in VectorSpace C++ Library 543


Chapter 5 Advanced Finite Element Methods
Plate_Heterosis::Plate_Heterosis(int en, Global_Discretization& gd) :
Element_Formulation_Couple(en, gd) {

Kb matrix; θ-field
if(gd.type() == theta_type) {
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp), Zai, Eta, 3x3 Gauss integration for bending
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 9, 2, qp);
Zai &= Z[0]; Eta &= Z[1];
Lagrangian shape functions for θ dof
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;
N[8] = (1-Zai.pow(2))*(1-Eta.pow(2));
N[0] -= N[8]/4; N[1] -= N[8]/4; N[2] -= N[8]/4; N[3] -= N[8]/4;
N[4] = ((1-Zai.pow(2))*(1-Eta)-N[8])/2; N[5] = ((1-Eta.pow(2))*(1+Zai)-N[8])/2;
N[6] = ((1-Zai.pow(2))*(1+Eta)-N[8])/2; N[7] = ((1-Eta.pow(2))*(1-Zai)-N[8])/2;
N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2;
N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dV(d(X).det());
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), Wx, Wy, B;
Wx &= W_x[0][0]; Wy &= W_x[0][1];
B &= (~Wx || C0(0.0)) &
(C0(0.0) || ~Wy ) & Kb = ∫ ( LNθ ) T DLNθ dΩ
(~Wy || ~Wx ); Ω
stiff &= (~B)*(D*B) | dV;
} else if(gd.type() == S_type) {
H matrix; S-field
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.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0; bilinear corner node shape function for
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0; coordinate transformation
C0 x = MATRIX("int, int, C0&, int, int", 4, 2, xl, 4, 0);
H1 X = N*x; J dv(d(X).det());
double sqrt3 = sqrt(3.0); C0 zero(0.0);
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4, qp), n0, n1, n2, n3, zai, eta;
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]);
n[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; n[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0;
n[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; n[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0; S-shape functions:
n0 &= n[0]; n1 &= n[1]; n2 &= n[2]; n3 &= n[3]; bilinear four Gauss point nodes
H0 N_S = (( n0 | zero | n1 | zero | n2 | zero | n3 | zero ) &
(zero | n0 | zero | n1 | zero | n2 | zero | n3 ));
stiff &= MATRIX("int, int", 16, 16);
H = – ∫ N ST  --- N S dΩ
C0 stiff_sub = MATRIX("int, int, C0&, int, int", 8, 8, stiff, 0, 0);
1
stiff_sub = -( ((~N_S) * N_S) | dv)/alpha_; α 

}
}
Element_Formulation_Couple* Plate_Heterosis::make(
int en, Global_Discretization_Couple& gdc) { return new Plate_Heterosis(en,gdc); }
Plate_Heterosis::Plate_Heterosis(int en, Global_Discretization_Couple& gdc) :
Element_Formulation_Couple(en, gdc) {
if(gdc.type() == S_theta_type) { C matrix; S-θ couple
Quadrature qp(2, 9);
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]; bilinear corner node shape function for
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
coordinate transformation
C0 x = MATRIX("int, int, C0&, int, int", 4, 2, xl, 4, 0);
H1 X = N*x;
J dv(d(X).det());

544 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
double sqrt3 = sqrt(3.0); C0 zero(0.0);
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4, qp), n0, n1, n2, n3, zai, eta;
zai &= ((H0)Z[0]); eta &= ((H0)Z[1]);
n[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; n[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0; S-shape functions:
n[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; n[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0; bilinear four Gauss point nodes
n0 &= n[0]; n1 &= n[1]; n2 &= n[2]; n3 &= n[3];
H0 N_S = (( n0 | zero | n1 | zero | n2 | zero | n3 | zero ) &
(zero | n0 | zero | n1 | zero | n2 | zero | n3 )); NS
H0 nt = INTEGRABLE_VECTOR("int, Quadrature", 9, qp),

θ-shape functions:
nt0, nt1, nt2, nt3, nt4, nt5, nt6, nt7, nt8;
nt[0] = (1-zai)*(1-eta)/4; nt[1] = (1+zai)*(1-eta)/4;
nt[2] = (1+zai)*(1+eta)/4; nt[3] = (1-zai)*(1+eta)/4; Lagrangian shape function
nt[8] = (1-zai.pow(2))*(1-eta.pow(2));
nt[0] -= nt[8]/4; nt[1] -= nt[8]/4; nt[2] -= nt[8]/4; nt[3] -= nt[8]/4;
nt[4] = ((1-zai.pow(2))*(1-eta)-nt[8])/2; nt[5] = ((1-eta.pow(2))*(1+zai)-nt[8])/2;
nt[6] = ((1-zai.pow(2))*(1+eta)-nt[8])/2; nt[7] = ((1-eta.pow(2))*(1-zai)-nt[8])/2;
nt[0] -= (nt[4]+nt[7])/2; nt[1] -= (nt[4]+nt[5])/2;
nt[2] -= (nt[5]+nt[6])/2; nt[3] -= (nt[6]+nt[7])/2;
nt0 &= nt[0]; nt1 &= nt[1]; nt2 &= nt[2]; nt3 &= nt[3]; nt4 &= nt[4];
nt5 &= nt[5]; nt6 &= nt[6]; nt7 &= nt[7]; nt8 &= nt[8];
H0 N_theta=((nt0|zero|nt1|zero|nt2|zero|nt3|zero|nt4|zero|nt5|zero|nt6|zero|nt7|zero|nt8|zero)& Nθ
(zero|nt0|zero|nt1|zero|nt2|zero|nt3|zero|nt4|zero|nt5|zero|nt6|zero|nt7|zero|nt8));
stiff &= MATRIX("int, int", 16, 18);
C0 stiff_sub = MATRIX("int, int, C0&, int, int", 8, 18, stiff, 0, 0); C = – ∫ N ST N θ dΩ
stiff_sub = -( ((~N_S) * N_theta) | dv);

} else if(gdc.type() == w_S_type) {
Quadrature qp(2, 9);
H1 Z(2, (double*)0, qp), , Zai, Eta E matrix; w-S couple
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 8, 2, qp); w shape function:
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4; serendipity shape functions
N[2] = (1+Zai)*(1+Eta)/4; N[3] = (1-Zai)*(1+Eta)/4;
N[4] = (1-Zai.pow(2))*(1-Eta)/2; N[5] = (1-Eta.pow(2))*(1+Zai)/2;
N[6] = (1-Zai.pow(2))*(1+Eta)/2; N[7] = (1-Eta.pow(2))*(1-Zai)/2;
N[0] -= (N[4]+N[7])/2; N[1] -= (N[4]+N[5])/2; N[2] -= (N[5]+N[6])/2; N[3] -= (N[6]+N[7])/2;
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dv(d(X).det());
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2/*nsd*/, Nx), Wx, Wy, grad_W;
Wx &= W_x[0][0]; Wy &= W_x[0][1];
grad_W &= (~Wx) & ∇w
(~Wy);
double sqrt3 = sqrt(3.0);
C0 zero(0.0);
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4, qp), n0, n1, n2, n3, zai, eta;
zai &= ((H0)Z)[0]; eta &= ((H0)Z)[1]; S-shape functions:
n[0] = (1.0-sqrt3*zai)*(1.0-sqrt3*eta)/4.0; n[1] = (1.0+sqrt3*zai)*(1.0-sqrt3*eta)/4.0; bilinear four Gauss point nodes
n[2] = (1.0+sqrt3*zai)*(1.0+sqrt3*eta)/4.0; n[3] = (1.0-sqrt3*zai)*(1.0+sqrt3*eta)/4.0;
n0 &= ((H0)n)[0]; n1 &= ((H0)n)[1]; n2 &= ((H0)n)[2]; n3 &= ((H0)n)[3];
H0 n_S = (n0|zero|n1|zero|n2|zero|n3|zero) & NS
(zero|n0|zero|n1|zero|n2|zero|n3);
stiff &=MATRIX("int, int", 8, 16);
C0 stiff_sub=MATRIX("int, int, C0&, int, int", 8, 8, stiff, 0, 0); E = ∫ ( ∇Nw )T NS dΩ
stiff_sub = ( (~grad_W) * n_S) | dv; Ω
double f_0 = 1.0;
force &= (((H0)N)*f_0) | dv;
} fw = ∫ NwT q dΩ
} Ω
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static Plate_Heterosis plate_heterosis_instance(element_type_register_instance);

Workbook of Applications in VectorSpace C++ Library 545


Chapter 5 Advanced Finite Element Methods
int main() {

θ
const int theta_ndf = 2;
Omega_h_i oh_theta(0);
gh_on_Gamma_h_i theta_gh(0, theta_ndf, oh_theta);
U_h theta_h(theta_ndf, oh_theta);
Global_Discretization theta_gd(oh_theta, theta_gh, theta_h, theta_type);
const int S_ndf = 2; S
Omega_h_i oh_S(1);
gh_on_Gamma_h_i S_gh(1, S_ndf, oh_S);
U_h S_h(S_ndf, oh_S);
Global_Discretization S_gd(oh_S, S_gh, S_h, S_type);
const int w_ndf = 1; w
Omega_h_i oh_w(2);
gh_on_Gamma_h_i w_gh(2, w_ndf, oh_w);
U_h w_h(w_ndf, oh_w);
Global_Discretization w_gd(oh_w, w_gh, w_h); S-θ
Global_Discretization_Couple gdc_S_theta(S_gd, theta_gd, S_theta_type);
w−S
Global_Discretization_Couple gdc_w_S(w_gd, S_gd, w_S_type);
Matrix_Representation mr_theta(theta_gd); Matrix_Representation mr_S(S_gd);
Matrix_Representation_Couple mrcE(gdc_w_S,0, 0,&(mr_S.rhs()),&mr_S);
Matrix_Representation_Couple mrcC(gdc_S_theta,0,
&(mr_S.rhs()),&(mr_theta.rhs()), &mr_theta);
mr_theta.assembly();
mr_S.assembly();
mrcC.assembly();
Kb ,
mrcE.assembly();
C0 K = ((C0)(mr_theta.lhs())), H,
H = ((C0)(mr_S.lhs())), C,
C = ((C0)(mrcC.lhs())),

f_theta = ((C0)(mr_theta.rhs())),
E = ((C0)(mrcE.lhs())), E
f_S = ((C0)(mr_S.rhs())), fS
f_w = ((C0)(mrcE.rhs()));
fw
Cholesky dK(K);
C0 K_inv = dK.inverse(),
CK_inv = C*K_inv,
A = H-CK_inv*(~C);
Cholesky dnA(-A);
C0 A_inv = -(dnA.inverse()),
EA_inv = E*A_inv, ŵ =
EA_invEt = EA_inv*(~E);
(EA-1ET)-1(EA-1fS - EA-1CKb-1fθ−fw)
Cholesky dnEA_invEt(-EA_invEt); ˆ
C0 w = -(dnEA_invEt*( E*-(dnA*f_S) - E*-(dnA*(C*(dK*f_theta))) -f_w) ), S = A-1(fS−ET ŵ −CKb-1fθ)
S = -(dnA*(f_S-(~E)*w-C*(dK*f_theta))),
theta = dK*(f_theta-(~C)*S);
theta_h = theta;
θ̂ = Kb-1(fθ-CT Sˆ )
theta_h = theta_gd.gh_on_gamma_h();
cout << "rotation (theta_x, theta_y):" << endl;
for(int i = 0; i < theta_h.total_node_no(); i++) cout << theta_h[i] << endl;
S_h = S;
S_h = S_gd.gh_on_gamma_h();
cout <<"shear force S:" << endl << S_h << endl;
w_h = w;
w_h = w_gd.gh_on_gamma_h();
cout << "deflection w:" << endl << w_h << endl;
return 0; }

Listing 5•15 Heterosis element for θ-S-w mixed thick plate formulation (project: “mixed_thick_plate” in
project workspace file “fe.dsw”).

546 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
Collocation of Shear Constraints on Reissner-Mindlin Plate
The so-called discrete Reissner-Mindlin method is to use the collocation method to enforce the shear con-
straints on the element boundaries. We consider a simple modification from the θ-w irreducible thick plate for-
mulation to enforce the shear constraints. The shear force on the boundaries of an element is, from Eq. 5•184,

S = α [ ∇w – θ ] Eq. 5•201

For a rectangular plate with edges parallel to x and y axis (Figure 5•9a), these constraints, on the mid-side nodes
of an element boundaries, can be easily computed from bilinear four-node shape functions for both θ and w as 1

w 1 – w 0 θx + θ x
0 1
ˆ4
S x = α ------------------- – ------------------
a 2

w2 – w 1 θy + θy
1 2
ˆ5
S y = α ------------------- – ------------------
b 2

w2 – w 3 θx + θx
2 3
ˆ6
S x = α ------------------- – ------------------
a 2

w 3 – w0 θy + θy
3 0
ˆ7
S y = α ------------------- – ------------------ Eq. 5•202
b 2

Eq. 5•202 can be written in matrix form

3 6
2
NS
x
4 = (1−η)/2

b 7 5
NS 5 = (1+ξ)/2
y

1
0 4 NS 6 = (1+η)/2
x
a

: θ and w : Sx : Sy
NS
y
7 = (1−ξ)/2
(a)
(b)
Figure 5•9 Discrete Reissner-Mindlin method for rectangular element with bilinear four-node
shape functions for both θ and w, and four shear constraints enforced on the element boundaries.

1. p.87-88 in Zienkiewicz, O.C., and R.L. Taylor, 1991, “ The finite element method: solid and fluid mechanics, dynamics,
and non-linearity”, 4-th eds., McGraw-Hill Inc., UK.

Workbook of Applications in VectorSpace C++ Library 547


Chapter 5 Advanced Finite Element Methods

S = α [ Q w ŵ – Q θ θ̂ ]
ˆ
Eq. 5•203

where

–1 1 –1 –1
------ --- 0 0 ------ 0 ------ 0 0 0 0 0
a a 2 2
1 –1 –1 –1
0 --- ------ 0 0 0 0 ------ 0 ------ 0 0
b b 2 2
Qw = , Qθ = Eq. 5•204
1 –1 –1 –1
0 0 --- ------ 0 0 0 0 ------ 0 ------ 0
a a 2 2
1 –1 –1 –1
--- 0 0 ------ 0 ------ 0 0 0 0 0 ------
b b 2 2

S =NS Sˆ with NS shape functions for these four mid-side nodes are shown in Figure 5•9(b). The matrix form of
the problem becomes

T
K ww K wθ ŵ fw
= Eq. 5•205
K wθ K θθ θ̂ fθ

where

K ww = ∫ ( NS Qw ) T αNS Qw dΩ Eq. 5•206


K wθ = ∫ ( NS Qw )T αNS Qθ dΩ Eq. 5•207


K θθ = ∫ [ ( LNθ ) T DLNθ + ( NS Qθ )T αNS Qθ ] dΩ Eq. 5•208


fw and fθ are the same as those defined in the irreducible formulation. The Program Listing 5•16 implements the
discrete Reissner-Mindlin formulation in the above. The center deflection for this bilinear four-node element
with discrete shear constraints is 325634, which is greater than the analytical solution for the thin-plate theory of
226800.

548 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods

#include "include\fe.h"
#include "include\omega_h_n.h"
Matrix_Representation_Couple::assembly_switch
Matrix_Representation_Couple::Assembly_Switch = Matrix_Representation_Couple::ALL;
EP::element_pattern EP::ep = EP::QUADRILATERALS_4_NODES; Ωθ & Ωw; bilinear 4-node element
static int row_node_no = 9; static int col_node_no = row_node_no;
Omega_h_i::Omega_h_i(int i) : Omega_h(0) {
if(i == 0 || i == 1) {
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_i::gh_on_Gamma_h_i(int i, int df, Omega_h& omega_h) : gh_on_Gamma_h() {
gh_on_Gamma_h::__initialization(df, omega_h);
boundary conditions
if(i == 0) {
for(int j = 0; j < row_node_no; j++) {
for(int k = 0; k < df; k++) {
the_gh_array[node_order((j+1)*row_node_no-1)](k) =
the_gh_array[node_order(row_node_no*(row_node_no-1)+j)](k) =
gh_on_Gamma_h::Dirichlet;
}
the_gh_array[node_order(j)](1) = gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(j*row_node_no)](0) = gh_on_Gamma_h::Dirichlet;
}
} else if(i == 1) {
for(int j = 0; j < 9; j++) {
the_gh_array[node_order((j+1)*row_node_no-1)](0) =
the_gh_array[node_order(row_node_no*(row_node_no-1)+j)](0) =
gh_on_Gamma_h::Dirichlet;
}
}
}
static Global_Discretization *w_type = new Global_Discretization;
static Global_Discretization *theta_type = new Global_Discretization;
class Plate_Discrete_Reissner_Mindlin : public Element_Formulation_Couple {
public:
Plate_Discrete_Reissner_Mindlin(Element_Type_Register a) :
Element_Formulation_Couple(a) {}
Element_Formulation *make(int, Global_Discretization&);
Plate_Discrete_Reissner_Mindlin(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&);
Plate_Discrete_Reissner_Mindlin(int, Global_Discretization_Couple&);
};
Element_Formulation* Plate_Discrete_Reissner_Mindlin::make( int en,
Global_Discretization& gd) { return new Plate_Discrete_Reissner_Mindlin(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)));
static const double Dv[3][3] = { 1 ν 0
{D_, D_*v_, 0.0 }, Et 3 ν 1 0
{D_*v_, D_, 0.0 }, D = -------------------------
{0.0, 0.0, D_*(1-v_)/2.0} 12 ( 1 – ν 2 ) 1–ν
0 0 ------------
}; 2
C0 D = MATRIX("int, int, const double*", 3, 3, Dv[0]);
static const double mu_ = E_/(2*(1+v_));
static const double alpha_ = (5.0/6.0)*mu_*t_;

Workbook of Applications in VectorSpace C++ Library 549


Chapter 5 Advanced Finite Element Methods
Plate_Discrete_Reissner_Mindlin::Plate_Discrete_Reissner_Mindlin(int en,
Global_Discretization& gd) : Element_Formulation_Couple(en, gd) {
if(gd.type() == theta_type) { Kθθ matrix;
Quadrature qp(2, 4);
H1 Z(2, (double*)0, qp), Zai, Eta, θ-shape functions:
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 4, 2, qp); Nθ
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1-Zai)*(1-Eta)/4; N[1] = (1+Zai)*(1-Eta)/4; bilinear four-node shape function
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());
H0 W_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, nsd, Nx), Wx, Wy, B;
Wx &= W_x[0][0]; Wy &= W_x[0][1];
B &= (~Wx || C0(0.0)) & (C0(0.0) || ~Wy ) & (~Wy || ~Wx );
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4, qp), zai, eta;
zai = ((H0)Z)[0]; eta &= ((H0)Z)[1]; S-shape functions:
n[0] = (1-eta)/2.0; n[1] = (1+zai)/2.0; n[2] = (1+eta)/2.0; n[3] = (1-zai)/2.0; NS
C0 half(0.5), zero(0.0);
C0 Q_theta = ( -half | zero | -half | zero | zero | zero | zero | zero ) & Qθ
( zero | zero | zero | -half | zero | -half | zero | zero ) &
( zero | zero | zero | zero | -half | zero | -half | zero ) & K θθ = ∫ ( ( LN θ ) T DLN θ ) dΩ +
( zero | -half | zero | zero | zero | zero | zero | -half );
stiff &= ((~B)*(D*B)+(((~Q_theta)*n)*((~n)*Q_theta))/alpha_) | dv; Ω
} else if (gd.type() == w_type) {
Quadrature qp(2, 4);
H1 Z(2, (double*)0, qp), Zai, Eta,
∫ ( NS Qθ ) T αNS Qθ dΩ

N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE(
"int, int, Quadrature", 4, 2, qp); Kww matrix
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
H1 X = N*xl; J dv(d(X).det());
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4, qp), zai, eta; S-shape functions:
zai = ((H0)Z)[0]; eta &= ((H0)Z)[1];
n[0] = (1-eta)/2.0; n[1] = (1+zai)/2.0; n[2] = (1+eta)/2.0; n[3] = (1-zai)/2.0; NS
C0 a_inv(1.0/norm(xl[1]-xl[0])), b_inv(1.0/norm(xl[2]-xl[1])), zero(0.0); Qw
C0 Q_w = ( -a_inv | a_inv | zero | zero ) &
( zero | b_inv | -b_inv | zero ) &
( zero | zero | a_inv | -a_inv ) &
( b_inv | zero | zero | -b_inv );

}
stiff &= ( ( ((~Q_w)*n)*((~n)*Q_w) ) | dv )/alpha_; K ww = ∫ ( NS Qw )T αNS Qw dΩ
} Ω
Element_Formulation_Couple* Plate_Discrete_Reissner_Mindlin::make(int en,
Global_Discretization_Couple& gdc) {
return new Plate_Discrete_Reissner_Mindlin(en,gdc);
}
Plate_Discrete_Reissner_Mindlin::Plate_Discrete_Reissner_Mindlin(int en,
Global_Discretization_Couple& gdc) : Element_Formulation_Couple(en, gdc) {
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.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
H1 X = N*xl; J dv(d(X).det()); S-shape functions:
H0 n = INTEGRABLE_VECTOR("int, Quadrature", 4, qp), zai, eta; NS
zai = ((H0)Z)[0]; eta &= ((H0)Z)[1];
n[0] = (1-eta)/2.0; n[1] = (1+zai)/2.0; n[2] = (1+eta)/2.0; n[3] = (1-zai)/2.0;

550 Workbook of Applications in VectorSpace C++ Library


Mixed and Hybrid Finite Element Methods
C0 a_inv(1.0/norm(xl[1]-xl[0])), b_inv(1.0/norm(xl[2]-xl[1])), half(0.5), zero(0.0);
C0 Q_w = ( -a_inv | a_inv | zero | zero ) &
( zero | b_inv | -b_inv | zero ) & Qw
( zero | zero | a_inv | -a_inv ) &
( b_inv | zero | zero | -b_inv ); Qθ
C0 Q_theta = ( -half | zero | -half | zero | zero | zero | zero | zero ) &
( zero | zero | zero | -half | zero | -half | zero | zero ) & K wθ = ∫ ( NS Qw )T αNS Qθ dΩ
( zero | zero | zero | zero | -half | zero | -half | zero ) & Ω
( zero | -half | zero | zero | zero | zero | zero | -half );
stiff &= ( ( ((~Q_w)*n)*((~n)*Q_theta) ) | dv )/alpha_;
double f_0 = 1.0; fw = ∫ NwT q dΩ
force &= (((H0)N)*f_0) | dv; Ω
}
Element_Formulation* Element_Formulation::type_list = 0;
static Element_Type_Register element_type_register_instance;
static Plate_Discrete_Reissner_Mindlin
plate_discrete_reissner_mindlin_instance(element_type_register_instance);
int main() {
const int theta_ndf = 2;
Omega_h_i oh_theta(0);
gh_on_Gamma_h_i theta_gh(0, theta_ndf, oh_theta);
U_h theta_h(theta_ndf, oh_theta);
Global_Discretization theta_gd(oh_theta, theta_gh, theta_h, theta_type);
const int w_ndf = 1;
Omega_h_i oh_w(1);
gh_on_Gamma_h_i w_gh(1, w_ndf, oh_w);
U_h w_h(w_ndf, oh_w);
Global_Discretization w_gd(oh_w, w_gh, w_h, w_type);
Global_Discretization_Couple gdc(w_gd, theta_gd);
Matrix_Representation mr_theta(theta_gd);
Matrix_Representation mr_w(w_gd);
Matrix_Representation_Couple mrc(gdc, 0, &(mr_w.rhs()), &(mr_theta.rhs()), &mr_theta);
mr_w.assembly();
mr_theta.assembly();
mrc.assembly(); Kθθ
C0 Ktt = ((C0)(mr_theta.lhs())), Kww
Kww = ((C0)(mr_w.lhs())), Kwθ
Kwt = ((C0)(mrc.lhs())),
f_theta = ((C0)(mr_theta.rhs())), fθ
f_w = ((C0)(mr_w.rhs())); fw
Cholesky dKww(Kww);
C0 Kww_inv = dKww.inverse(),
KwttKww_invKwt = (~Kwt)*(Kww_inv*Kwt),
K = Ktt-KwttKww_invKwt,
f = f_theta-(~Kwt)*(dKww*f_w);
Cholesky dK(K);
θ̂ = [Kθθ−(Kwθ)T(Kww)-1Kwθ] -1
C0 theta = dK*f; [fθ−(Kwθ)T(Kww)-1fw ]
theta_h = theta; ŵ = (Kww) [fw-(Kwθ)Tθ̂ ]
-1
theta_h = theta_gd.gh_on_gamma_h();
cout << "rotation:" << endl << theta_h << endl;
C0 w = dKww*(f_w-Kwt*theta);
w_h = w;
w_h = w_gd.gh_on_gamma_h();
cout << "deflection:" << endl << w_h << endl;
return 0;
}

Listing 5•16 Discrete Reissner-Mindlin plate formulation (project: “discrete_reissner_mindlin” in project


workspace file “fe.dsw”).

Workbook of Applications in VectorSpace C++ Library 551


Chapter 5 Advanced Finite Element Methods
5.2 Contact Mechanics
With the development in the hybrid irreducible subdomain formulation in Section 5.1.3, we are most ready to
tackle the contact problem. The contact problem in this section is assumed to be frictionless. Friction makes the
contact problem becomes a material nonlinear one. We begin the nonlinear problem on the subject of elastoplas-
ticity in Section 5.3. We do not include the frictional case in this workbook, because the best understanding on
the treatment of the “stick-slip” friction on the contact surface is considered as a degeneration, 1 dimensionless,
from that of the elastoplastic behavior on a continuum.

5.2.1 Basic Physics


The stress tensor σ0 at the point x0 on subdomain Ω0 is related to the traction t0 according to Cauchy’s for-
mula1 (see Figure 5•4)

t0 = n σ0 Eq. 5•209

where n is the outward unit surface normal at the point x0 on subdomain Ω0. Since the outward unit surface nor-
mal at the point x1 on subdomain Ω1 is opposite in direction to n. We also have

t1 = (- n) σ1 Eq. 5•210

Because we assumed the contact is frictionless, only the normal component of the tractions, t 0 • n = – t 1 • n ≡ λ ,
on the contact surface are required to be in equilibrium according to Newton’s third law of motion. The contact
conditions on contact surface Γc is2

0
g ≡ [ u1 – u 0 ] • n + g = 0 ⇒ t0 • n = – t1 • n < 0
0
g ≡ [ u1 – u 0 ] • n + g > 0 ⇒ t 0 • n = –t 1 • n = 0 Eq. 5•211

where “g” is the “gap” between the two bodies, and “g0” is the initial gap. In constraint optimization (see Eq.
2•14 in Chapter 2), Eq. 5•211 can be concisely written as the Kuhn-Tucker condition

g λ = 0, λ ≤ 0, g ≥ 0 Eq. 5•212

We note that λ, the normal component of the tractions, also plays the role of the Lagrange multipliers in the con-
text of constraint optimization problem. The geometry of contact element is shown in Figure 5•10. The node
number “8”, with coordinates x11, is projected to its opposite boundary on x0. The segment length proportional
parameter α is computed from

1. see p. 63 in Fung Y.C., 1965, “Foundations of solid mechanics”, Prentice-Hall, Inc., Englewood Cliffs, New Jersey.
2. Simo, J.C. Wriggers, P., and R.L.Taylor, 1985, “A perturbed Lagrangian formulation for the finite element solution of
contact problem”, Computer Methods in Applied Mechanics and Engineering, vol. 50, p. 163-180, Elsevier Science Publish-
ers, North-Holland.

552 Workbook of Applications in VectorSpace C++ Library


Contact Mechanics

9x1
nodes
6 Ω1 x11 1

projected x10
7
nodes
n0 8
y2
contact element n1
1 numbers 0 5
1 2 4 y1
3 4x 1
unit surface 3 0
normal 1 x00 x00 5
2
Ωi domains 0
Ω0 αl
l = ||x01-x00||
Figure 5•10 Contact element geometry. Six contact line elements are formed
through projection of real nodes onto the boundary of the opposite boundaries.

( x 10 – x 00 ) • ( x 01 – x 00 )
α = -------------------------------------------------- Eq. 5•213
x 01 – x 00

and the coordinates of the projected point x00, and displacement on the projected node u0 can be linearly interpo-
lated from the level rule as

0
x 0 = ( 1 – α )x 00 + αx 01, and u 0 = ( 1 – α )u 00 + αu 01 Eq. 5•214

For the contact element number “5” in Figure 5•10 we recognize that, on Ω0, the node numbers “3”, “4”, in glo-
bal node numbering, are involved in the element. The contact element node on Ω0 associated with the current
element number “5” has the coordinates of {x00, x01}. The displacement associated with these two nodes is
denoted as u = [u00, u01]T which is related to the displacement of “real” nodes (global node numbers “3” and “4”,
0 1
and redefined as local element node number “0” and “1”) û = [ û 0 , û0 ]T by the same level rule as

0 0
u0 ( 1 – α 0 )n 0 α0 n0 û 0
u= = = cû Eq. 5•215
u0
1 α1 n1 ( 1 – α 1 )n 1 û 1
0

where α0 for node number “3” is computed from Eq. 5•213, and α1= 0 (consistent with Eq. 5•213) for node num-
ber “4” which is not a projected node. The initial gap, g0, and the tangent vector, s, along boundary Γ34 are

0 ( x 01 – x 00 )
g = x 10 – x 0 , s = ---------------------
- Eq. 5•216
x 01 – x 00

The outward unit surface normal vector, n0, is defined from rotating the tangent vector 90o counter-clockwise

Workbook of Applications in VectorSpace C++ Library 553


Chapter 5 Advanced Finite Element Methods

π π
cos --- sin --- s
2 2 x
n0 = Rs = = [sy, -sx]T Eq. 5•217
π π sy
– sin --- cos ---
2 2
The Lagrangian functional to the contact problem is (with domain index i = 0, 1)

( u 0, u 1, λ ) = --- ∫ ( Lu i ) T D Lu i dΩ – ∫ uiT b dΩ – ∫ uiT h i dΓ + ∫ λT { [ u2 – u1 ] • n + g


1 0
} dΓ Eq. 5•218
2
Ωi Ωi Γh Γc
i

The Euler-Lagrange equations are obtained by taking the directional derivatives of Lagrangian functional with
respect to ui and λ, then, set to zero.

∫ δuiT L T ( D Lui ) dΩ – ∫ δuiT b dΩ – ∫ δuiT hi dΓ + ∫ { [ δu2T – δu1T ] • n } λ dΓ = 0 Eq. 5•219


Ωi Ωi Γi Γc

∫ δ λT { [ u2 – u1 ] • n + g 0 } dΓ = 0 Eq. 5•220
Γc

The first three terms in Eq. 5•219 are the same as the standard irreducible formulation. The last term in Eq.
5•219 and Eq. 5•220 are needed for the contact formulation. The Lagrangian multiplier and the displacement (on
the contact elements) are approximated as linear line element from their nodal values (with “hat”)

λ = Nλa λ̂ a, u = N a u a = N a cû a = N ua û a Eq. 5•221

where N ua ≡ N a c ; i.e., the projected displacement u of contact nodes is related to u of global nodes by Eq. 5•215.
The off-diagonal element stiffness submatrices in the mixed formulation of Eq. 5•219 and Eq. 5•220 are

Q = ∫ NλT Nu dΓ Eq. 5•222


Γc

Eq. 5•222 adds nothing new from mixed formulation point of view. What is new to the program is we need to
feed the information, at the element level, on the corresponding λ and u on domain Ω0 and Ω1 that are associ-
ated with the particular contact segment.

5.2.2 Frictionless Rigid Punch On Ealstic Foundation


To bypass, for the moment, the difficulty in dealing with the active set method (page 118 in Chapter 2) to
implement the Kuhn-Tucker condition in Eq. 5•212 by inspecting an example with a rigid punch on an elastic
foundation. In this example, The contact geometry does not change significantly during deformation. The con-
tact geometry (element-node connection, projection parameters α) is defined manually at the beginning. In the
next section, the contact searching algorithm is to be implemented, which automatically re-defines the changing
contact geometry at each step.

554 Workbook of Applications in VectorSpace C++ Library


Contact Mechanics
The present example is illustrated in Figure 5•11a. The contact elements of this problem are illustrated in
details in Figure 5•11b. The two domains Ωrigid punchand Ωelastic foundation are defined as they are defined in stan-
dard irreducible formulation. In addition, from Figure 5•11b, we need to define Γrigid punch , and Γelastic foundation
which are the potential contact boundaries on Ωrigid punch and Ωelastic foundation, respectively. The contact ele-
ments per se are lied on an imaginary surface Γc in the middle of Γrigid punch and Γelastic foundation. The off-diago-
nal element stiffness submatrix C from Eq. 5•222 are contributed by the degree of freedoms (λ-ui) from the
Global_Discretization_Couple consists of Γc-Γi.
Γi are boundaries which consist of line elements which share some common variables with the boundary of
Ωi. Γi can be defined similar to regular domains Ωi without instantiating variables of their own. Lines 2-13 is to
instantiate objects for the elastic foundation (the lower part).

1 int ndf = 2;
2 Omega_h_i elastic_foundation_oh(0); // elastic foundation; Ω elastic foundation
3 gh_on_Gamma_h_i elastic_foundation_gh(0, ndf, elastic_foundation_oh);
4 U_h u_0(ndf, elastic_foundation_oh); // uelastic foundation
5 Global_Discretization *elastic_foundation_type = new Global_Discretization();
6 Global_Discretization
7 elastic_foundation_gd(elastic_foundation_oh, elastic_foundation_gh, u_0,
8 elastic_foundation_type);
9 Gamma_h_i // potential contact segment; Γelastic founcation
10 elastic_foundation_gamma_h(0, elastic_foundation_oh);
11 Global_Discretization_Gamma_h_i
12 elastic_foundation_gamma_h_gd(0, elastic_foundation_gd, elastic_foundation_type,
13 elastic_foundation_gamma_h);

where line 9 defines the potential contact surface for the elastic foundation with a new class Gamma_h_i for Γi .
The Gamma_h_i class does not have variables of its own. The Global_Discretization of this class,
Global_Discretization_Gamma_h_i, uses the variable u_0 instantiated for the Ωi. The following lines define the
rigid punch (the upper part) in a similar manner.

1 Omega_h_i rigid_punch_oh(1); // Ω rigid punch


2 gh_on_Gamma_h_i rigid_punch_gh(1, ndf, rigid_punch_oh);
3 U_h u_1(ndf, rigid_punch_oh); // urigid punch
4 Global_Discretization *rigid_punch_type = new Global_Discretization();
5 Global_Discretization
6 rigid_punch_gd(rigid_punch_oh, rigid_punch_gh, u_1, rigid_punch_type);
7 Gamma_h_i // potentail contact segment; Γrigid punch
8 rigid_punch_gamma_h(1, rigid_punch_oh);
9 Global_Discretization_Gamma_h_i
10 rigid_punch_gamma_h_gd(1, rigid_punch_gd, rigid_punch_type, rigid_punch_gamma_h);
After domains Ωi and their potential contact surfaces Γi have been defined, we define the contact surface, Γc,
itself with the variables λ associated with it to form its Global_Discretization object.

Workbook of Applications in VectorSpace C++ Library 555


Chapter 5 Advanced Finite Element Methods

B.C.
rigid punch problem v = -2
(a)
8 9 10 11 E = 1.e10
(B.C for the next section ν = 0.5
on rigid sled problem 4 5 6 7
4 u = 0.2 t)
0 1 2 3
14 15 16 17 18 19 20

7 8 9 10 11 12 13

20

E = 1.e5
ν = 0.5
0 1 2 3 4 5 6

60

(b) Ωrigid punch Γrigid puch


0 0 1 1 2 2 3
Γc
0 1 2 3 4 5 6
g0
0 1 2 3 4 5

15 0 16 1 17 2 18 3 19 4
Ωelastic foundation
Γelastic foundation

Figure 5•11 Frictionless contact problem with rigid punch (or sled) on top an elastic foundation.

556 Workbook of Applications in VectorSpace C++ Library


Contact Mechanics
1 Omega_h_i interface_oh(2); // Γc in between Γelastic founcation and Γrigid punch
2 gh_on_Gamma_h_i interface_gh(2, ndf, interface_oh); // get initialized
3 U_h lambda(ndf, interface_oh); // λ
4 Global_Discretization interface_gd(interface_oh, interface_gh, lambda);
The Global_Discretization_Couple consists of two pairs of boundaries, {Γc-Γelastic founcation } and {Γc-Γrigid
punch}, are defined as

1 Global_Discretization_Couple *interface_elastic_foundation_gamma_h_type
2 = new Global_Discretization_Couple();
3 Global_Discretization_Couple // {Γc-Γelastic founcation}
4 interface_elastic_foundation_gdc(interface_gd, elastic_foundation_gamma_h_gd,
5 interface_elastic_foundation_gamma_h_type);
6 Global_Discretization_Couple *interface_rigid_punch_gamma_h_type
7 = new Global_Discretization_Couple();
8 Global_Discretization_Couple // {Γc-Γrigid punch}
9 interface_rigid_punch_gdc(interface_gd, rigid_punch_gamma_h_gd,
10 interface_rigid_punch_gamma_h_type);
The contact element domain (Ωeh)c is defined as a new C++ class “Contact_Omega_eh” derived from class of
element domain Ωeh as

1 class Contact_Omega_eh : public Omega_eh {


2 int the_associate_element[2];
3 double the_projection_parameter[4];
4 double the_normal_vector[4];
5 public:
6 Contact_Omega_eh(int* ae, double* pp, double* nv, Omega_eh& oh) : Omega_eh(oh) {
7 for(int i = 0; i < 2; i++) the_associate_element[i] = ae[i];
8 for(int i = 0; i < 4; i++) the_projection_parameter[i] = pp[i];
9 for(int i = 0; i < 4; i++) the_normal_vector[i] = nv[i];
10 }
11 int* associate_element() { return the_associate_element; }
12 double* projection_parameter() { return the_projection_parameter; }
13 double* normal_vector() { return the_normal_vector; }
14 };

The public inheritance relationship makes the class of contact element domain inherits what is available in the
class of element domain. In addition, the derived class is equipped with new private data which define what
boundary elements on the surrounding bodies are associated with the contact element domain. For contact ele-
ment number 3 in Figure 5•11b, the associated elements to the domain from the lower body is the boundary ele-
ment number 2, and from the upper body is the boundary element number 1. A contact element domain for
contact element number 3 is defined inside the constructor of class Omega_h_i as (see also Eq. 5•12)

Workbook of Applications in VectorSpace C++ Library 557


Chapter 5 Advanced Finite Element Methods

Ω1
1
α11 = 0

α10=1/2
n1
3 4
n0 3
α01 =1/2

α00 = 0 2
Ω0
Figure 5•12 Details of the contact element number “3”.

1 int ena[2];
2 ena[0] = 3; ena[1] = 4;
3 Omega_eh *elem = new Omega_eh(3, 0, 0, 2, ena); // store standard element information
4 int aen[2]; // associated element number array
5 aen[0] = 2; aen[1] = 1;
6 double pp[4], nv[4]; // projection parameters and unit normals
7 pp[0] = 0.0; pp[1] = 0.5; pp[2] = 0.5; pp[3] = 0.0; // α00, α01 (lower), and α10, α11 (upper)
8 nv[0] = 0.0; nv[1] = -1.0; // n0 = [n0x, n0y]T
9 nv[2] = 0.0; nv[3] = 1.0; // n1 = [n1x, n1y]T
10 Contact_Omega_eh *c_elem = new Contact_Omega_eh(aen, pp, nv, elem); // contact element
11 omega_eh_array().add(c_elem); // element array

The associated element number array supplies the element numbers of the lower and the upper boundary associ-
ated with the contact element number “3”. The element number for the lower boundary is “2”, and the upper ele-
ment number for the upper boundary is “1”. That is, in line 5,

aen[0] = 2; aen[1] = 1;

This information is needed for the contact element, so the contact element will know which real nodes are asso-
ciated with the contact element. The projection parameters, α00 and α01, for the lower boundary nodes are store
in pp[0] and pp[1]. The first node on the lower boundary associated with the contact element number 3 is the ele-
ment number 17 which is a real node α00 = 0. The second node is a projection node in the middle of element
number 17 and 18. Therefore, the projection parameter α01 = 0.5. Alternatively, we can use Eq. 5•213 to com-
pute the value of α when the geometrical condition becomes more complicated. α10 and α11 for the upper
boundary can be computed similarly. The first node again lies in the middle of node number “1” and node num-
ber “2” of the upper boundary. The projection parameter α10 = 0.5. The second node on the upper boundary is a
real node number “2” of the upper boundary and we have α11 = 0. We write in line 7

pp[0] = 0.0; pp[1] = 0.5; // α00, α01 (Ω0; lower)


pp[2] = 0.5; pp[3] = 0.0; // α10, α11 (Ω1; upper)

558 Workbook of Applications in VectorSpace C++ Library


Contact Mechanics
In lines 10-11, the contact element domain is instantiated and added to the global element array. This eleven lines
of codes are to be automatized in “contact searching algorithm” when contact relationship is changed signifi-
cantly during the deformation process. That is the definition of the contact element domains are created on-the-
fly. The contact searching algorithm will be programmed in the next section.
The Program Listing 5•17 implements the frictionless rigid punch problem. The solution of this problem is
shown in Figure 5•13.
The definition of class “Contact_Omega_eh” bring a significant impact on the object dependency hierarchy
as shown in Section 4.1.3 “Object-Oriented Analysis and Design of Finite Element Method” on page 285. The
reason is that the Ωh is at the top of class dependency graph. Any change bring to it will affect the classes below
it. The contact element domain (Ωeh)c is defined to be associated with two other boundary element by
“associated_element_array”. This information needs to be pass to Element_Formulation, by a switch, so the
Element_Formulation knows whether we are computing the off-diagonal submatrix C with respect to Γelastic
founcation or Γrigid punch. The same switch is also needed for “Global_Discretization_Couple::assembly()” mem-
ber function and “Global_Tensor::operator+=()” operator. An ad hoc extension will be just to re-write these
function definitions within the application source file. These definitions will then be adopted first in the compil-
ing phase, and the linker will not seek their definition in “fe.lib”. The strategy is to let the general purpose library
“fe.lib” remains as simple as possible. The ad hoc extension codes are use for one-shot situation. One certainly
can integrate this impact into the “fe.lib” in a more systematic manner. However, when a beginner of “fe.lib”
read the source code of it, he will be facing a huge set of problems all at once. The advantage of an ad hoc imple-
mentation is that both the code-reuse and simplicity of the “fe.lib” is achieved. The decision on inclusion of
exclusion of certain algorithm into the library is, therefore, a development policy issue.

Figure 5•13 Rigid punch on elastic foundation. The punch has been pushed down 2
unit.

Workbook of Applications in VectorSpace C++ Library 559


Chapter 5 Advanced Finite Element Methods

#include "include\fe.h"
#include "include\omega_h_n.h"
Matrix_Representation_Couple::assembly_switch
Matrix_Representation_Couple::Assembly_Switch = Matrix_Representation_Couple::ALL;
#include "include\global_discretization_gamma_h_n.h"
class Contact_Omega_eh : public Omega_eh {
int the_associate_element[2]; defininig class of a contact element
double the_projection_parameter[4]; domain
double the_normal_vector[4];
public:
Contact_Omega_eh(int* ae, double* pp, double* nv, Omega_eh& oh) : Omega_eh(oh) {
for(int i = 0; i < 2; i++) the_associate_element[i] = ae[i];
for(int i = 0; i < 4; i++) the_projection_parameter[i] = pp[i];
for(int i = 0; i < 4; i++) the_normal_vector[i] = nv[i];
}
int* associate_element() { return the_associate_element; }
double* projection_parameter() { return the_projection_parameter; }
double* normal_vector() { return the_normal_vector; }
};
static const double E_[2] = {1.e5, 1.e10}; Young’s moduli
static const double v_ = (0.5-1.e-12); Poisson ratios
static const double lambda_[2] = { v_*E_[0]/((1+v_)*(1-2*v_)),
v_*E_[1]/((1+v_)*(1-2*v_))};
static const double mu_[2] = {E_[0]/(2*(1+v_)), shear moduli
E_[1]/(2*(1+v_))};
static const double lambda_bar[2] = {2*lambda_[0]*mu_[0]/(lambda_[0]+2*mu_[0]),
2*lambda_[1]*mu_[1]/(lambda_[1]+2*mu_[1])}; λ
Omega_h_i::Omega_h_i(int i) : Omega_h(0){
if(i == 0) { elastic foundation; Ωelastic founcation
double v[2]; Node *node;
v[0] = -30.0; v[1] = -20.0; node = new Node(0, 2, v); node_array().add(node); define nodes
v[0] = -16.0; node = new Node(1, 2, v); node_array().add(node);
v[0] = -8.0; node = new Node(2, 2, v); node_array().add(node);
v[0] = 0.0; node = new Node(3, 2, v); node_array().add(node);
v[0] = 8.0; node = new Node(4, 2, v); node_array().add(node);
v[0] = 16.0; node = new Node(5, 2, v); node_array().add(node);
v[0] = 30.0; node = new Node(6, 2, v); node_array().add(node);
v[0] = -30.0; v[1] = -8.0; node = new Node(7, 2, v); node_array().add(node);
v[0] = -16.0; node = new Node(8, 2, v); node_array().add(node);
v[0] = -8.0; node = new Node(9, 2, v); node_array().add(node);
v[0] = 0.0; node = new Node(10, 2, v); node_array().add(node);
v[0] = 8.0; node = new Node(11, 2, v); node_array().add(node);
v[0] = 16.0; node = new Node(12, 2, v); node_array().add(node);
v[0] = 30.0; node = new Node(13, 2, v); node_array().add(node);
v[0] = -30.0; v[1] = 0.0; node = new Node(14, 2, v); node_array().add(node);
v[0] = -16.0; node = new Node(15, 2, v); node_array().add(node);
v[0] = -8.0; node = neww Node(16, 2, v); node_array().add(node);
v[0] = 0.0; node = new Node(17, 2, v); node_array().add(node);
v[0] = 8.0; node = new Node(18, 2, v); node_array().add(node);
v[0] = 16.0; node = new Node(19, 2, v); node_array().add(node);
v[0] = 30.0; node = new Node(20, 2, v); node_array().add(node);
int ena[4]; Omega_eh *elem;
ena[0] = 0; ena[1] = 1; ena[2] = 8; ena[3] = 7; define elements
elem = new Omega_eh(0, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 1; ena[1] = 2; ena[2] = 9; ena[3] = 8;
elem = new Omega_eh(1, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 2; ena[1] = 3; ena[2] = 10; ena[3] = 9;
elem = new Omega_eh(2, 0, 1, 4, ena); omega_eh_array().add(elem);

560 Workbook of Applications in VectorSpace C++ Library


Contact Mechanics
ena[0] = 3; ena[1] = 4; ena[2] = 11; ena[3] = 10;
elem = new Omega_eh(3, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 4; ena[1] = 5; ena[2] = 12; ena[3] = 11;
elem = new Omega_eh(4, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 5; ena[1] = 6; ena[2] = 13; ena[3] = 12;
elem = new Omega_eh(5, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 7; ena[1] = 8; ena[2] = 15; ena[3] = 14;
elem = new Omega_eh(6, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 8; ena[1] = 9; ena[2] = 16; ena[3] = 15;
elem = new Omega_eh(7, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 9; ena[1] = 10; ena[2] = 17; ena[3] = 16;
elem = new Omega_eh(8, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 10; ena[1] = 11; ena[2] = 18; ena[3] = 17;
elem = new Omega_eh(9, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 11; ena[1] = 12; ena[2] = 19; ena[3] = 18;
elem = new Omega_eh(10, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 12; ena[1] = 13; ena[2] = 20; ena[3] = 19;
elem = new Omega_eh(11, 0, 1, 4, ena); omega_eh_array().add(elem);
} else if(i == 1) {
double v[2]; Node *node;
rigid punch; Ω rigid punch
v[0] = -10.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node); define nodes
v[0] = -4.0; node = new Node(1, 2, v); node_array().add(node);
v[0] = 4.0; node = new Node(2, 2, v); node_array().add(node);
v[0] = 10.0; node = new Node(3, 2, v); node_array().add(node);
v[0] = -10.0; v[1] = 2.0; node = new Node(4, 2, v); node_array().add(node);
v[0] = -4.0; node = new Node(5, 2, v); node_array().add(node);
v[0] = 4.0; node = new Node(6, 2, v); node_array().add(node);
v[0] = 10.0; node = new Node(7, 2, v); node_array().add(node);
v[0] = -10.0; v[1] = 4.0; node = new Node(8, 2, v); node_array().add(node);
v[0] = -4.0; node = new Node(9, 2, v); node_array().add(node);
v[0] = 4.0; node = new Node(10, 2, v); node_array().add(node);
v[0] = 10.0; node = new Node(11, 2, v); node_array().add(node);
int ena[4]; Omega_eh *elem; define elements
ena[0] = 0; ena[1] = 1; ena[2] = 5; ena[3] = 4;
elem = new Omega_eh(0, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 1; ena[1] = 2; ena[2] = 6; ena[3] = 5;
elem = new Omega_eh(1, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 2; ena[1] = 3; ena[2] = 7; ena[3] = 6;
elem = new Omega_eh(2, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 4; ena[1] = 5; 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] = 10; ena[3] = 9;
elem = new Omega_eh(4, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 6; ena[1] = 7; ena[2] = 11; ena[3] = 10;
elem = new Omega_eh(5, 0, 0, 4, ena); omega_eh_array().add(elem);
} else if(i == 2) { Γc; contact line elements
double v[2]; Node *node; int ena[2]; int aea[4]; double pp[4], nv[4];
v[0] = -10.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node);
define nodes
v[0] = -8.0; node = new Node(1, 2, v); node_array().add(node);
v[0] = -4.0; node = new Node(2, 2, v); node_array().add(node);
v[0] = 0.0; node = new Node(3, 2, v); node_array().add(node);
v[0] = 4.0; node = new Node(4, 2, v); node_array().add(node);
v[0] = 8.0; node = new Node(5, 2, v); node_array().add(node);
v[0] = 10.0; node = new Node(6, 2, v); node_array().add(node);
Omega_eh *elem; Contact_Omega_eh *c_elem;
ena[0] = 0; ena[1] = 1; elem = new Omega_eh(0, 0, 0, 2, ena);
define elements
aea[0] = 0; aea[1] = 0; pp[0] = 3.0/4.0; pp[1] = 0.0; pp[2] = 0.0; pp[3] = 2.0/3.0;
nv[0] = 0.0; nv[1] = 1.0; nv[2] = 0.0; nv[3] = -1.0;
c_elem = new Contact_Omega_eh(aea, pp, nv, *elem); omega_eh_array().add(c_elem);

Workbook of Applications in VectorSpace C++ Library 561


Chapter 5 Advanced Finite Element Methods
ena[0] = 1; ena[1] = 2; elem = new Omega_eh(1, 0, 0, 2, ena);
aea[0] = 1; aea[1] = 0; pp[0] = 0.0; pp[1] = 1.0/2.0; pp[2] = 1.0/3.0; pp[3] = 0.0;
nv[0] = 0.0; nv[1] = -1.0; nv[2] = 0.0; nv[3] = 1.0;
c_elem = new Contact_Omega_eh(aea, pp, nv, *elem); omega_eh_array().add(c_elem);
ena[0] = 2; ena[1] = 3; elem = new Omega_eh(2, 0, 0, 2, ena);
aea[0] = 1; aea[1] = 1; pp[0] = 1.0/2.0; pp[1] = 0.0; pp[2] = 0.0; pp[3] = 1.0/2.0;
nv[0] = 0.0; nv[1] = 1.0; nv[2] = 0.0; nv[3] = -1.0;
c_elem = new Contact_Omega_eh(aea, pp, nv, *elem); omega_eh_array().add(c_elem);
ena[0] = 3; ena[1] = 4; elem = new Omega_eh(3, 0, 0, 2, ena);
aea[0] = 2; aea[1] = 1; pp[0] = 0.0; pp[1] = 1.0/2.0; pp[2] = 1.0/2.0; pp[3] = 0.0;
nv[0] = 0.0; nv[1] = -1.0; nv[2] = 0.0; nv[3] = 1.0;
c_elem = new Contact_Omega_eh(aea, pp, nv, *elem); omega_eh_array().add(c_elem);
ena[0] = 4; ena[1] = 5; elem = new Omega_eh(4, 0, 0, 2, ena);
aea[0] = 2; aea[1] = 2; pp[0] = 1.0/2.0; pp[1] = 0.0; pp[2] = 0.0; pp[3] = 1.0/3.0;
nv[0] = 0.0; nv[1] = 1.0; nv[2] = 0.0; nv[3] = -1.0;
c_elem = new Contact_Omega_eh(aea, pp, nv, *elem); omega_eh_array().add(c_elem);
ena[0] = 5; ena[1] = 6; elem = new Omega_eh(5, 0, 0, 2, ena);
aea[0] = 3; aea[1] = 2; pp[0] = 0.0; pp[1] = 3.0/4.0; pp[2] = 2.0/3.0; pp[3] = 0.0;
nv[0] = 0.0; nv[1] = -1.0; nv[2] = 0.0; nv[3] = 1.0;
c_elem = new Contact_Omega_eh(aea, pp, nv, *elem); omega_eh_array().add(c_elem);
}
}
Gamma_h_i::Gamma_h_i(int i, Omega_h &oh) : Omega_h(oh), the_index(i) {
new ena[2]; Omega_eh *elem; potential contact boundaries
if(i == 0) { // elastic foundation upper boundary as potential contact surface Γelastic founcation
ena[0] = 15;ena[1] = 16;elem = new Omega_eh(0, 0, 0, 2, ena);the_omega_eh_array.add(elem);
ena[0] = 16;ena[1] = 17;elem = new Omega_eh(1, 0, 0, 2, ena);the_omega_eh_array.add(elem);
ena[0] = 17;ena[1] = 18;elem = new Omega_eh(2, 0, 0, 2, ena);the_omega_eh_array.add(elem);
ena[0] = 18;ena[1] = 19;elem = new Omega_eh(3, 0, 0, 2, ena);the_omega_eh_array.add(elem);
ena[0] = 19;ena[1] = 20;elem = new Omega_eh(4, 0, 0, 2, ena);the_omega_eh_array.add(elem);
} else if(i == 1) { // rigid punch lower bounary as potential contact surface
ena[0] = 0;ena[1] = 1;elem = new Omega_eh(0, 0, 0, 2, ena);the_omega_eh_array.add(elem); Γrigid punch
ena[0] = 1;ena[1] = 2;elem = new Omega_eh(1, 0, 0, 2, ena);the_omega_eh_array.add(elem);
ena[0] = 2;ena[1] = 3;elem = new Omega_eh(2, 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() { Boundary conditions
gh_on_Gamma_h::__initialization(df, omega_h);
if(i == 0) {.
the_gh_array[node_order(0)](0) = the_gh_array[node_order(0)](1) = elastic foundation
the_gh_array[node_order(1)](1) = the_gh_array[node_order(2)](1) =
the_gh_array[node_order(3)](1) = the_gh_array[node_order(4)](1) =
the_gh_array[node_order(5)](1) = the_gh_array[node_order(6)](0) =
the_gh_array[node_order(6)](1) = the_gh_array[node_order(7)](0) =
the_gh_array[node_order(13)](0) = the_gh_array[node_order(14)](0) =
the_gh_array[node_order(20)](0) = gh_on_Gamma_h::Dirichlet;
} else if(i == 1) {
the_gh_array[node_order(8)](0) = the_gh_array[node_order(8)](1) = rigid punch; push downwards 2 units
the_gh_array[node_order(9)](0) = the_gh_array[node_order(9)](1) =
the_gh_array[node_order(10)](0) = the_gh_array[node_order(10)](1) =
the_gh_array[node_order(11)](0) = the_gh_array[node_order(11)](1) =
gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(8)][1] = the_gh_array[node_order(9)][1] =
the_gh_array[node_order(10)][1] = the_gh_array[node_order(11)][1] =-2.0;
}
}

562 Workbook of Applications in VectorSpace C++ Library


Contact Mechanics
static int ndf = 2; instantiation of global objects
static Omega_h_i elastic_foundation_oh(0); elastic foundation begins
static gh_on_Gamma_h_i elastic_foundation_gh(0, ndf, elastic_foundation_oh);
static U_h u_0(ndf, elastic_foundation_oh);
static Global_Discretization *elastic_foundation_type = new Global_Discretization();
static Global_Discretization Ωelastic founcation
elastic_foundation_gd(elastic_foundation_oh, elastic_foundation_gh, u_0,
elastic_foundation_type);
static Gamma_h_i potential contact boundary on
elastic_foundation_gamma_h(0, elastic_foundation_oh); elastic foundation; Γelastic founcation
static Global_Discretization_Gamma_h_i
elastic_foundation_gamma_h_gd(0, elastic_foundation_gd, elastic_foundation_type,
elastic_foundation_gamma_h); elastic foundation ends
static Omega_h_i rigid_punch_oh(1); rigid punch begins
static gh_on_Gamma_h_i rigid_punch_gh(1, ndf, rigid_punch_oh);
static U_h u_1(ndf, rigid_punch_oh);
static Global_Discretization *rigid_punch_type = new Global_Discretization();
static Global_Discretization Ω rigid punch
rigid_punch_gd(rigid_punch_oh, rigid_punch_gh, u_1, rigid_punch_type);
static Gamma_h_i potential contact boundary on
rigid_punch_gamma_h(1, rigid_punch_oh); rigid punch; Γrigid punch
static Global_Discretization_Gamma_h_i rigid punch ends
rigid_punch_gamma_h_gd(1, rigid_punch_gd, rigid_punch_type, rigid_punch_gamma_h);
static Omega_h_i interface_oh(2); Contact interface; Γc
static gh_on_Gamma_h_i interface_gh(2, ndf, interface_oh);
static U_h lambda(ndf, interface_oh);
static Global_Discretization interface_gd(interface_oh, interface_gh, lambda);
static Global_Discretization_Couple *interface_elastic_foundation_gamma_h_type λ
= new Global_Discretization_Couple(); for {Γc-Γelastic founcation}
static Global_Discretization_Couple
interface_elastic_foundation_gdc(interface_gd, elastic_foundation_gamma_h_gd,
interface_elastic_foundation_gamma_h_type);
static Global_Discretization_Couple *interface_rigid_punch_gamma_h_type
= new Global_Discretization_Couple(); for {Γc-Γrigid punch}
static Global_Discretization_Couple
interface_rigid_punch_gdc(interface_gd, rigid_punch_gamma_h_gd,
interface_rigid_punch_gamma_h_type);
class Elastic_Contact_Q4 : public Element_Formulation_Couple { begin contact element formulation
public:
Elastic_Contact_Q4(Element_Type_Register a) : Element_Formulation_Couple(a) {}
Element_Formulation *make(int, Global_Discretization&); diagonal block formulations; Ki
Elastic_Contact_Q4(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&);
Elastic_Contact_Q4(int, Global_Discretization_Couple&); off-diagonal block formulations; Qi
};
Element_Formulation* Elastic_Contact_Q4::make(int en, Global_Discretization& gd) {
return new Elastic_Contact_Q4(en,gd);
}
Elastic_Contact_Q4::Elastic_Contact_Q4(int en, Global_Discretization& gd) :
Element_Formulation_Couple(en, gd) {
int index;
if(gd.type() == elastic_foundation_type) index = 0;
else if(gd.type() == rigid_punch_type) index = 1; K0(elsatic foundation)
Quadrature qp2(2, 4); K1(rigid punch)
H1 Z(2, (double*)0, qp2), Zai, Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 2, qp2);
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;

Workbook of Applications in VectorSpace C++ Library 563


Chapter 5 Advanced Finite Element Methods
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dv2(d(X).det());
C0 e = BASIS("int", ndf), E = BASIS("int", nen), u = e*E, 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_dev = mu_[index]* (
+( ((2.0*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.0*Wy*~Wy)+(Wx*~Wx))*((e[1]%e[1])*(E%E)) )
| dv2);
Quadrature qp1(2, 1);
H1 z(2, (double*)0, qp1), zai, eta,
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 2, qp1);
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 dv1(d(x).det());
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_bar[index]*(
+( wx*~wx*U[0][0]+wx*~wy*U[0][1]+
wy*~wx*U[1][0]+wy*~wy*U[1][1] )
| dv1 );
stiff &= stiff_vol + stiff_dev;
}
Element_Formulation_Couple* Elastic_Contact_Q4::make(int en,
Global_Discretization_Couple& gdc) { return new Elastic_Contact_Q4(en,gdc); }
Elastic_Contact_Q4::Elastic_Contact_Q4(int en, Global_Discretization_Couple& gdc)
: Element_Formulation_Couple(en, gdc) {
Quadrature qp(1, 2);
H1 Z(qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 2, 1, qp);
N[0] = (1.0-Z)/2.0; N[1] = (1.0+Z)/2.0;
H1 X = N*xl;
J d_l(norm(d(X)(0)));
C0 zero(0.0);
H0 N_lambda = ((~(H0)N) || zero ) &
(zero || (~(H0)N));
Omega_eh *elem = &(gdc.principal_discretization().omega_h()(en));
0
double *pp = ((Contact_Omega_eh*)elem)->projection_parameter(), ( 1 – α 0 )n 0 α0 n0 û 0
*nv = ((Contact_Omega_eh*)elem)->normal_vector(); u= = cû
C0 n_0(2,nv), n_1(2, nv+2); α1 n1 ( 1 – α 1 )n 1 1
û 0
if(gdc.type() == interface_rigid_punch_gamma_h_type) pp += 2;
C0 alpha(2,pp);
H0 N_bar_0 = ( ((H0)N[0])*((1.0-alpha[0])*n_0) + ((H0)N[1])*(alpha[1]*n_1)
N_bar_1 = ( ((H0)N[0])*(alpha[0]*n_0) + ((H0)N[1])*((1.0-alpha[1])*n_1) ),
), Q = ∫ NλT Nu dΓ ; where N ≡ Nc
u
Γc
N_u_bar = ( N_bar_0[0] | zero | N_bar_1[0] | zero )&
( zero | N_bar_0[1] | zero | N_bar_1[1] ); Q0 {Γc-Γelastic founcation}
if(gdc.type() == interface_elastic_foundation_gamma_h_type)
stiff &= -(((~N_lambda)*N_u_bar)|d_l); Q1 {Γc-Γrigid punch}
else
stiff &= (((~N_lambda)*N_u_bar)|d_l);
}
Element_Formulation* Element_Formulation::type_list = 0;
Element_Type_Register element_type_register_instance;
static Elastic_Contact_Q4 elastic_contact_q4_instance(element_type_register_instance);

564 Workbook of Applications in VectorSpace C++ Library


Contact Mechanics
static Matrix_Representation mr_K_0(elastic_foundation_gd); K0
static Matrix_Representation_Couple mrc_Q_0(interface_elastic_foundation_gdc, 0, 0,
&(mr_K_0.rhs()) );
Q0
static Matrix_Representation mr_K_1(rigid_punch_gd); K1
static Matrix_Representation_Couple mrc_Q_1(interface_rigid_punch_gdc, 0, &(mrc_Q_0.rhs()), Q1
&(mr_K_1.rhs()) );
#include <time.h>
int main() {
time_t time0 = time(0);
mr_K_0.assembly();
C0 K_0 = (C0)(mr_K_0.lhs()); cout << "K_0:" << endl << K_0 << endl;
K0
time_t time1 = time(0);
cout << "elipsed time: " << (difftime(time1, time0)/60) << " minutes." << endl;
mrc_Q_0.assembly();
C0 Q_0 = (C0)(mrc_Q_0.lhs()); cout << "Q_0:" << endl << Q_0 << endl;
Q0
time1 = time(0);
cout << "elipsed time: " << (difftime(time1, time0)/60) << " minutes." << endl;
mr_K_1.assembly();
C0 K_1 = (C0)(mr_K_1.lhs()); cout << "K_1:" << endl << K_1 << endl;
K1
time1 = time(0);
cout << "elipsed time: " << (difftime(time1, time0)/60) << " minutes." << endl;
mrc_Q_1.assembly();
C0 Q_1 = (C0)(mrc_Q_1.lhs()), f_0 = (C0)(mr_K_0.rhs()),
Q1
f_1 = (C0)(mr_K_1.rhs()), f_i = (C0)(mrc_Q_0.rhs()); f0, f1, fi
cout << "Q_1:" << endl << Q_1 << endl; cout << "f_0:" << endl << f_0 << endl;
cout << "f_1:" << endl << f_1 << endl; cout << "f_i:" << endl << f_i << endl;
time1 = time(0);
cout << "elipsed time: " << (difftime(time1, time0)/60) << " minutes." << endl;
cout << endl << "solution phase begins... " << endl;
Cholesky dK0(K_0);
C0 K0_inv = dK0.inverse(); C0 QK_inv_0 = Q_0*K0_inv; C0 QKQ_0 = QK_inv_0*(~Q_0);
K0-1, Q0 K0-1 Q0T
Cholesky dK1(K_1);
C0 K1_inv = dK1.inverse(); C0 QK_inv_1 = Q_1*K1_inv; C0 QKQ_1 = QK_inv_1*(~Q_1); K1-1, Q1 K1-1 Q1T
Cholesky mdQKQ(QKQ_0+QKQ_1, 1.e-12);
double *iptr; iptr = new double(0.0); modf(log10(mdQKQ.cond()), iptr);
(Q0 K0-1 Q0T + K1-1, Q1 K1-1 Q1T)-1
if(*iptr > (2.0-1.e-12)) cout << "Warnning: lost of " << int(*iptr) <<
" significant digits in modified Cholesky decomposition." << endl;
delete iptr;
C0 lambda = mdQKQ * (QK_inv_0*f_0+QK_inv_1*f_1-f_i);
λ̂ =(Q0 K0-1 Q0T + K1-1, Q1 K1-1 Q1T)-1
C0 u_0 = dK0*(f_0-(~Q_0)*lambda); (Q0 K0-1 f0 + Q1K1-1 f1 - fi)
C0 u_1 = dK1*(f_1-(~Q_1)*lambda); û 0 = K0-1 (f0 - Q0T λ̂ )
interface_gd.u_h() = lambda;
interface_gd.u_h() = interface_gd.gh_on_gamma_h();
û 1 = K1-1 (f1 - Q1T λ̂ )
cout << "contact forces:" << endl << interface_gd.u_h();
elastic_foundation_gd.u_h() = u_0;
elastic_foundation_gd.u_h() = elastic_foundation_gd.gh_on_gamma_h();
cout << "elastic foundation displacement:" << endl << elastic_foundation_gd.u_h();
rigid_punch_gd.u_h() = u_1;
rigid_punch_gd.u_h() = rigid_punch_gd.gh_on_gamma_h();
cout << "rigid punch displacement:" << endl << rigid_punch_gd.u_h();
time1 = time(0);
cout << "elipsed time: " << (difftime(time1, time0)/60) << " minutes." << endl;
return 0;
}

Listing 5•17 Frictionless rigid punch on elastic foundation ( project: “contact_rigid_punch_on_elastic_


foundation” in project workspace file “fe.dsw”).

Workbook of Applications in VectorSpace C++ Library 565


Chapter 5 Advanced Finite Element Methods
5.2.3 Frictionless Rigid Sled on Elastic Foundation
In the context of optimization, the “active set” method for the rigid punch problem in the previous section is
defined manually. For the rigid sled problem the contact geometry change dramatically for each time step, where
a contact searching algorithm is used to implement the “active set” automatically. The contact searching algo-
rithm is resided in class Omega_h_i (included in the source code and the header file in “fe.lib”), which is to
define geometrical relationship in two pairs of boundaries, {Γc-Γelastic founcation } and {Γc-Γrigid sled}. We use the
procedure programming method for the implementation of the contact searching algorithm. The reason of using
the procedure programming method instead of the object-oriented programming method in this case is that the
object-oriented programming is usually applied to implement a set of problems, which is a more systematic and
thorough approach, and is naturally more time consuming to establish such a framework for serving a set of
problems. Therefore, it is a design decision meaning we are dealing with contact problem, which is not the sole
focus in this workbook, only in an ad hoc manner.
The basic elements of the procedure programming method are data structure and algorithm. In the spirit of
the relational database, we use a table to represent the data structure of the information in Figure 5•11 b.
TABLE5• 2. contains two rows, the first row corresponding to the node numbers in Γelastic founcation and the sec-
ond row in Γrigid punch. The header numbers (columns) are the contact boundary node numbers. All the cells in
the table are given the default value of “-1”. These default values are to be over-written with node numbers
(greater of equal to zero) of the Γelastic founcation and Γrigid punch encountered. Therefore, if in a cell contains the
default value (-1) after the over-writing process, it means the corresponding node on the two boundaries, for the
contact surface, is a projected node as in Figure 5•11 (b).

Contact node# 0 1 2 3 4 5 6
Γelastic founcation 0 -1 1 -1 2 -1 3
Γrigid sled -1 16 -1 17 -1 18 -1
TABLE5• 2. Contact node number table.

What is not shown in the present example is, if for a particular contact node number both cells contain the
default value “-1” it means that there is a detachment segment along the contact surface. This situation occurred
either there is no node project onto certain segments or the projection form a gap that is beyond a pre-defined
tolerance value. If both cell contains no default values, that is the case when the node on one side are projected
to the node on the other side within a tolerance distance. The contact searching algorithm is to (1) construct
TABLE5• 2. automatically, then (2) use this table to define contact nodes and contact elements. The second part
is straight forward, since we have the manual input experience gained from the rigid punch problem in the previ-
ous section. We focus on the first part. The basic idea is to scan through the contact surface Γc to find a leading
node from either side of the potential contact boundaries Γi . The leading node is the first node encountered that
is successful projected to the opposite boundary segment. For example, if we are now in the middle of the pro-
cess at the contact node number 2, the next leading node that can be successfully project to the opposing Γi is
node number 17 on the Γelastic foundation. Successive leading nodes are filled in to the rows that they are corre-
sponding to, and leave the opposite row with the default value (-1) which indicates a projected node. Inspecting
on yet another example in Figure 5•10, we find that the leading node is not necessary always alternating on rows
in their appearance on the contact searching number table as in TABLE5• 2. In general, they may appear consec-

566 Workbook of Applications in VectorSpace C++ Library


Contact Mechanics
utive on the same side for indefinite number of times. This is implemented in file “contact_searching.cpp” under
directory “vs\fe\lib”.
We need some geometrical utilities to perform the node projection on boundary. Eq. 5•213, which basically is
the cosine theorem, can be used for this purpose. For example if the projection parameter has the value of

0≤α≤1 Eq. 5•223

Then the projection of a node onto its opposite target segment is successful, on condition that the gap as com-
puted from Eq. 5•211 between the current node and the projected node is within a tolerance value. In a special
condition, when α is very close to 0 or 1 and the distance between the projected node and the ends of the target
segment is small, we have a node-on-node projection situation. These functions are implemented in the same file
“contact_searching.cpp” under its “geometrical utilities” section.
The contact searching algorithm should be invoked upon every iteration. The Q matrix will be changed
accordingly. The rigid sled is push from left to the right for 8 steps with incremental displacement of ∆v = 0.2;
i.e., vt = ∆v t ( where t = 0, 8). The Program implements the frictionless sled on elastic foundation problem. The
Program Listing 5•18 implements the frictionless rigid punch problem. The solution of this problem is shown in
Figure 5•14.

Workbook of Applications in VectorSpace C++ Library 567


Chapter 5 Advanced Finite Element Methods

t=2

t=5

t=8

Figure 5•14 Rigid sled on elastic foundation.

568 Workbook of Applications in VectorSpace C++ Library


Contact Mechanics

#include "vs.h"
#include "fe.h"
#include "omega_h_n.h"
Matrix_Representation_Couple::assembly_switch
Matrix_Representation_Couple::Assembly_Switch = Matrix_Representation_Couple::ALL;
#include "global_discretization_gamma_h_n.h"
#include "contact_searching.h"
static const double E_[2] = {1.e5, 1.e10}; static const double v_ = (0.5-1.e-12); Young’s moduli
static const double lambda_[2] = {v_*E_[0]/((1+v_)*(1-2*v_)), v_*E_[1]/((1+v_)*(1-2*v_))}; Poisson ratios
static const double mu_[2] = {E_[0]/(2*(1+v_)), E_[1]/(2*(1+v_))}; shear moduli
static const double lambda_bar[2] =
{2*lambda_[0]*mu_[0]/(lambda_[0]+2*mu_[0]),2*lambda_[1]*mu_[1]/(lambda_[1]+2*mu_[1])}; λ
Omega_h_i::Omega_h_i(int i) : Omega_h(0){
the_index = i;
if(i == 0) {
double v[2]; Node *node; elastic foundation; Ωelastic founcation
v[0] = -30.0; v[1] = -20.0; node = new Node(0, 2, v); node_array().add(node); define nodes
v[0] = -16.0; node = new Node(1, 2, v); node_array().add(node);
v[0] = -8.0; node = new Node(2, 2, v); node_array().add(node);
v[0] = 0.0; node = new Node(3, 2, v); node_array().add(node);
v[0] = 8.0; node = new Node(4, 2, v); node_array().add(node);
v[0] = 16.0; node = new Node(5, 2, v); node_array().add(node);
v[0] = 30.0; node = new Node(6, 2, v); node_array().add(node);
v[0] = -30.0; v[1] = -8.0; node = new Node(7, 2, v); node_array().add(node);
v[0] = -16.0; node = new Node(8, 2, v); node_array().add(node);
v[0] = -8.0; node = new Node(9, 2, v); node_array().add(node);
v[0] = 0.0; node = new Node(10, 2, v); node_array().add(node);
v[0] = 8.0; node = new Node(11, 2, v); node_array().add(node);
v[0] = 16.0; node = new Node(12, 2, v); node_array().add(node);
v[0] = 30.0; node = new Node(13, 2, v); node_array().add(node);
v[0] = -30.0; v[1] = 0.0; node = new Node(14, 2, v); node_array().add(node);
v[0] = -16.0; node = new Node(15, 2, v); node_array().add(node);
v[0] = -8.0; node = new Node(16, 2, v); node_array().add(node);
v[0] = 0.0; node = new Node(17, 2, v); node_array().add(node);
v[0] = 8.0; node = new Node(18, 2, v); node_array().add(node);
v[0] = 16.0; node = new Node(19, 2, v); node_array().add(node);
v[0] = 30.0; node = new Node(20, 2, v); node_array().add(node);
int ena[4]; Omega_eh *elem; define elements
ena[0] = 0; ena[1] = 1; ena[2] = 8; ena[3] = 7;
elem = new Omega_eh(0, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 1; ena[1] = 2; ena[2] = 9; ena[3] = 8;
elem = new Omega_eh(1, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 2; ena[1] = 3; ena[2] = 10; ena[3] = 9;
elem = new Omega_eh(2, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 3; ena[1] = 4; ena[2] = 11; ena[3] = 10;
elem = new Omega_eh(3, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 4; ena[1] = 5; ena[2] = 12; ena[3] = 11;
elem = new Omega_eh(4, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 5; ena[1] = 6; ena[2] = 13; ena[3] = 12;
elem = new Omega_eh(5, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 7; ena[1] = 8; ena[2] = 15; ena[3] = 14;
elem = new Omega_eh(6, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 8; ena[1] = 9; ena[2] = 16; ena[3] = 15;
elem = new Omega_eh(7, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 9; ena[1] = 10; ena[2] = 17; ena[3] = 16;
elem = new Omega_eh(8, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 10; ena[1] = 11; ena[2] = 18; ena[3] = 17;
elem = new Omega_eh(9, 0, 1, 4, ena); omega_eh_array().add(elem);

Workbook of Applications in VectorSpace C++ Library 569


Chapter 5 Advanced Finite Element Methods
ena[0] = 11; ena[1] = 12; ena[2] = 19; ena[3] = 18;
elem = new Omega_eh(10, 0, 1, 4, ena); omega_eh_array().add(elem);
ena[0] = 12; ena[1] = 13; ena[2] = 20; ena[3] = 19;
elem = new Omega_eh(11, 0, 1, 4, ena); omega_eh_array().add(elem);
} else if(i == 1) { rigid sled; Ω rigid sled
double v[2]; Node *node;
v[0] = -10.0; v[1] = 0.0; node = new Node(0, 2, v); node_array().add(node);
define nodes
v[0] = -4.0; node = new Node(1, 2, v); node_array().add(node);
v[0] = 4.0; node = new Node(2, 2, v); node_array().add(node);
v[0] = 10.0; node = new Node(3, 2, v); node_array().add(node);
v[0] = -10.0; v[1] = 2.0; node = new Node(4, 2, v); node_array().add(node);
v[0] = -4.0; node = new Node(5, 2, v); node_array().add(node);
v[0] = 4.0; node = new Node(6, 2, v); node_array().add(node);
v[0] = 10.0; node = new Node(7, 2, v); node_array().add(node);
v[0] = -10.0; v[1] = 4.0; node = new Node(8, 2, v); node_array().add(node);
v[0] = -4.0; node = new Node(9, 2, v); node_array().add(node);
v[0] = 4.0; node = new Node(10, 2, v); node_array().add(node);
v[0] = 10.0; node = new Node(11, 2, v); node_array().add(node);
int ena[4]; Omega_eh *elem;
ena[0] = 0; ena[1] = 1; ena[2] = 5; ena[3] = 4; define elements
elem = new Omega_eh(0, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 1; ena[1] = 2; ena[2] = 6; ena[3] = 5;
elem = new Omega_eh(1, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 2; ena[1] = 3; ena[2] = 7; ena[3] = 6;
elem = new Omega_eh(2, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 4; ena[1] = 5; 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] = 10; ena[3] = 9;
elem = new Omega_eh(4, 0, 0, 4, ena); omega_eh_array().add(elem);
ena[0] = 6; ena[1] = 7; ena[2] = 11; ena[3] = 10;
elem = new Omega_eh(5, 0, 0, 4, ena); omega_eh_array().add(elem);
} Γc; contact line elements
} are defined by “contact
searching algorithm” on the
fly
Contact_Omega_h_i::Contact_Omega_h_i(int i) : Omega_h_i(i) { /* do nothing */ }
Gamma_h_i::Gamma_h_i(int i, Omega_h &oh) : Omega_h(oh), the_index(i) {
int ena[2]; Omega_eh *elem;
if(i == 0) { potential contact boundaries
ena[0] = 15; ena[1] = 16; Γelastic founcation
elem = new Omega_eh(0, 0, 0, 2, ena); the_omega_eh_array.add(elem);
ena[0] = 16; ena[1] = 17;
elem = new Omega_eh(1, 0, 0, 2, ena); the_omega_eh_array.add(elem);
ena[0] = 17; ena[1] = 18;
elem = new Omega_eh(2, 0, 0, 2, ena); the_omega_eh_array.add(elem);
ena[0] = 18; ena[1] = 19;
elem = new Omega_eh(3, 0, 0, 2, ena); the_omega_eh_array.add(elem);
ena[0] = 19; ena[1] = 20;
elem = new Omega_eh(4, 0, 0, 2, ena); the_omega_eh_array.add(elem);
} else if(i == 1) {
ena[0] = 0; ena[1] = 1; Γrigid sled
elem = new Omega_eh(0, 0, 0, 2, ena); the_omega_eh_array.add(elem);
ena[0] = 1; ena[1] = 2;
elem = new Omega_eh(1, 0, 0, 2, ena); the_omega_eh_array.add(elem);
ena[0] = 2; ena[1] = 3;
elem = new Omega_eh(2, 0, 0, 2, ena); the_omega_eh_array.add(elem);
}
}

570 Workbook of Applications in VectorSpace C++ Library


Contact Mechanics
gh_on_Gamma_h_i::gh_on_Gamma_h_i(int i, int df, Omega_h& omega_h) : gh_on_Gamma_h() { Boundary conditions
gh_on_Gamma_h::__initialization(df, omega_h);
if(i == 0) {
the_gh_array[node_order(0)](0) = the_gh_array[node_order(0)](1) = elastic foundation
the_gh_array[node_order(1)](1) = the_gh_array[node_order(2)](1) =
the_gh_array[node_order(3)](1) = the_gh_array[node_order(4)](1) =
the_gh_array[node_order(5)](1) = the_gh_array[node_order(6)](0) =
the_gh_array[node_order(6)](1) = the_gh_array[node_order(7)](0) =
the_gh_array[node_order(13)](0) = the_gh_array[node_order(14)](0) =
the_gh_array[node_order(20)](0) = gh_on_Gamma_h::Dirichlet;
} else if(i == 1) {
the_gh_array[node_order(8)](0) = the_gh_array[node_order(8)](1) = rigid sled; push horizontally 2 units
the_gh_array[node_order(9)](0) = the_gh_array[node_order(9)](1) =
the_gh_array[node_order(10)](0) = the_gh_array[node_order(10)](1) =
the_gh_array[node_order(11)](0) = the_gh_array[node_order(11)](1) =
gh_on_Gamma_h::Dirichlet;
the_gh_array[node_order(8)][1] = the_gh_array[node_order(9)][1] =
the_gh_array[node_order(10)][1] = the_gh_array[node_order(11)][1] = -0.2;
}
} instantiation of global objects
static int ndf = 2; elastic foundation begins
Ωelastic founcation
static Omega_h_i elastic_foundation_oh(0);
static gh_on_Gamma_h_i elastic_foundation_gh(0, ndf, elastic_foundation_oh);
static U_h u_0_h(ndf, elastic_foundation_oh);
static Global_Discretization *elastic_foundation_type = new Global_Discretization();
static Global_Discretization elastic_foundation_gd(
elastic_foundation_oh, elastic_foundation_gh, u_0_h, elastic_foundation_type);
potential contact boundary on
static Gamma_h_i elastic_foundation_gamma_h(0, elastic_foundation_oh); elastic foundation; Γelastic founcation
static Global_Discretization_Gamma_h_i elastic_foundation_gamma_h_gd(0, elastic foundation ends
elastic_foundation_gd, elastic_foundation_type, elastic_foundation_gamma_h);
static Omega_h_i rigid_sled_oh(1);
rigid sled begins
static gh_on_Gamma_h_i rigid_sled_gh(1, ndf, rigid_sled_oh); Ω rigid sled
static U_h u_1_h(ndf, rigid_sled_oh);
static Global_Discretization *rigid_sled_type = new Global_Discretization();
static Global_Discretization rigid_sled_gd(rigid_sled_oh, rigid_sled_gh, u_1_h, rigid_sled_type);
static Gamma_h_i rigid_sled_gamma_h(1, rigid_sled_oh); potential contact boundary on
static Global_Discretization_Gamma_h_i rigid sled; Γrigid sled
rigid_sled_gamma_h_gd(1, rigid_sled_gd, rigid_sled_type, rigid_sled_gamma_h);
static Contact_Omega_h_i interface_oh(2);
rigid sled ends
static gh_on_Gamma_h_i interface_gh(2, ndf, interface_oh); Contact interface; Γc
static U_h lambda_h(ndf, interface_oh); λ
static Global_Discretization interface_gd(interface_oh, interface_gh, lambda_h);
static Global_Discretization_Couple *interface_elastic_foundation_gamma_h_type
for {Γc-Γelastic founcation}
= new Global_Discretization_Couple();
static Global_Discretization_Couple interface_elastic_foundation_gdc(interface_gd,
elastic_foundation_gamma_h_gd, interface_elastic_foundation_gamma_h_type);
static Global_Discretization_Couple *interface_rigid_sled_gamma_h_type
= new Global_Discretization_Couple(); for {Γc-Γrigid sled}
static Global_Discretization_Couple interface_rigid_sled_gdc(interface_gd,
rigid_sled_gamma_h_gd, interface_rigid_sled_gamma_h_type);
class Elastic_Contact_Q4 : public Element_Formulation_Couple {
public: Element Formulation
Elastic_Contact_Q4(Element_Type_Register a) : Element_Formulation_Couple(a) {}
Element_Formulation *make(int, Global_Discretization&);
Elastic_Contact_Q4(int, Global_Discretization&);
Element_Formulation_Couple *make(int, Global_Discretization_Couple&);
Elastic_Contact_Q4(int, Global_Discretization_Couple&);
};

Workbook of Applications in VectorSpace C++ Library 571


Chapter 5 Advanced Finite Element Methods
Element_Formulation* Elastic_Contact_Q4::make(int en, Global_Discretization& gd) { diagonal block formulations; Ki
return new Elastic_Contact_Q4(en,gd);
}
Elastic_Contact_Q4::Elastic_Contact_Q4(int en, Global_Discretization& gd) :
Element_Formulation_Couple(en, gd) {
int index;
if(gd.type() == elastic_foundation_type) index = 0;
K0(elsatic foundation)
else if(gd.type() == rigid_sled_type) index = 1; K1(rigid sled)
Quadrature qp2(2, 4);
H1 Z(2, (double*)0, qp2), Zai, Eta,
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 4, 2, qp2);
Zai &= Z[0]; Eta &= Z[1];
N[0] = (1.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dv2(d(X).det());
C0 e = BASIS("int", ndf), E = BASIS("int", nen), u = e*E, 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_dev = mu_[index]* (
+( ((2.0*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.0*Wy*~Wy)+(Wx*~Wx))*((e[1]%e[1])*(E%E)) )
| dv2);
Quadrature qp1(2, 1);
H1 z(2, (double*)0, qp1), zai, eta,
n = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE( "int, int, Quadrature", 4, 2, qp1);
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 dv1(d(x).det());
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_bar[index]*( +( wx*~wx*U[0][0]+wx*~wy*U[0][1]+
wy*~wx*U[1][0]+wy*~wy*U[1][1] ) | dv1 );
stiff &= stiff_vol + stiff_dev;
}
Element_Formulation_Couple* Elastic_Contact_Q4::make(int en,
Global_Discretization_Couple& gdc) { return new Elastic_Contact_Q4(en,gdc); }
off-diagonal block formulations; Qi
Elastic_Contact_Q4::Elastic_Contact_Q4(int en, Global_Discretization_Couple& gdc)
: Element_Formulation_Couple(en, gdc) {
Omega_eh *elem = &(gdc.principal_discretization().omega_h()(en));
double *pp = ((Contact_Omega_eh*)elem)->projection_parameter(),
*nv = ((Contact_Omega_eh*)elem)->normal_vector();
C0 n_0(2,nv), n_1(2, nv+2);
C0 t(2, 2, (double*)0);
t[0][0] = ul[0]; t[0][1] = ul[1]; t[1][0] = ul[2]; t[1][1] = ul[3];
double lambda_0 = (double)(t[0]*n_0), lambda_1 = (double)(t[1]*n_1),
lambda_bar = (lambda_0+lambda_1)/2.0;
static const double EPSILON = 1.e-12;
if(lambda_bar > -EPSILON) stiff &= C0(4, 4, (double*)0);
else {
if(gdc.type() == interface_rigid_sled_gamma_h_type) pp += 2;
C0 alpha(2,pp);
Quadrature qp(1, 2);
H1 Z(qp),
N = INTEGRABLE_VECTOR_OF_TANGENT_BUNDLE("int, int, Quadrature", 2, 1, qp);
N[0] = (1.0-Z)/2.0; N[1] = (1.0+Z)/2.0;

572 Workbook of Applications in VectorSpace C++ Library


Contact Mechanics
H1 X = N*xl; J d_l(norm(d(X)(0))); C0 zero(0.0);
0
u = ( 1 – α 0 )n0 α0 n0
H0 N_lambda = ((~(H0)N) || zero ) & (zero || (~(H0)N));
û 0
H0 N_bar_0 = ( ((H0)N[0])*((1.0-alpha[0])*n_0) + ((H0)N[1])*(alpha[1]*n_1) ), = cû
N_bar_1 = ( ((H0)N[0])*(alpha[0]*n_0) + ((H0)N[1])*((1.0-alpha[1])*n_1) ), α1 n1 ( 1 – α 1 )n 1 û 1
N_u_bar = ( N_bar_0[0] | zero | N_bar_1[0] | zero )& 0

∫ NλT Nu
( zero | N_bar_0[1] | zero | N_bar_1[1] );
if(gdc.type() == interface_elastic_foundation_gamma_h_type) Q = dΓ ; where N ≡ Nc
u
stiff &= -(((~N_lambda)*N_u_bar)|d_l); Γc
else stiff &= (((~N_lambda)*N_u_bar)|d_l); Q0 {Γc-Γelastic founcation}
}
} Q1 {Γc-Γrigid punch}
Element_Formulation* Element_Formulation::type_list = 0;
Element_Type_Register element_type_register_instance;
static Elastic_Contact_Q4 elastic_contact_q4_instance(element_type_register_instance);
#include "\vs\ex\fe\contact_frictionless_sled_on_elastic_foundation\omega_h_n.cpp"
int main() {
C0 K_0, Q_0, K_1, Q_1, f_0, f_i, f_1, lambda, u_0, u_1;
interface_oh.contact_searching_algorithm(interface_oh, elastic_foundation_gamma_h_gd,
rigid_sled_gamma_h_gd, lambda_h, interface_gh, 0.05);
Matrix_Representation mr_K_0(elastic_foundation_gd);
Matrix_Representation mr_K_1(rigid_sled_gd);
Matrix_Representation_Couple mrc_Q_0(interface_elastic_foundation_gdc, 0, 0,
&(mr_K_0.rhs()) );
Matrix_Representation_Couple mrc_Q_1(interface_rigid_sled_gdc, 0, &(mrc_Q_0.rhs()),
&(mr_K_1.rhs()) );
for(int i = 0; i < 9; i++) {
mr_K_0.assembly(); K_0 &= (C0)(mr_K_0.lhs()); K0
mr_K_1.assembly(); K_1 &= (C0)(mr_K_1.lhs()); K1
mrc_Q_0.assembly(); Q_0 &= (C0)(mrc_Q_0.lhs()); Q0
mrc_Q_1.assembly(); Q_1 &= (C0)(mrc_Q_1.lhs());
f_0 &= (C0)(mr_K_0.rhs()); f_1 &= (C0)(mr_K_1.rhs()); f_i &= (C0)(mrc_Q_0.rhs()); Q1
Cholesky dK0(K_0); Cholesky dK1(K_1); f0, f1, fi
C0 K0_inv = dK0.inverse(); C0 QK_inv_0 = Q_0*K0_inv; C0 QKQ_0 = QK_inv_0*(~Q_0); K0-1, Q0 K0-1 Q0T
C0 K1_inv = dK1.inverse(); C0 QK_inv_1 = Q_1*K1_inv; C0 QKQ_1 = QK_inv_1*(~Q_1);
Cholesky mdQKQ(QKQ_0+QKQ_1, 1.e-12); K1-1, Q1 K1-1 Q1T
lambda &= mdQKQ * (QK_inv_0*f_0+QK_inv_1*f_1-f_i); (Q0 K0-1 Q0T + K1-1, Q1 K1-1 Q1T)-1
u_0 &= dK0*(f_0-(~Q_0)*lambda); u_1 &= dK1*(f_1-(~Q_1)*lambda); λ̂=(Q0 K0-1 Q0T + K1-1, Q1 K1-1 Q1T)-1
lambda_h = lambda; lambda_h = interface_gd.gh_on_gamma_h();
u_0_h = u_0; u_0_h = elastic_foundation_gd.gh_on_gamma_h(); (Q0 K0-1 f0 + Q1K1-1 f1 - fi)
u_1_h = u_1; u_1_h = rigid_sled_gd.gh_on_gamma_h(); û 0 = K0-1 (f0 - Q0Tλ̂)
cout << "contact forces:" << endl << lambda_h << "elastic foundation displacement:" << û 1 = K1-1 (f1 - Q1Tλ̂)
endl << u_0_h << "rigid sled displacement:" << endl << u_1_h;
if(i < 8) {
for(int j = 8; j < 12; j++) {
int node_order = rigid_sled_gd.gh_on_gamma_h().node_order(j);
rigid_sled_gd.gh_on_gamma_h().gh_array()[node_order][0] = ((double)(i+1));
}
cout << "step " << i << " :" << endl;
rigid_sled_gd.u_h() = rigid_sled_gd.gh_on_gamma_h();
interface_oh.contact_searching_algorithm(interface_oh, elastic_foundation_gamma_h_gd,
rigid_sled_gamma_h_gd, lambda_h, interface_gh,
mr_K_0, mr_K_1, mrc_Q_0, mrc_Q_1, 0.05);
}
}
return 0;
}

Listing 5•18 Frictionless sled on elastic foundation (project: “contact_frictionless_sled_on_elastic_


foundation.dsp” in project workspace: “fe.dsw”).

Workbook of Applications in VectorSpace C++ Library 573


Chapter 5 Advanced Finite Element Methods
5.3 Elastoplasticity
The project for the elastoplastic computation is “elastoplasticity” in the project workspace file “fe.dsw”.
In the elastoplastic problem, the stress-strain relation is path dependent. The radial return mapping algo-
rithm is introduced to accomplish the stress-strain path integration scheme. Without a special treatment, the tan-
gent moduli used for this problem will result in very slow convergent rate. Therefore, the consistent tangent
moduli, which is derived consistently with the radial return method, is used to retain the quadratic convergence
rate of the classic Newton-Raphson method. The implementation of the small deformation elastoplastic problem
in this workbook is entirely based on Simo and Taylor[1985]1.

5.3.1 Basic Theory

Non-linear Problem with Incremental Loading and Global Newton-Raphson Iteration


For general introduction on non-linear problem, we refer to Chapter 2 in this workbook. See Zienkiewicz and
Taylor[1991]2, for a more specific treatment in the context of finite element method.
An outer-loop of for control statement in the incremental loading algorithm is to advance the time step.
Then, within each time step, an inner-loop of for control statement apply the “global” Newton-Raphson iteration
to achieve a convergent solution. This solution is to be used as the initial value for the next time step. We denote
two consecutive time steps as tn and tn+1, with tn stands for the previous time step and tn+1 stands for the current
time step. The unknown displacement û in+1 is at the current time step tn+1 upon the i-th Newton-Raphson itera-
tion. The initial value of the current time step, û 0n+1, is set to the converged solution at the previous time step tn
as û 0n+1 = û n. Define the residual vector of the problem R( û i+1) at tn+1 as

∂R
R ( û i + 1 ) = R ( û i + δ û i ) ≅ R ( û i ) + ------- δ û i = 0
∂û û i Eq. 5•224

where û i+1 = û i + δ û i. The solution of the non-linear problem is obtained by setting the residual vector to van-
ish; i.e., find the root for the equation R(ui+1) = 0. Notice that the approximation step in Eq. 5•224 is done by the
Taylor expansion up to the first-order. From this approximated equation, the incremental displacement δui is the
solution of the simultaneous linear algebraic equations

∂R  – 1
δ û i = – -------  R ( û i ) ≡ K –T1 R ( û i ) Eq. 5•225
∂û û i 
∂R 
where K T ≡ –-------
∂û  is the tangent stiffness matrix.
 û 
i

1. Simo and Taylor, 1985, “Consistent tangent operators for rate-independent elastoplasticity” in Computer
Methods in Applied Mechanics and Engineering, v. 48, p.101-118
2. Chapter 7 in Zienkiewicz and Taylor, 1991, “The finite element method”, 4th ed. v. 2, McGraw-Hill, UK.

574 Workbook of Applications in VectorSpace C++ Library


Elastoplasticity
Hence, we need to define the quantities in the right-hand-side of Eq. 5•225 to compute δui. In finite element
method, the residual is the external load vector (Fext) subtracts the internal force divergence term expressed as

R = F ext – ∑ ∫ B T σ d Ω Eq. 5•226


e Ωe

Hence, the tangent stiffness matrix KT in Eq. 5•225 is obtained by taking the derivatives of R in Eq. 5•226 with
respect to the solution u as

∂R  ∂σ  T
-------
∂û
= –∑ ∫Ω BT ------
∂û
-  B dΩ = – ∑ ∫Ω B DT B dΩ ≡ – K T Eq. 5•227
û i
e e ûi e e

where, DT ≡ ∂ Ψ ⁄ ∂ ε is the tangent moduli, with the Cauchy stress σ ≡ ∂Ψ ⁄ ∂ε and where Ψ is the free energy
2 2

function. With KT defined, the incremental displacement δui is readily obtained by solving δ û i = KT–1 R ( ûi ) .
Note that the tangent moduli is the second derivatives with respect to free energy function. The minimization is
–1
with respect to free energy Ψ, and the equation δ û i = KT R ( û i ) is equivalent to the Newton’s Formula for opti-
mization of a function f(x) as δx = - df / d 2f (obtained from second-order Taylor expansion), with x += δx for

solution update.

Implementation of Incremental Loading and Global Newton-Raphson Iteration


The Program Listing 5•19 demonstrates the implementation of the incremental loading algorithm and the glo-
bal Newton-Raphson iteration of the non-linear problem described in the above. However, most intensive com-
putation is done at the element level. We defer the element level details to Section 5.3.2. The grand structure of
the algorithm is a nested loop with an outer incremental loading loop using a for statement, and an inner loop of
global Newton-Raphson iteration using a do-while statement.

Incremental Loading Loop: At the outer incremental loop, enclosed in the “for” statement, the incremental load-
ing is added to the boundary at the beginning of each time step; i.e., at the first iteration (i = 0), δu0 is set to the
incremental boundary condition δu0 of the time step. This quantity is then given to the element level for the com-
putation of the residual (R). The residual is to be modified by the reaction caused by the incremental boundary
condition as R -= KTδu0.
For the stress-strain relation in plasticity, which is path-dependent, we also need to introduce total incremen-
tal displacement ∆ û = ∑ δ û (with k = 0,1, 2, ..., i). ∆ui is the “total increment”; the summation of all incre-
i k

mental displacement from the first iteration up to the current iteration within the current time step. This quantity
is updated at the global level in the “main()” function, and is referred to in the element level.

Global Newton-Raphson Iteration: The inner loop is the global Newton-Raphson iteration inside the do-while
control statement. The heart of the algorithm is the member function “Matrix_Representation::assembly()” which
performs three heavy-duty steps to be coded in the Element_Formulation. These three steps are (1) apply exter-
nal load (Fext in Eq. 5•226) and subtract the internal force divergence term as in Eq. 5•226. The details of these
three steps in the element level is in Section 5.3.2.

Workbook of Applications in VectorSpace C++ Library 575


Chapter 5 Advanced Finite Element Methods

static ofstream ofs("temp.cpp", ios::out | ios::trunc);


int main() {
Matrix_Representation mr(gd);
delta_uh.matrix_representation() = &mr;
d_uh.matrix_representation() = &mr; total increment ∆u,
C0 delta_u,
du;
and increment δu
for(int i = 0; i < 10; i++) { Incremental loading loop
int count = 0;
const int MAX_ITERATION_NO = 50; const double EPSILON = 1.e-9;
double residual_norm, energy, energy_0;
if(i != 0) { ∆ û = 0.0, if i == 0
delta_u = 0.0; delta_uh = delta_u; length of ∆ û not known yet
}
for(int j = 86; j <= 92; j++) {
incremental boundary condition
d_gh[j][1] = 0.01; delta_gh[j][1] = 0.0; δu = 0.01
} ∆u = 0.0
d_uh = d_gh; delta_uh = delta_gh; uh = gh;
do {
ofs << (i+1) << "-time step, " << (++count) << "-iteration, " << endl; Global Newton-Raphson iteration
mr.assembly(); form KT, and Fext
du = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
if(count == 1) {
R -= KT δu0
for(int j = 86; j <= 92; j++) { u += δu
gh[j][1] += d_gh[j][1]; delta_gh[j][1] = d_gh[j][1]; d_gh[j][1] = 0.0; ∆u =δu
}
uh = gh; delta_uh = delta_gh; d_uh = d_gh;
δu = 0.0 (reset)
}
if(i == 0 && count == 1) delta_u &= C0(du.length(), (double*)0); ∆ û +=δ û
delta_uh = delta_u += du;
û += δ û
uh += du;
residual_norm = norm((C0)(mr.rhs())); residual norm = R • R
energy = fabs( (double)(du * ((C0)(mr.rhs())) ) ); Energy norm = δ û • R
if(count == 1) energy_0 = energy;
ofs << " residual norm: " << residual_norm <<" energy: " << energy << endl;
cout << " energy: " << energy << endl;
(C0)(mr.lhs()) = 0.0; (C0)(mr.rhs()) = 0.0;
} while(energy > (EPSILON*energy_0) &&
(count < MAX_ITERATION_NO) );
if(count == MAX_ITERATION_NO) {
ofs << " Warning: global Newton iteration failed!" << endl <<
" current solution: " << endl << uh;
return 0;
}
for(int j = 0; j < oh.total_element_no(); j++) { Update history data
Omega_eh *elem = &(oh(j));
((Non_Linear_Omega_eh*) elem)->update_history_data();
(.)n = (.)n+1
}
ofs << endl << (i+1) << "-time step solution: " << endl;
ofs << uh << endl;
}
ofs.close();
return 0;
}

Listing 5•19 Incremental loading algorithm and the global Newton-Raphson iteration of the non-linear
problem

576 Workbook of Applications in VectorSpace C++ Library


Elastoplasticity
Next, δ û is solved for by a matrix solver embedded in the member function “C0::operator / ()”, and the
update procedure includes updating (1) the total incremental displacement ∆ û += δ û , and (2) total displacement
û += δ û . The residual norm = R • R , and the energy norm = δ û • R , is reported to an output file. The energy
norm is used as the convergence criterion to terminate the Newton-Raphson iteration. After the first Newton-
Raphson iteration, the incremental boundary condition δu 0 is assigned to the total incremental boundary condi-
tion ∆u 1 = δu 0, and then set δu i = 0 for all i > 0, so no further subtraction of reaction of R -= KTδu i caused by
incremental boundary conditions is repeated unnecessarily by the default behavior of “fe.lib”.

5.3.2 Radial Return Mapping Algorithm


For the fundamentals of plasticity theory, we refer to two popular graduate texts by Fung[1965]1, and Malv-
ern[1969]2.

Theory of Elastoplasticity
Following the development in Simo and Taylor[1985]3 for the infinitesimal case, the deviatoric stress (s) and
deviatoric strain (e) tensors are defined as

s = σ – 1--3- tr ( σ )1 , and e = ε – 1--3- tr ( ε )1 Eq. 5•228

with second-order unit tensor, 1, and the trace operator, tr( ). The von Mises yield criterion is

f ( ξ, α, κ ) ≡ ξ – 2- κ ( e p ) ≤ 0 , Eq. 5•229
3

The plastic strain is integrated as


t
2
ep = -
3
d p ( τ ) dτ , Eq. 5•230
0

with ξ = s – α . where α is the back stress for the kinematic hardening. The kinematic hardening function κ(e p),
depends on plastic strain ep, is for the isotropic hardening, where dp is the plastic strain rate. The consistency
condition, or the normality rule, in the theory of plasticity requires that stress remains on the yield surface during
the plastic deformation. This is equivalent to assuming maximum plastic dissipation for a quasi-equilibrium pro-
cess in the context of irreversible thermodynamics. From the consistency condition, we get the deviatoric stress-
strain rate constitutive equation as

1. “Foundations of solid mechanics”, Prentice-Hall, Inc.


2. “Introduction to the mechanics of a continuous medium”, Prentice-Hall, Inc.
3. “Consistent tangent operators for rate-independent elastoplasticity” in Computer Methods in Applied
Mechanics and Engineering, v. 48, p.101-118

Workbook of Applications in VectorSpace C++ Library 577


Chapter 5 Advanced Finite Element Methods
·
s· = 2µ [ I – γ ( n̂ ⊗ n̂ ) ] : e , Eq. 5•231

where plastic consistency parameter γ is defined as

1
γ ≡ d p = -----------------------------
κ' + H’α
Eq. 5•232
1 + 3µ -------------------

µ is the shear modulus. n̂ = ξ TRn + 1 ⁄ ξ nTR+ 1 , where superscript “TR” denotes the trial elastic state. The primes
in κ and Hα denotes the first-derivative with respect to ep. The so-called continuum elastoplastic tangent moduli
·
ae p for the constitutive law σ = a : ε· in rate form is thus obtained as
ep

a ep = K ( 1 ⊗ 1 ) + 2µ I – 1--3- ( 1 ⊗ 1 ) – 2µ γ ( n̂ ⊗ n̂ ) , Eq. 5•233

where K is the bulk modulus.

Stress-Strain Path Integration Algorithm—Closest-Point-Projection


Elastoplastic constitutive equation as manifested in the tangent moduli of Eq. 5•233 is path-dependent. A
stress-strain path can be constructed with an integration scheme for an ordinary differential equation of the form
·
σ = a ep: ε· . Along the course of integration, the closest-point-projection method (Figure 5•15) is used to
enforce the von Mises yield criterion (Eq. 5•229) as an inequality constraint. This is to bring back the stress
state, sn+1TR, to the yield surface at sn+1 when the constraint is violated. In the case of von Mises yield criterion
with associative flow law, the closest-point-projection reduces to the radial return mapping algorithm. We note
by passing that more than one method is possible to accomplish the radial return mapping algorithm. In the
“Implementation 1” of the finite deformation elastoplasticity, in section , the alternative cutting-plane method is
used, which makes the conceptual formulation to satisfy the objectivity principle for the finite deformation prob-
lem a lot easier than the closest-point-projection method, where we need not introduce the covariant operations
in the algorithm.
The closest-point-projection method is consist of two primary phases: elastic-predictor and plastic-correc-
tor.

elastic predictor
sTRn+1
plastic corrector

sn sn+1
Yield Surface

Figure 5•15 Closet-poinst-projection stress correction, when the constraint is violated.

578 Workbook of Applications in VectorSpace C++ Library


Elastoplasticity
Elastic-Predictor: First we assume there is no plastic deformation occurs. All incremental deviatoric strain ∆en+1
is totally allocated to the deviatoric elastic response as (∆en+1elastic)0 = ∆en+1 and (∆en+1plastic)0 = 0 where super-
script “e” denotes the elastic part, and superscript “p” denotes the plastic part, so,

s nTR+ 1 = s n + 2µ ∆ e n + 1 Eq. 5•234

where sn is obtained from stored data of the last time step, and superscript “TR” denotes the trial elastic state. We
also have

ξnTR+ 1 = s n + 1 – α n , and n̂ =
TR
ξ TR n + 1 ⁄ ξnTR+ 1 Eq. 5•235

where back stress αn is also obtained from stored data of the last time step.

Plastic-Corrector: The consistency condition requires the state of stress remains on yield surface during plastic
deformation. This leads to

φ ( γ ∆t ) ≡ – 2-3 κ ( e np + 1 ) + ξnTR+ 1 – 2µ γ ∆t + 2- ∆H α = 0 ,
3
Eq. 5•236

where the last two terms 2µγ∆t is the magnitude of plastic relaxation stress and 2 ⁄ 3 ∆Hα is the magnitude of
translation on “π-plane” of yield surface due to kinematic hardening with the “π-plane” proportional length-fac-
tor 2 ⁄ 3 considered. ep is integrated as

e np (+k 1+ 1 ) = e np (+k )1 + 2- ( γ ∆t ) Eq. 5•237


3

Define λ = γ∆t, the local Newton-Raphson iteration to enforce the consistency condition provides the Newton’s
formula for numerical root-finding on Eq. 5•236 as

δλ = – φ ( λ ) ⁄ d φ ( λ ) , with the solution update as λ += δλ Eq. 5•238

the back stress and deviatoric stress are integrated for updating history data according to

αn + 1 = αn + -2 ∆H α ( enp + 1 ) n̂ , and s n + 1 =
3
αn + 1 + -2 κ ( enp + 1 ) n̂
3
Eq. 5•239

then, the Cauchy stress is updated by

σn+1 = sn+1 + K tr(ε) 1. Eq. 5•240

Workbook of Applications in VectorSpace C++ Library 579


Chapter 5 Advanced Finite Element Methods
Consistent Tangent Moduli
It turns out the convergence rate for the global Newton-Raphson iteration with the continuum tangent moduli
in Eq. 5•233 is unbearably slow. This is because the incremental loading always has a finite increment, accord-
ingly the stress-strain path integration step in the radial return mapping is discrete in nature. If we use the contin-
uum tangent moduli for the stress integration method. The tangent moduli is implicitly taken as constant during
tn to tn+1. Therefore, error is introduced and the iterative method will lose its most desirable quadratic conver-
gent rate from the Newton-Raphson method. Simo and Taylor[1985]1 was able to derive, consistent with the
radial return mapping method, a tangent moduli which retains the quadratic convergent rate. This discrete ver-
sion of tangent moduli is the consistent tangent moduli;

a ep = K ( 1 ⊗ 1 ) + 2 µβ I – 1--3- ( 1 ⊗ 1 ) – 2 µ γ ( n̂ ⊗ n̂ ) Eq. 5•241

where,

[ κ n + 1 + ∆H α ] 1
β ≡ 2-3 -----------------------------------
- , and γ ≡ -------------------------------------------
[ κ' + H’ ]
- – (1 – β) = γ – (1 – β)
ξ TR
n+1 α n+1
Eq. 5•242
1 + ----------------------------------

We can check this formula by investigating that whether in the limit of infinitesimal incremental loading step,
the consistent tangent moduli does degenerate to the continuum tangent moduli; i.e., to have β = 1 , thus
γ = γ – ( 1 – β ) = γ . This follows immediately from the definitions of β and γ in Eq. 5•242.

5.3.3 Object-Oriented Modeling for Stress-Strain Integration Scheme


The incremental stress-strain path is an integration scheme that is consist of updating variables from {sn, αn,
e pn} at tn to {sn+1, αn+1, e pn+1} at tn+1. These data are referred to as “history data”. The deviatoric stress (s), back
stress (α), and plastic strain (ep) are quantities associated with each element, or more specifically with each
quadrature point of the element. Therefore, one can model these quantities as H0 type private data members
associated with an element (see Program Listing 5•20). Therefore, we need to extend the capacity of standard
element class Omega_eh in “fe.lib” to have these quantities. Applying the concept of programming by specifi-
cation, in the mechanisms provided by C++, this amounts to define a public derived class using Omega_eh as
its base class. This way the interface and implementation of the base class Omega_eh are inherited by the public
derived class Non_Linear_Omega_eh. This derived class becomes a more specific use of the more general base
class it is derived from. At the end of each time step, Omega_eh::update_history_data( ) is called by main()
function in Program Listing 5•19.
In addition, several global level variables need to be linked to the element level. These include various dis-
placement variables û , ∆ û , δ û . These links are coded in the private member function __initialization() of the
public derived Element_Formulation class “Elastoplastic_B_bar_Q41”. This member function will be called
in the definition of the constructor “Elastoplastic_B_bar_Q41::Elastoplastic_B_bar_Q41(...)”.

1. “Consistent tangent operators for rate-independent elastoplasticity” in Computer Methods in Applied


Mechanics and Engineering, v. 48, p.101-118

580 Workbook of Applications in VectorSpace C++ Library


Elastoplasticity

Implementation of Radial Return Mapping with Consistent Tangent Moduli


The stress-strain integration is implemented in Program Listing 5•20. The derived class Elastoplastic_B_bar
_Q41 from the base class Element_Formulation is implemented with the elastoplastic computation on top of the
B_bar_Q41. We refer to that section for discussion on the details of the B-method (page 502).
Τhe basic steps of the radial return mapping is an elastic-predictor followed by a plastic-corrector. In the elas-
tic predictor, the trial state of elastic deviatoric stress is

sTRn+1 = sn + 2µ∆e Eq. 5•243

where sn is obtained from history data. In order to get the trial deviatoric stress, we first need to compute the ele-
ment total incremental displacement (∆ û e = ∆ û +∆u, free plus fixed degree of freedom), and then total incre-
mental deviatoric strain (∆e = Bdev ∆ û e). Then, we can get to the 2µ∆e term in the elastic predictor (Eq. 5•243).
Note that Bdev is the deviatoric part of the B matrix. This trial state of elastic deviatoric stress (sTRn+1) may or
may not shoot out of the yield surface. This is inspected by checking the yield radius (R) against the norm of
ξTRn+1, where ξTRn+1 = sTRn+1 - αn. The back stress αn is obtained from the history data. If the norm of ξTRn+1 is
smaller than or equal to R, the state of stress remains inside the yield surface in the elastic state, we have sn+1 =
sTRn+1. Otherwise, the constraint is violated. The state of the stress is outside the yield surface and the plastic-cor-
rector follows.
The plastic-corrector is to return to the state of stress back to the yield surface—the consistency condition;
i.e., to satisfy φ ( λ ) ≡ – 2-3 κ ( e np + 1 ) + ξnTR+ 1 – 2 µλ + 2- ∆H α = 0 (with λ = γ∆t) in Eq. 5•236. With the Vector-
3
Space C++ Library, root-finding is very simple. Just declare C1 type variables and function for this equation. The
Newton iteration is furnished by the formula δ λ = – φ ( λ ) ⁄ d φ ( λ ) , with the solution updated by λ += δλ. The
C++ codes parallel the mathematical expression exactly. After we obtain the converged result form the local
Newton iteration, one can update s, α, and ep according to Eq. 5•237 and Eq. 5•239.

The consistent tangent moduli (Eq. 5•241) is readily obtained with all these parameters available. The
Cauchy stress is computed with averaged volumetric strain εv = Bvol û e = tr(ε)1, and then plug the volumetric
strain into the second term in the right-hand-side of Eq. 5•240. The rest of the Program Listing 5•20is the (tan-
gent) stiffness, and the residual. The stiffness is computed as defined in
T ep
∫B a B dΩ
Ωe
with B in place of the B for the B-formulation. The residual only needed to be computed for the non-linear prob-
lem, where we need to subtract the internal stress divergence term out of the “force” vector as

∫ B σ dΩ
T

Ωe
The implementation of the radial return mapping method is shown is Program Listing 5•20.

Workbook of Applications in VectorSpace C++ Library 581


Chapter 5 Advanced Finite Element Methods

static Quadrature qp(2, 4); class Non_Linear_Omega_eh


class Non_Linear_Omega_eh : public Omega_eh {
H0 the_s, the_s_n,
deviatoric stress {sx, sy, sxy}T
the_ep, the_ep_n, total plastic strain
the_alpha, the_alpha_n; back stress
void __initialization();
public:
Non_Linear_Omega_eh(int en = 0, int etn = 0, int mtn = 0, int nen = 0, int* ena = 0) :
Omega_eh(en, etn, mtn, nen, ena) { __initialization(); }
H0& s_n() { return the_s_n; }
H0& ep_n() { return the_ep_n; }
H0& alpha_n() { return the_alpha_n; }
H0& s() { return the_s; }
H0& ep() { return the_ep; }
H0& alpha() { return the_alpha; }
void update_history_data();
};
void Non_Linear_Omega_eh::__initialization() {
the_s &= H0(3, (double*)0, qp);
the_ep &= H0((double*)0, qp);
the_alpha &= H0(3, (double*)0, qp);
the_s_n &= H0(3, (double*)0, qp);
the_ep_n &= H0((double*)0, qp);
the_alpha_n &= H0(3, (double*)0, qp);
} update function at the end of each time
void Non_Linear_Omega_eh::update_history_data() {
the_s_n = the_s;
step. (.)n = (.)n+1
the_ep_n = the_ep;
the_alpha_n = the_alpha;
}
class Elastoplastic_B_bar_Q41 : public Element_Formulation {
C0 delta_ul;
H0 s, s_old, ep, ep_old, alpha, alpha_old;
void __initialization(int);
public:
Elastoplastic_B_bar_Q41(Element_Type_Register a) : Element_Formulation(a) {}
Element_Formulation *make(int, Global_Discretization&);
Elastoplastic_B_bar_Q41(int, Global_Discretization&);
};
void Elastoplastic_B_bar_Q41::__initialization(int en) {
delta_ul &= gd_delta_uh.element_free_variable(en) + gd_delta_uh.element_fixed_variable(en); ∆ û e = ∆ û + ∆ û
ul += gl;
û e = û + u
gl = gd_d_uh.element_fixed_variable(en);
Omega_eh *elem = &(oh(en)); δ û e = δu
s &= ((Non_Linear_Omega_eh*) elem)->s(); (.)n and (.)n+1
ep &= ((Non_Linear_Omega_eh*) elem)->ep();
alpha &= ((Non_Linear_Omega_eh*) elem)->alpha();
s_old &= ((Non_Linear_Omega_eh*) elem)->s_old();
ep_old &= ((Non_Linear_Omega_eh*) elem)->ep_old();
alpha_old &= ((Non_Linear_Omega_eh*) elem)->alpha_old();
}
Element_Formulation* Elastoplastic_B_bar_Q41::make(int en, Global_Discretization& gd) {
return new Elastoplastic_B_bar_Q41(en,gd);
}

Listing 5•20 History data and implementation of class Non_Linear_Omega_eh.

582 Workbook of Applications in VectorSpace C++ Library


Elastoplasticity

static const double i_dev[3][3] = {


I_dev
{(1.0-1.0/3.0), -1.0/3.0, 0.0},
{-1.0/3.0, (1.0-1.0/3.0),0.0},
{0.0, 0.0, 1.0}
};
C0 I_dev = MATRIX("int, int, const double*", 3, 3, i_dev[0]);
static double one[3] = {1.0, 1.0, 0.0};
One = VECTOR("int, const double*", 3, one);
static double identity_mu[3][3] = { {1.0, 0.0, 0.0} , {0.0, 1.0, 0.0}, {0.0, 0.0, 0.5} };
I_mu
C0 I_mu = MATRIX("int, int, const double*", 3, 3, identity_mu[0]);
Elastoplastic_B_bar_Q41::Elastoplastic_B_bar_Q41(int en, Global_Discretization& gd) : class Elastoplastic_B_bar_Q41
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.0-Zai)*(1.0-Eta)/4.0; N[1] = (1.0+Zai)*(1.0-Eta)/4.0;
N[2] = (1.0+Zai)*(1.0+Eta)/4.0; N[3] = (1.0-Zai)*(1.0+Eta)/4.0;
H1 X = N*xl; H0 Nx = d(N) * d(X).inverse(); J dv(d(X).det());
H0 w_x = INTEGRABLE_SUBMATRIX("int, int, H0&", 1, 2/*nsd*/, Nx), wx, wy, B, B_bar;
wx &= w_x[0][0]; wy &= w_x[0][1]; C0 zero(0.0);
B &= (~wx || zero) &
(zero || ~wy ) &
(~wy || ~wx );
H0 N_vp(qp); N_vp = 1.0;
H0 mN_vp = N_vp & N_vp & zero;
C0 C = ((~mN_vp)*B) | dv, E = (N_vp) | dv, W = C/E; Bvol
H0 B_bar_vol = (1.0/3.0) * (mN_vp % W[0]), Bdev
B_dev = I_dev * B;
B = Bdev + Bvol
B_bar &= B_dev + B_bar_vol;
__initialization(en); retreive displacement variables u, δu,
H0 delta_e = INTEGRABLE_VECTOR("int, Quadrature", 3, qp); delta_e = 0.0; ∆u, and history data s, α, ep
for(int i = 0; i < nen; i++)
Elastic Predictor begins
delta_e += B_dev(i*ndf)*delta_ul[i*ndf] + B_dev(i*ndf+1)*delta_ul[i*ndf+1];
s = s_n + delta_e*(2.0*mu_*I_mu); ∆ û e = ∆ û +∆u
H0 ZAI = s - alpha_n, ZAI_norm(qp); ∆e = Bdev ∆ û e
#define qv quadrature_point_value
sTRn+1 = sn + 2µ∆e
ξTRn+1 = sTRn+1 - αn
for(int i = 0; i < qp.no_of_quadrature_point(); i++) {
C0 ZAI_0_q, ZAI_1_q, ZAI_2_q;
ZAI_0_q &= ZAI[0].qv(i); ZAI_1_q &= ZAI[1].qv(i); ZAI_2_q &= ZAI[2].qv(i);
ZAI_norm.qv(i) = sqrt((double)(ZAI_0_q.pow(2)+ZAI_1_q.pow(2)+2.0*ZAI_2_q.pow(2))); ξ = ξ 2 00 + ξ 2 11 + 2 ξ 2 01
}
H0 KAPA(qp); KAPA = Y_infinity - (Y_infinity - Y_0) * exp(-iota*ep);
H0 radius = sqrt(2.0/3.0)*KAPA, κ =Y ∞ – ( Y ∞ – Y 0 )e ( –ιe p )
yield_ratio = ZAI_norm/radius;
H0 Dt(3, 3, (double*)0, qp), lambda(qp), 2
sigma = INTEGRABLE_VECTOR("int, Quadrature", 3, qp); R = --- κ ( e p )
3
for(int i = 0; i < qp.no_of_quadrature_point(); i++) {
C0 s_q, ep_q, ep_n_q, alpha_q, alpha_n_q, ZAI_q, ZAI_norm_q, sigma_q, Dt_q,
aep; tangent moduli
σ; Cauchy stress
B_bar_q, B_vol_q, radius_q, lambda_q, yield_ratio_q;
s_q &= s.qv(i); ep_q &= ep.qv(i); ep_n_q = ep_n.qv(i);
alpha_q &= alpha.qv(i); alpha_n_q = alpha_n.qv(i); λ = γ∆t
ZAI_q &= ZAI.qv(i); ZAI_norm_q &= ZAI_norm.qv(i);
loop over each quadrature point
sigma_q &= sigma.qv(i); Dt_q &= Dt.qv(i);
B_bar_q = B_bar.qv(i); B_bar_vol_q = B_bar_vol.qv(i); alias; subscript “q” denotes quadrature
radius_q &= radius.qv(i); lambda_q &= lambda.qv(i); point index
yield_ratio_q &= yield_ratio.qv(i);

Workbook of Applications in VectorSpace C++ Library 583


Chapter 5 Advanced Finite Element Methods
if(ZAI_norm_q > radius_q) { chech if the elastic predictor shoot out
ofs << "***Plastic state: element # " << en << ", quadrature point # " << i of the yield surface
<< ", yield ratio: " << yield_ratio_q << endl;
double EPSILON = 1.e-20, d_lambda_norm, d_lambda_norm_0; Plastic Corrector begins
int MAX_ITERATION_NO = 50, count = 0; root-finding with
C0 d_lambda(0.0); C1 phi, LAMBDA(0.0); local Newton iteration on each quadra-
do {
C1 EP = ep_n_q+sqrt(2.0/3.0)*LAMBDA, ture point , λ0 = 0
KAPA = Y_infinity - (Y_infinity - Y_0)*exp(-iota*EP), e np + 1 = e np + -2 λ
3
RADIUS = sqrt(2.0/3.0)*KAPA,
delta_H_alpha = sqrt(2.0/3.0)*d_H_alpha*LAMBDA;
phi &= -RADIUS + = Y ∞ – ( Y ∞ – Y 0 )e ( –ιe
ZAI_norm_q - (2.0*mu_*(LAMBDA) + sqrt(2.0/3.0)*delta_H_alpha);
d_lambda = - ((C0)phi)/d(phi); φ ( λ ) ≡ – 2-3 κ ( e np + 1 ) + ξnTR+ 1
((C0) LAMBDA) += d_lambda;
d_lambda_norm = norm(d_lambda);
if(count == 0) d_lambda_norm_0 = d_lambda_norm; – 2µγ∆t + 2- ∆H α = 0
3
} while(d_lambda_norm>(d_lambda_norm_0*EPSILON) &&
count++<MAX_ITERATION_NO);
lambda_q = (C0) LAMBDA; δ λ = – φ ( λ ) ⁄ d φ ( λ ) ; root finding by
if(count == MAX_ITERATION_NO) { local Newton iteration with update λ +=
ofs << "Warning: local Newton iteration failed to converge!" << endl << δλ
" element # " << en << ", quadrature point # " << i << endl;
} update history data
ep_q = ep_n_q + sqrt(2.0/3.0)*lambda_q;
C0 n = ZAI_q/ZAI_norm_q; e np (+i 1+ 1 ) = e np (+i )1 + 2- ( λ )
double delta_H_alpha = sqrt(2.0/3.0)*d_H_alpha*(double)lambda_q; 3
alpha_q = alpha_n_q + sqrt(2.0/3.0)*delta_H_alpha*n;
C1 EP((double)ep_q), KAPA = Y_infinity - (Y_infinity - Y_0)*exp(-iota*EP); n̂ = ξ TRn + 1 ⁄ ξnTR+ 1
double factor = 1.0+1.e-8;
s_q = alpha_q + (sqrt(2.0/3.0)*((double)((C0)KAPA))*factor)*n; // (3.3); (iv-2),
double gamma = 1.0/(1.0+((((double)d(KAPA))+d_H_alpha)/3.0/mu_)); αn + 1 = αn + -2 ∆H α ( e np + 1 ) n̂
3
#if defined(__TEST_CONSISTENT_TANGENT_OPERATOR)
double beta=sqrt(2.0/3.0)*((double)((C0)KAPA)+delta_H_alpha)/(double)ZAI_norm_q, sn + 1 = αn + 1 + -2 κ ( e np + 1 ) n̂
3
gamma_bar = gamma - (1.0-beta);
Dt_q = lambda_*(One%One) +
2.0 * mu_ * (beta * I_mu - gamma_bar * (n%n)); 1
#else γ ≡ d p = ----------------------------
1 + κ’ + H’α
-
Dt_q = lambda_*(One%One) + 2.0 * mu_ * (I_mu - gamma * (n%n)); -------------------
#endif 3µ
} else {
[ κ n + 1 + ∆H α ] .
Dt_q = lambda_*(One%One) + 2.0*mu_*I_mu;
ofs << " Elastic state: element # " << en << ", quadrature point # " << i
β ≡ 2-3 -----------------------------------
- , γ = γ −(1−β)
<< ", yield ratio: " << yield_ratio_q << endl; ξ nTR+ 1
}
C0 eps_v(3, (double*)0);

εv = Bvol û e = tr(ε)1
for(int i = 0; i < nen; i++)
eps_v += B_bar_vol_q(i*ndf)*ul[i*ndf] + B_bar_vol_q(i*ndf+1)*ul[i*ndf+1];
eps_v *= 3.0; σn+1 = sn+1 + K Ttr(ε) 1
stiffness += ∫ B a B dΩ
sigma_q = s_q + K_*eps_v; ep
}
stiff &= ((~B_bar)*Dt*B_bar)|dv; Ωe

∫ B σ dΩ
T
force &= -((~B_bar)*sigma)|dv; force -=
} Ωe

Listing 5•21 Radial return mapping alogrithm for the elastoplastic element.

584 Workbook of Applications in VectorSpace C++ Library


Elastoplasticity

δu = 0.01m

18 m

5m

5m
Figure 5•16 Perforated strip under uniaxial extension.
5.3.4 Perforated Strip under Uni-axial Extension
A benchmark test problem for elastoplasticity is shown in Figure 5•16. For the experimental results see Theo-
caris and Marketos[1964]1. The geometry and its boundary conditions are described below. Only a quadrant of
the problem is modeled due to the symmetry of the problem. Plain strain is assumed. The material properties are
Young’s Modulus E = 70 MPa, Poisson ratio ν = 0.2. The isotropic hardening law is given specifically as

κ = Y ∞ – ( Y ∞ – Y 0 )e ( –ιep ) Eq. 5•244

where, Y0 = Y∞ = 0.243 MPa, ι = 0.1. No kinematic hardening is assumed, for simplicity. This test problem will
also be used in the two implementations of the finite deformation elastoplasticity in Section 5.4 for comparison.
The project “elastoplasticity” in project workspace file “fe.dsw” implements the example problem in this sec-
tion and the algorithm discussed in the last section. The yield ratio contours of the perforated strip after 10 incre-
mental loading steps are shown in Figure 5•17. The plastic zone is developed in area where the highest intensity
of the maximum shear values are expected. This zone runs about 45 o upwards from point A, forming a plastic
enclave. This direction is mostly parallel to the point-wise maximum shear directions inside this zone.
For the non-linear problem at hand, the iterative process requires the “matrix assembly” and “solution phase”
been processed many times, comparing to a linear problem where this process is only go thourgh once. There-
fore, the computing time becomes a concern for us. However, the computing time is very sensitive to the size of
the problem. We use “n” to represent the size of a problem, which may stand for total node/element number of a
problem. The element formulation in the assembly process is linearly proportional to “n”. We denote this linear
dependency as O(n). The memory space required for storing global stiffness matrix is proportional to n2 or

1. J. Mech. Phys. Solids, v. 12, p. 377.

Workbook of Applications in VectorSpace C++ Library 585


Chapter 5 Advanced Finite Element Methods

3.50
3.00
2.50
2.00
1.50
1.00
0.75
0.50
0.25
A 0.00

Figure 5•17 Contours of yield ratio (normalized to initial yield value) after
accumulating 10 incremental steps (δu = 0.01 m; total streching ~0.56%).

denote as O(n2), while the matrix solution problem is known to be a process of O(n3). As the size of the problem
increases, obviously the matrix solution process will become very costly. Therefore, optimization methods,
introduced in Chapter 2, that alleviates matrix solution process will be most desirable when the problem size
becomes large. We discuss (1) the conjugate gradient method that completely avoids the need for inverting a
matrix, and (2) the quasi-Newton BFGS method that only inverting matrix once in a while. A word of caution is
as you have seen in Chapter 2, the classical Newton method is the most powerful one that has quadratic conver-
gence rate. Use of these other methods may slow down the convergence rate substantially. The trade-off is then
many more iterations are needed to get to the same level convergence, when the second-order information (the
inverse of Hessian in this case is the decomposition of the global stiffness matrix) is to be avoid either partially
or completely.

Conjugate Gradient Method


The finite element implementation for the conjugate gradient method has use the fact that we are dealing
with a quadratic programming problem with minimization of energy functional (quadratic) of the type

1
Π ( u ) = --- u T Ku – u T f Eq. 5•245
2

where K is the global stiffness matrix and f is the global force vector and we drop the “hat” sign for the nodal
solution as u for simplicity. For the conjugate gradient method the initial search direction is taken as the negative
gradient as

586 Workbook of Applications in VectorSpace C++ Library


Elastoplasticity
p 0 ≡ – g 0 = – D u Π ( u 0 ) = f0 – Ku 0 ≡ r 0 Eq. 5•246

where Du is the directional derivatives, and r0 is the residual. The the solution u, for the next iteration, is updated
along the search direction p as

uk+1 = uk + αk pk Eq. 5•247

The that minimized the objective functional for this quadratic programming case can be shown as1

g kT p k
α k = – -----------------
- Eq. 5•248
p kT Kp k

The next search direction is so chosen that it is orthogonal to the set of previously chosen search directions as

g kT + 1 Kp k
p k + 1 = – g k + 1 + β k p k, where β k = -----------------------
- Eq. 5•249
p kT Kp k

and gk+1 is g evaluated at uk+1. In Chapter 2, the conjugate gradient method is introduced with line search
method that no Hessian (global stiffness matrix) is required. In that case, the convergence will be extremely slow.
For finite element method, not assembling the global stiffness matrix gives some advantage that the required
memory space grow as O(n2) as discussed earlier. However, if the memory is not of very much concern, the vital
second-order information (the Hessian), will help convergence by a lot. The decision to assemble the global stiff-
ness matrix also depends on the fact that computation to form element stiffness matrix is un-avoidable. In order
to compute the residual according to Eq. 5•246, the element stiffness ke must be computed anyway. Since ele-
ment stiffness matrices are computed, assembly of the global stiffness matrix provides vital second-order infor-
mation which facilitates the convergence of the conjugate gradient method. Eq. 5•246 to Eq. 5•249 are
implemented for each incremental loading step, with its index “i”, as

1 const int MAX_ITERATION_NO = 20;


2 const double EPSILON = 1.e-9;
3 if(i != 0) { // initial state of a incremental loading step
4 delta_u = 0.0; // initial free total increment of displacement set to zero
5 delta_uh = delta_u;
6 }
7 for(int j = 86; j <= 92; j++) {
8 d_gh[j][1] = 0.01; // displacement increment on top
9 delta_gh[j][1] = 0.0;
10 }
11 d_uh = d_gh;
12 delta_uh = delta_gh;

1. see p.244 in Luenberger, D.G., 1984, “Linear and nonlinear programming”, 2nd eds., Addison-Wesley Publishing Com-
pany, Inc., Reading, Massachusetts.

Workbook of Applications in VectorSpace C++ Library 587


Chapter 5 Advanced Finite Element Methods
13 uh = gh;
14 double norm_p_0; C0 p;
15 do {
16 cout << (i+1) << "-time step, " << (count) << "-iteration, " << endl;
17 if(count == 1) { // first iteration only
18 for(int j = 86; j <= 92; j++) {
19 gh[j][1] += d_gh[j][1]; // u += du
20 delta_gh[j][1] = d_gh[j][1]; //
21 d_gh[j][1] = 0.0; // after first iteration reset du = 0
22 }
23 uh = gh;
24 delta_uh = delta_gh;
25 d_uh = d_gh;
26 }
27 C0 g, K;
28 for(int k = 0; k < MAX_ITERATION_NO; k++) { // “partial” conjugate gradient loop
29 if(k == 0) {
30 Matrix_Representation::Assembly_Switch = Matrix_Representation::ALL;
31 mr.assembly();
32 K = ((C0)(mr.lhs())); // Kk
33 p = ((C0)(mr.rhs())); // pk = - gk; (gk = rk)
34 g = -p;
35 (C0)(mr.rhs()) = 0.0;
36 (C0)(mr.lhs()) = 0.0;
37 } g kT p k
38 double alpha = (-g*p)/(p*(K*p)); α
// k = – -----------------
-
p kT Kp k
39 du = alpha*p;
40 if(i == 0 && count == 1 && k == 0) delta_u &= C0(p.length(), (double*)0);
41 delta_uh = delta_u += du; // update increment
42 uh += du;
43 if(k != 29) { // compute next search direction pk+1
44 Matrix_Representation::Assembly_Switch = Matrix_Representation::ALL;
45 mr.assembly();
46 C0 g1 = -(C0)(mr.rhs()); // gk+1
47 K = ((C0)(mr.lhs())); // Kk+1
48 (C0)(mr.rhs()) = 0.0;
49 (C0)(mr.lhs()) = 0.0; // g kT + 1 Kp k
pk + 1 = – g k + 1 + β k p k, where β k = ----------------------- -
50 double beta = g1*(K*p)/(p*(K*p)); p kT Kp k
51 p = -g1 + beta * p;
52 g = g1; // update search direction p and gradient g
53 }
54 if(count == 1 && k == 0) norm_p_0 = (double)norm(p); // save initial || p0||
55 if(((double)norm(p)) < (norm_p_0*EPSILON)) break; // convergence if || pk+1|| > ε || p0||
56 cout << k << " p norm: " << ((double)norm(p)) << endl;

588 Workbook of Applications in VectorSpace C++ Library


Elastoplasticity
57 }
58 } while(((double)norm(p)) > (EPSILON*norm_p_0) && (count < MAX_ITERATION_NO) );

The global stiffness matrix is assembled in this algorithm, but no matrix solution, by direct method, is performed.
The convergence rate compared to the classical Newton-Raphson method is still very very slow. The implemen-
tation of the conjugate gradient method for quadratic programming case can be activated by setting the macro
definition “__TEST_CONJUGATE_GRADIENT_METHOD” in project “elastoplasticity” of project workspace
file “fe.dsw”.

Quasi-Newton BFGS Method


In the conjugate gradient method for the quadratic programming problem in the above, we have completely
avoid the use of matrix solver (direct method) for the global stiffness matrix. The convergence rate can be
improved if matrix solver is used. By assuming that this matrix solver is still costly for a large size problem, we
want to use it sparingly. The BFGS method, introduced in Chapter 2, require matrix solver “only once in a
while”. When the inverse of the Hessian, Bi, is formed, the subsequent Bi+1 to Bi+n are obtained by an updating
formula which is relative inexpensive to compute as (see Eq. 2•39 in page 134 of Chapter 2)

 q i • ( B i q i ) p i ⊗ p i p i ⊗ ( B i q i ) + ( B i q i ) ⊗ p i
B iBFGS = B i +  1 + --------------------------- ------------------ – ---------------------------------------------------------------- Eq. 5•250
+1
 qi • pi  pi • pi qi • pi

The basic algorithm is described as follows

Step 1: search direction—compute di = - Bi gi , where Bi = K-1(xi), and gi = ∇Π(xi).


Step 2: loop—partial quasi-Newton method, loop over “n” dimension
a: line search by taking α = 1; i.e., p i = x i+1 - x i = αd i = d i ,
b: gradient difference—qi = gi+1 - gi , where gi+1 = ∇Π(xi+1),
c: update inverse of Hessian for B i + 1 from Eq. 5•250 .
Step 3: restart—repeat Step 1 and 2, and reset B .

For size of B as “n”, step 1 to step 2 can be repeated every “m” times (m < n). This m-iteration loop can be
repeated many times until all the convergence criteria are met. This additional outer loop is known as the partial
quasi-newton method, which aids the convergent rate of a numerical iterative process as opposed to theoretical
one that the convergence is guaranteed in “n” steps. The implementation of the BFGS method is as the follow-
ings

1 const int MAX_ITERATION_NO = 10;


2 const double EPSILON = 1.e-9;
... // incremental loading update
3 double energy, energy_0;
4 do {
5 cout << (i+1) << "-time step, " << (++count) << "-partial quasi-newton loop, " << endl;
... // incremental loading update
6 C0 g, H, B, d_;

Workbook of Applications in VectorSpace C++ Library 589


Chapter 5 Advanced Finite Element Methods
7 for(int k = 0; k < MAX_ITERATION_NO; k++) {
8 if(k == 0) {
9 Matrix_Representation::Assembly_Switch = Matrix_Representation::ALL;
10 mr.assembly();
11 H = ((C0)(mr.lhs())); // Hessian
12 g = -((C0)(mr.rhs())); // gradient
13 Cholesky dH(H, 1.e-12); // modified cholesky method
14 B = dH.inverse(); // inverse of Hessian
15 d_ = -B*g; // search direction
16 (C0)(mr.rhs()) = 0.0;
17 (C0)(mr.lhs()) = 0.0;
18 }
19 if(i == 0 && count == 1 && k == 0) delta_u &= C0(d_.length(), (double*)0);
20 delta_uh = delta_u += du; // update total increment of displacement
21 uh += du; // upeate total displacement
22 double residual_norm = norm(g);
23 energy = fabs( (double)(du *g );
24 if(count == 1 && k == 0) energy_0 = energy;
25 cout << k << " bfgs iteration energy: " << energy << " residual: " << residual_norm << endl;
26 if(k != (MAX_ITERATION_NO-1)) {
27 Matrix_Representation::Assembly_Switch = Matrix_Representation::RHS;
28 mr.assembly(); // form residual vector (rhs)
29 C0 g1 = -(C0)(mr.rhs()); // next gradient
30 (C0)(mr.rhs()) = 0.0;
31 C0 p, q, Bq; p &= du; q = g1 -g; Bq = B*q;
32 B += (1.0+(q*Bq)/(q*p))*((p%p)/(p*q)) - (p%Bq+Bq%p)/(q*p);
33 g = g1; // update gradient
34 d_ = -B*g; // update search direction
35 }
36 if(energy < (energy_0*EPSILON)) break;
37 } // end partial quasi-newton for loop
38 } while(energy > (energy_0*EPSILON) && count < MAX_ITERATION_NO);

With the aids of the inverse of Hessian, B, the convergence rate shows dramatic improvement. When the size of
the matrix is increased, saving time in inverting the global stiffness matrix, a O(n3) process, will become more
and more critical. This implementation can be activated by setting marco definition “__TEST_QUASI_
NEWTON_BFGS_METHOD” in project “elastoplasticity” of workspace file “fe.dsw”.

590 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity
5.4 Finite Deformation Elastoplasticity
Finite deformation can be an obscure subject, because it requires extremely high-flown mathematics to do it
right. We have always conducted in a manner that only minimum necessary mathematics is used. To convince
you that all your investment of time and effort is worthwhile, we provide some motivation to justify a deeper
mathematical engagement. Heuristic approaches can be made to show the contrary: with the finite rotation, we
analyze how much error is happening for the infinitesimal strain formulation. If there are two observers
using different frames of reference, will the stress rate and material moduli be different for the two observers?
Any difference in the stress rates violates the principle of determinism for stress, and any difference in material
moduli contradicts the principle of material frame indifference.1 These two principles are fundamental postulates
of a purely mechanical theory. These two principles are intuitively comprehensible, and the contrary to them is
just absurd! On top of the material frame indifference, we will also introduce the more advanced concept of
covariance in this section.
A heuristic discussion on the material frame indifference in section 5.4.1 is to complement a more formal
introduction followed in section 5.4.2. Some counter examples are introduced first. 2 These examples are helpful
in clearing out many basic issues regrading finite deformation.

5.4.1 Counter Examples of Infinitesimal Assumption

Strain Measures
We first check the effect of finite rotation on the infinitesimal strain. A rigid body rotation can be expressed
as an orthogonal transformation, with θ as the rotation angle,

R = cos θ – sin θ . Eq. 5•251


sin θ cos θ

An initial position X is rotated to the current position x, as x = R X. The displacement u = x - X = (R - I) X. The


infinitesimal strain (ε) is (with the derivatives with respect to X),

1 2
--- θ + O ( θ 4 ) 0
cos θ – 1
ε ≡ 1--2- ( ∇ u + ∇ T u ) = 1--2- ( R + R T – 2 I ) = 0

2
Eq. 5•252
0 cos θ – 1 1 2
0 --- θ + O ( θ 4 )
2

The last approximation step is the Taylor expansion of the cos θ. Therefore, finite rotation, a rigid body motion,
would excite unwanted strain in the order of θ2 (in radian). In many practical engineering applications we are

1. Malvern, 1969, “Introduction to the Mechanics of a Continuous Medium”, Prentice-Hall, Inc.


2. Belytschko, T, 1986, “An Overview of Semi-discretization and Time Integration Procedures”, p.1, in “Com-
putational Methods for Transient Analysis”, edited by T. Belytschko and T.J.R. Hughes, Elsevier Science Pub-
lishers B.V.

Workbook of Applications in VectorSpace C++ Library 591


Chapter 5 Advanced Finite Element Methods
interested in strain in the order of 10-4 to 10-3. We consider that one degree rotation angle is about in the order of
10-2 in radian. The error in strain caused by this one degree of rotation will be in the order of 10-4. One can
expect that the resultant strain value to be seriously contaminated by the unwanted error. Therefore, the infinites-
imal strain assumption can be rather restrictive.

Stress Rates
In small deformation problems, the Cauchy stress is used in the formulation. Unfortunately, the Cauchy
stress rate is not objective. Consider again applying a rigid body rotation to a body, where no stress should be
excited. Since the stress is a second order tensor, it transforms with the rotation tensor according to the transfor-
mation rule

σ’ = R σ RT Eq. 5•253

Denote the spin tensor (W) as

· 1 T
W ≡ R = --- ( ∇ u· – ∇ u· ) Eq. 5•254
2

Note that W is skew symmetric, so WT = -W. For two coordinates with x = X initially, and therefore R = I initially,
taking time derivative with respect to Eq. 5•253, (then evaluated at x = X, and R = I)

σ· ’ = W σ + W σ = Wσ – σW
T
Eq. 5•255

If σ ≠ 0 , σ· will not vanish. However, no stress rate is expected to be excited. Therefore, σ· is the spurious
’ ’
stress rate that needs to be corrected. The Jaumann stress rate takes out of this spurious stress rate and is defined
as

σ̃ = σ· – W σ + σ W Eq. 5•256

σ̃ is also known as the co-rotational stress rate. For a more formal treatment of the objective stress rate see stan-
dard text.1
The Jaumann stress rate is said to be objective only with respect to isomorphism. Isomorphism means that
the transformation is a one-to-one mapping with inverse of the transformation defined. We are also interested in
diffeomorphism. Diffeomorphism is a transformation that further requires the derivatives of the transformation is
also one-to-one mapping with inverse. There are many other objective stress rates historically. They are either
objective with respect to isomorphism or diffeomorphism.2 Rates which are objective with respect to diffeomor-
phism are called covariance. Therefore, the covariance requirement demands that a physical quantity invariant
under arbitrary Cn transformation. The covariance let us leap forward to the essence of modern Einstein’s rela-

1. Malvern, 1969, “Introduction to the Mechanics of a Continuous Medium” Prentice-Hall, Inc.


2. p.99-102 in Marsden, J.E. and T.J.R. Hughes,1983,“Mathematical foundations of elasticity”, Prentice-Hall,
Inc.

592 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity
tivity, and by-pass even the quantum mechanics which is yet to be made covariant. A covariant stress-strain path
integration of elastoplastic constitutive equation is the central theme in “implementation 2” in section 5.4.4.

Material Moduli
We restrict our discussion on the constitutive law to what is relevant to the strain measure and the stress rate
issues developed above. First of all, lets note the material moduli is a fourth-order tensor. A transformation apply
to a fourth-order tensor is similar to that of Eq. 5•253 for the second-order tensor, only the spurious term gener-
ated will be twice as ugly as what is in Eq. 5•255. Disregarding what those ugly spurious terms really are, it is
suffice to say that the material moduli so derived is not invariance with respect to a rigid body motion. This has
certain impact on material moduli of anisotropic materials. For isotropic materials, the constitutive law are
required to be written, by choosing its independent parameters, to be invariant with respect to rotation. It is not so
fortunately for the anisotropic materials. It becomes our responsibility to make it objective by subtracting corre-
sponding spurious terms similar to what was done in Eq. 5•256. The argument above for anisotropic material
moduli is therefore completely parallel that for stress rate. Secondly, we already know the Cauchy stress rate is
not objective (the spurious stress σ · ≠ 0 in Eq. 5•255 occurs). When we construct the constitutive equation in

stress-strain rate form, we could expect that the constitutive equation should be defined using the objective stress
rates only; i.e., not to use the Cauchy stress rate. Then, if it is more convenient for computation, the objective
stress rates can then be expressed in terms of the Cauchy stress rate with its corresponding spurious terms. When
the objective stress rate is used, the problem occurred in the material moduli here resolves automatically.

5.4.2 Basic Theory of Finite Deformation Elastoplasticity


We proceed with the theory on finite deformation. We follow notations and definitions primarily from J.E.
Marsden and T.J.R. Hughes[1983]1. Our intention in this workbook is to implement the endeavor of Simo et
al.[1985]2, and Simo[1988]3. The perspectives covered below is limited to the subject that is most relevant to
computational alogrithms that we are going to impelement in VectorSpace C++ Library. For scientists and engi-
neers, we recommend introduction in finite element context with less mathematical by Crisfield[1997]4 or Bonet
and Wood[1997]5. For a minimum exposure for the mathematical-impatient readers, Crisfield[1997] is recom-
manded. However, if you are not interested in the subject of finite deformation elastoplasticity itself, you just
want to assess how much VectorSpace C++ Library can make programming of a high-flown mathematical sub-
ject easier. This section should be self-contained and be able to assist you dig into all the programming details.
You can learn by comparing the mathematical expression herein and the actual codes of implementation in Vec-
torSpace C++ Library.

1. Marsden, J.E. and T.J.R. Hughes,1983,“Mathematical foundations of elasticity”, Prentice-Hall, Inc.


2. J.C. Simo, R.L. Taylor, and K.S. Pister, 1985, “Variational and projection methods for the volume constraint in finite
deformation elasto-plasticity”, Computer Methods in Applied Mechanics and Engineering, vol. 51, p. 177-208.
3. J.C. Simo, 1988, “A framework for finite strain elastoplasticity based on maximum plastic dissipation and the multiplica-
tive decompositions: part I. continuum formulation.” Computer Methods in Applied Mechanics and Engineering, vol. 66, p.
199-219. “Part II. Computational Aspects.”, vol. 68, p.1-31.
4. Crisfield, M.A., 1997, “Non-linear finite element analysis of solid and structures”, vol. 2, John Wiley & Son Ltd. , UK.
5. Bonet, J. and R.D. Wood, 1997, “Nonlinear continuum mechancis for finite element analysis”, Cambridge University
Press, Cambridge, UK.

Workbook of Applications in VectorSpace C++ Library 593


Chapter 5 Advanced Finite Element Methods

φ
φ(X) = x
WX
X Tφ WX

Figure 5•18 “Push-forward” of a tangent vector WX.

Kinematics
A simple body is an open set in n. Define its containing space as = n. A C1 configuration is repre-
sented by a mapping φ: → , see Figure 5•18. A tangent space to the set at point X is denoted as TX . The
tangent space is the vector space n considered as vectors with base point X. Similarly the tangent space to is
Tx . A tangent vector WX = (X, W) ∈ T X, with X denoting its base point. A tangent map of φ is defined as T
φ: TX → Tx . T φ(X, W) = (φ(X), Dφ(X) • W) where “Dφ(X) • W” denotes the derivative of φ(X) at X in the
direction of W. Then, T φ • WX is called “push-forward” of W by φ. The push-forward is also denoted as φ*.

Deformation Gradient
The tangent of φ is denoted F, the deformation gradient of φ; i.e., F = T φ. Let {XA} and {xa} denote coordi-
nate system on and , respectively. The matrix F with respect to the coordinate bases EA(X) and ea(x) is given
by F = FaA ea ⊗ EA (= GRAD φ(X) ≡ ∇ ⊗ φ(X), where the operator ⊗ denotes the tensor product)

a ∂φ a ∂x a
F A ( X ) = ---------- ( X ) , note that d x a = ---------- dX A Eq. 5•257
∂X A ∂X A

where dxa and dXA are the infinitesimal position vectors, which are the tangent vectors in and . From Eq.
5•257, dxa = F • dXA (= T φ • dXA = φ* dXA) is the “push-forward” of dXA by φ. The Jacobian is, J = det F.
The restriction that J > 0 is the impenetrability of matter, in that the local invertibility of F is required. Therefore,
the relative orientation of the line elements is preserved under deformation.
Metric tensor on is defined as gab(x) = <ea , eb>x , and metric tensor on is defined as GAB(X) = <EA ,
EB>X , where < , > is the inner product in n. The transpose of F is defined as

A
( F T ( x ) )a = g ab ( x ) F Bb ( X ) G AB ( X ) Eq. 5•258

Note that the deformation gradient F is a two-point tensor which can be considered as a tensor with “two legs”;
one on and the other on . Therefore, the question of whether F is symmetric is irrelevant!
Since F with two different “legs” can not be symmetrical, we want to find ways to define “symmetrized”
quantities from it; i.e., somehow, get the off-diagonal components to have same value and “leg”. Therefore, the

594 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity
Green deformation tensor (or the right Cauchy-Green tensor) C is defined as CAB(X) = F(X)TF(X). Note that
the “legs” of C is now all in upper-case indices. If F is one-to-one, C is symmetric and positive definite. Simi-
larly the left Cauchy-Green tensor b is defined as bab(x) = F(X) F(X)T. b is also symmetric and positive definite,
and its inverse c (= b-1) is called the Finger deformation tensor.

Lagrangian Strain Tensor and Eulerian Strain Tensor


The difference of the square of infinitesimal position vectors in and is

d x 2 – d X 2 = d X • ( FT F – G ) • d X = d X • ( C – G ) • d X
Eq. 5•259
2E

The Lagrangian (material, or Green) strain tensor E is defined as 2E = (C - G), (where G = I, in the Cartesian
Coordinates, where I is the identity on TX ). Similarly,

d x 2 – d X 2 = d X • ( g – ( FF T ) –1 ) • d X = d X • ( g – b –1 ) • d X Eq. 5•260
2e

The Eulerian (spatial) strain tensor e is defined as 2e = (g - b-1), (where g = i, where i is the identity on Tx ).

Symmetric Positive Definitive Tensor and Orthogonal Tensor


Before we get to the polar decomposition theorem, let’s first look at the properties of two second-order ten-
sors: (1) a symmetric positive definite tensor, and (2) an orthogonal tensor. A symmetric positive definite tensor
S in 3 admits spectral representation with three positive eigenvalues λ i , and three eigenvectors pi as follows:

3
S = ∑ λi ( pi ⊗ pi )
i=1
Eq. 5•261

An orthogonal tensor Q satisfies a necessary and sufficient condition by definition

QTQ = I, and det Q = 1 Eq. 5•262

This leads to det(Q - I) = 0, indicating there exists a unit vector p such that

Q p = p = QT p Eq. 5•263

The orthogonal tensor Q can be expressed as the following equation (for derivation see P. Chadwick[1976]1)

Qx = pp + (q cosθ-r sinθ)q+(q sinθ+r cosθ)r Eq. 5•264

1. see p. 37 in Chadwick, P., 1976, “Continuum mechanics, concise theory and problems”, John Wiley & Sons,
New York.

Workbook of Applications in VectorSpace C++ Library 595


Chapter 5 Advanced Finite Element Methods
p3

λ1 p x x’
λ3
q
p2
p1 λ2

r
Figure 5•19 Stretches and Rotation for the Polar Decomposition
One recognizes, in Eq. 5•264, (q cosθ-r sinθ)q+(q sinθ+r cosθ)r is a rotation about p-axis on (q, r)-plane. Fig-
ure 5•19 illustrates both the interpretation of S as stretches λ1, λ2, λ3, and Q as rotation around p-axis.
Recall C and b are symmetric and positive definite. Define U = C , and V = b , where U and V are the
right stretch tensor and the left stretch tensor, respectively. U and V have common eigenvalues λ i. The eigen-
vectors pi of U and the eigenvectors qi of V are called the referential stretch axis and the current stretch axis,
respectively.

Polar Decomposition Theorem


Polar Decomposition Theorem: For a regular φ, there exists an orthogonal transformation R(X): TX
→ Tx , such that, F = R U—the right polar decomposition, and F = V R—the left polar decomposition are
both unique.

Step 0 Step 1 Step 2


Decompsition

pi
Right Polar

λi
qi

Stretch by U Rotate by R
Decomposition

pi
Left Polar

qi λi

Rotate by R Stretch by V

Figure 5•20 Two Steps of Right and Left Polar Decomposition.


See P. Chadwick[1976, p. 33] for proof. The geometrical interpretations of the right polar decomposition,
and the left polar decomposition follows. The deformation on a given particle, is proceeded as either first apply-
ing stretches λ i in the directions pi, and then rotating from directions pi to directions qi (the right polar decom-
position), or the operations can be performed in a reverse order (the left polar decomposition). These two
operations in 2-D are shown in Figure 5•20

596 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity
Deviatoric-Spherical Kinematic Split
A kinematic split of infinitesimal strain tensor into its deviatoric and spherical parts is

ε 1
= e + --- Θ g
3
Eq. 5•265

where infinitesimal strain tensor ε ≡ ( ∇ u + ∇T u ) ⁄ 2 , and the mean dilatation Θ ≅ J ≡ ∇•u . This kinematic split
is important for kinematic constraint for the incompressible materials, and is useful in the context of J2 plasticity.
In finite deformation this additive split is substituted by a multiplicative split.

F = J 1 / 3 F̂ Eq. 5•266

where “^”, denotes the volume-preserving part. Therefore, the corresponding right Cauchy-Green tensor is

T
Ĉ = J – 2 / 3 C = F̂ F̂ Eq. 5•267

The infinitesimal version of the additive split (Eq. 5•265) can be understood as linearization of the multiplicative
split (Eq. 5•267) about φ = I as the followings. Let denotes the set of all configuration φ. A tangent vector to
at φ0 ∈ is a vector field u covering φ0. The variation εu, where ε ∈ , is the infinitesimal deformation imposed
on the finite deformation φ0, or writes εu = δ φ0, the variation of the configuration. Considering an incremental
motion φ0ε = φ0 + εu • φ0, the deformation gradient with infinitesimal increment Fε is

Fε = [F + ε ∇uF]. Eq. 5•268

The linearization of the volume-preserving right Cauchy Green tensor Ĉ in Eq. 5•267 gives “2e”, , where e,
unfortunately an overloaded symbol, is the infinitesimal deviatoric strain tensor. The derivation is explained in
the followings. The derivatives of the Jacobian, and the right Cauchy-Green tensor evaluated at ε = 0 are:

d d
det ( F ε ) = det ( F ) tr ( ∇u ) , and Cε = F T ( ∇ u + ∇T u ) F Eq. 5•269
dε ε=0
dε ε=0

εu
φ0

εu

φ0( )
Figure 5•21 Infinitesimal deformation imposed on finite deformation

Workbook of Applications in VectorSpace C++ Library 597


Chapter 5 Advanced Finite Element Methods
On specifying φ0 = I, and substituting two results in Eq. 5•269 into the derivatives of Eq. 5•267 gives 2e = - 2/3
tr( ∇u) g + ( ∇ u + ∇ T u ) . By definition ( ∇ u + ∇ T u ) = 2 ε . Therefore, the linearization of C in Eq. 5•267 gives
ˆ
the infinitesimal deviatoric strain e, as the additive version, e = ε - 1/3 tr( ∇u) g.

Lie Derivatives and Objective Rates


The Lie derivatives of a spatial tensor field t relative to the deformation φ associated with the spatial velocity
field vt is defined as


L v t = φ ∂ t φ∗t Eq. 5•270
*

where φ* is the “pull-back” by φ, defined as action taken by tangent map T φ-1. In words, the procedure of taking
the Lie derivatives is:

(1) “pull-back” the spatial object to the reference configuration with F,

(2) taking the time derivative at the reference configuration, and

(3) “push-forward” the result of the time derivative to the current configuration using F.

This objective spatial rate is sometimes called a convected time derivative. It is the change of a spatial object rel-
ative to the flow of the spatial velocity field. Therefore, it is naturally objective. As we have mention earlier, the
objective flux (rate) defined by the Lie derivatives is objective with respect to diffeomorphisms and is called
covariant. A covariant rate transforms tensorially independent of any preferred coordinate system.
Strain rates are essential in defining constitutive laws in rate-form, for example, viscosity and plasticity. The
covariant nature derived from the Lie derivatives plays an important role. Following results of the Lie deriva-
tives on spatial tensors, g and b, are important.

Lv g = 2 d, and Lv b = 0 Eq. 5•271

We discuss the derivation in words, with two additional pull-back identities φ*(g) = C and φ*(b) = G [see Mars-
den and Hughes]1. In the first part of this equation, the Lie derivative of spatial metric tensor g gives two times
T
the spatial rate of deformation tensor d ( ≡ ( ∇ u· + ∇ u· ) ⁄ 2 ). The result follows immediately from the definition
of the Lie derivative per se. The first step is: “pull-back” of g is C the right Cauchy-Green tensor; i.e., φ*(g) = C.
Then the second step is: taking the time derivative on C yields 2D by definition; i.e., 2D ≡ ∂C ⁄ ∂t , where D is
the material rate of deformation tensor. The third step is: “push-forward” of D is the spatial rate of deformation
tensor d; i.e., φ*(D) = d. The second part of Eq. 5•271 shows the left Cauchy-Green tensor b is “dragged” by the
flow v. Again, following the procedure defined in the Lie derivatives, in the first step, the “pull-back” of b is the
material metric tensor G; i.e., φ*(b) = G. In the second step, the material metric tensor G is constant with respect
to time, and will be zero upon taking time derivative. In the third step, “push-forward” of zero gives zero. Eq.
5•271 is useful for developing the constitutive law in rate form.

1. “Mathematical Foundations of Elasticity”, Prentice-Hall, Inc.

598 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity
Piola Transformation, Stresses, and Stress Rates
The Piola Transformation is of primary importance in relating material and spatial descriptions of a continu-
ous medium. This transformation is similar to the “pull-back”, with additional presence of the Jacobian factor.
The presence of the Jacobian indicates that volume or area are being transformed. Definition of the Piola trans-
formation is as the followings.

Piola Transformation: Let y be a vector field on , and Y a vector field on . The Piola Transform of y to Y is
given by

Y = J φ*y Eq. 5•272

This transformation will be used frequently in the following developments.


n
Ν
y
Y da
dA φ0( )

Figure 5•22 Piola tranformation


The Cauchy stress, σ, is defined on spatial configuration. In infinitesimal theory no distinction of spatial and
material configurations is necessary. Therefore, Cauchy stress definition is sufficient for the analysis under the
infinitesimal assumption. In the context of finite deformation, we have distinct spatial and material configura-
tions. Although one can use the Cauchy stress to formulate the equation of motion in terms of the Eulerian for-
mulation, the Lagrangian formulation is often preferred in elasticity, particularly for the Green elastic material
(see also the hyperelasticity in the next section on Constitutive Equations). For the Green elastic material, it is
assumed that there exists a natural state to which the body would return to the natural state when it is unloaded.
With spatial and material configurations in mind, many stress definitions, based on different combinations of
configurations, are possible. The idea is to choose the one that appears to be natural to the constitutive postulate.
Considering the stress is force per unit area. It will be natural to measure the force in the spatial configuration
where the force is currently balanced, and measure the per unit area in the material configuration to which the
natural unloaded state is defined. Therefore, the first Piola-Kirchhoff stress tensor P is a two-point tensor given
by the Piola transformation on the second index of the Cauchy stress tensor σ. That is,

PaA = J (F-1)Ab σab Eq. 5•273

This stress definition gives simple form of equations of motion. However, the first Piola-Kirchhoff stress tensor
is not symmetrical. Recall mixed upper-lower case indices means that the first Piola-Kirchhoff stress is a two-
point tensor with “two legs”. This disadvantage of first Piola-Kirchhoff stress tensor leads to the definition of
second Piola-Kirchhoff stress tensor. The second Piola-Kirchhoff stress tensor S is defined as pulling the “first
leg” of P back by φ as

SAB = (F-1)Aa PaB = J (F-1)Aa (F-1)Bbσab Eq. 5•274

Workbook of Applications in VectorSpace C++ Library 599


Chapter 5 Advanced Finite Element Methods
Both σ and S are symmetrical tensors. The symmetrical property of S can be directly derived from the moment
of momentum balance principle. It is similar to the standard argument in continuum mechanics that leads to the
proof of the symmetry of σ. Similarly, the Kirchhoff stress tensor τ is the “push-forward” of the second leg of
the first Piola-Kirchhoff stress tensor; i.e.,

τab = PaB FbB = J σab Eq. 5•275

Both τ and σ are defined only in spatial configuration. We observe the difference of them is a scalar factor of J,
the Jacobian. The reason for this proliferation of stresses (τ and σ) in the same configuration is whether one is
performing the Piola transformation with J factor considered, or merely pull-back and push-forward the tensor
objects between spatial and material configurations.
In this section, we mentioned one of the disadvantage of the Cauchy stress in describing the Green elastic
material. The other dismay of the Cauchy stress is the rate of the Cauchy stress is not objective (while the rate of
another symmetric stress tensor, the second Piola-Kirchhoff stress tensor S is objective). Therefore, the Cauchy
stress rate will not be suitable for defining constitutive laws formulated in rate form, e.g., viscosity and plasticity.
The subject of objective rates has been very controversial. All the “objective rates” of second-order tensors, for
examples the Oldroyd rate, Truesdell rate, and Jaumann rate are in fact either (1) the Lie derivatives of the
Cauchy stress tensor σab, or (2) the Lie derivative of the Cauchy stress’s associate tensors (σab, σab, σab), or
(3) certain linear combinations of the Lie derivatives of the associate Cauchy stresses.

Constitutive Equations
In the context of elastoplasticity for this workbook, we restrict ourselves on isotropic material in hyperelas-
ticity and associative flow law in the classical J2 plasticity (see e.g., Fung [1965]1, or Malvern [1969]2 for intro-
duction). We focus on the consequence of finite deformation on such a constitutive law.
For pure elasticity (isothermal), Ψ—the free energy, and E—the internal energy coincide (Ψ = E). We also
postulate that the constitutive equation can be expressed in differential operator which is defined in “local”
points to represent the whole material; i.e., the axiom of locality. Taking covariance of energy balance as an
other axiom, one can deduce

∂E ∂F
ρRef  ------- = P: ------- Eq. 5•276
∂t ∂t

This is, the change in internal energy (the left-hand side) equals the “mechanical power” (the right-hand side).
Therefore, the constitutive equation, in material configuration expressed by the first Piola-Kirchhoff stress ten-
sor, should have the form of

∂Ψ
P = ρRef g# -------- Eq. 5•277
∂F

1. Fung, Y.C., 1965, “Foundations of solid mechanics” Prentice-Hall, Inc., Englewood Cliffs, New Jersey.
2. Malvern, L.E., 1969, “Introduction to the mechanics of a continuous medium” Prentice-Hall, Inc., Englewood
Cliffs, New Jersey.

600 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity
where g# is the Riemannian spatial metric tensor with the sharp sign, “#”, indicating all of its indices raised. This
is to say the free energy Ψ depends on point value of X, and depends on φ only through F. We observe that Ψ
doesn’t require to depend on φ itself, or higher derivatives of F.
From principle of material frame indifference (in 3), as an axiom, Eq. 5•277 has to be objective under
orthogonal transformation (rotation). So, the free energy Ψ depends on F only through C, which makes intuitive
sense since C = FTF = U2, which is free of the orthogonal component R—the rotational part of the polar decom-
position of F. In other words, the free energy function Ψ(X, F) reduces to Ψ(X, C). The “mechanical power” in
material configuration can also be expressed in S and C by writing P : ∂F ⁄ ∂t = S : D = 1/2 S : ∂C ⁄ ∂t . The same
argument to derive Eq. 5•277 leads to

∂Ψ
S = 2 ρRef -------- Eq. 5•278
∂C

Recall the “push-forward” of C is g. Having the Piola transformation in mind, Eq. 5•278 is equivalent to consti-
tutive equations written in the Cauchy stress σ, and the Kirchhoff stress τ defined in spatial configuration as the
follows

Ψ ∂Ψ
σ (= J-1 τ) = 2 ρ ∂-------
- ⇒ τ = 2 ρRef -------- Eq. 5•279
∂g ∂g

This is the Doyle-Erickson formula. For the dependence of free energy Ψ on g, one can feel more comfortable by
directly considering spatial setting as the followings. Changes of spatial metric tensor on from g to say g'
affect the accelerations of particles. Thus, the internal energy E must depend on the metric tensor g; i.e., for the
free energy Ψ to depend on g.
The elasticity tensor or the elasticities A is a fourth-order tensor on , and is defined as

∂S ABCD ∂S AB ∂2 Ψ
A = ------- , i.e., A = -------------- = 2 ρ Ref ----------------------------- Eq. 5•280
∂C ∂C CD ∂C AB ∂C CD

the symmetry of the elasticity tensor AABCD = ACDAB implies the cross partial differentiation of Ψ are equal

∂2 Ψ ∂2Ψ
----------------------------- = ----------------------------- Eq. 5•281
∂C AB ∂C CD ∂CCD ∂C AB

This is the condition for the existence of the free energy Ψ as a potential function from calculus; i.e., also the
condition to define hyperelasticity.
We have concluded that the free energy Ψ depends on φ and F only through C. Recall that C is a symmetric
tensor, which can be brought to diagonal form by orthogonal transformation. For regular φ we have seen, a posi-
tive definite symmetric tensor in this case C, admits spectral representation (Eq. 5•261). The free energy Ψ must
be a function only of the eigenvalues of C; that is, Ψ depends only on the principles stretches of U. However,
instead of using eigenvalues, the three invariants of C, where I(C) = tr (C), II(C) = det C tr C-1, and III(C) = det
C = J2, is also convenient to use. The same is true for the free energy Ψ expressed in spatial configuration with
the left Cauchy-Green tensor b, used in the next section.

Workbook of Applications in VectorSpace C++ Library 601


Chapter 5 Advanced Finite Element Methods
5.4.3 An Isotropic Material with Uncoupled Volumetric and Deviatoric Response
A specific case of material property, isotropic with uncoupled volumetric and deviatoric response, is devel-
oped in Simo[1988]1. This hyperelastic material is used for the numerical computation adopted in this manual.

Elasticity
Recall the kinematic split into deviatoric and spheric parts, the volume-preserving right Cauchy-Green ten-
T
sor is, therefore, defined as Ĉ = J –2 / 3 C = F̂ F̂ . The volume-preserving left Cauchy-Green tensor is defined
similarly as b̂ = J –2 / 3 b = F̂F̂ . The idea is to express the free energy Ψ as function of three invariant of b; i.e.,
T

Ib, IIb, and IIIb. For an isotropic material with uncoupled volumetric and deviatoric responses, the stored energy
ˆ
function has the form of Ψ = Ψ ( b̂ ) + U ( J ) . A special case in invariants of b is

1
Ψ = --- µ ( Îb – 3 ) + U ( J ) Eq. 5•282
2

where Î b = J-2/3 Ib, (where Ib ≡ b : g), and that J = IIIb1/2. Two identities are useful for the following deriva-
tions: Lv(IIIb) = Lv(J2) = 2 IIIb g# : d, and Lv(Ib) = Lv(b# : g) = 2 b# : d. Recall the Doyle-Erickson formula
(Eq. 5•279) as the Kirchhoff stress in relation to the free energy Ψ is

Ψ
τ = 2 ρRef ∂-------
- ⇒ τ = J p g# + µ dev b̂ , Eq. 5•283
∂g
∂τ
where p = dU/dJ. The spatial elasticity tensor a ≡ 2 ------ becomes,
∂g

2  1 
a = J 2 U’’( J )( g ⊗ g ) + Jp ( g ⊗ g – 2I )+ --- µ  Î b I – --- ( g ⊗ g ) – ( devb̂ ⊗ g – g ⊗ devb̂ )  Eq. 5•284
3  3 

Define the deviatoric part of the elasticity tensor a as

2  1 
a dev ≡ --- µ  ˆI b I – --- ( g ⊗ g ) – ( devb̂ ⊗ g – g ⊗ devb̂ )  Eq. 5•285
3  3 

For example, a possible case of U, satisfying polyconvex condition, is U(J) = K/2 (J2-1). U’ = dU/dJ = p = KJ =
K (1+ ∫ dV/V) = K (1+logV), U’’ = d2U/d2J = K = V ( ∂p ⁄ ∂V ) T , with the last identity derived from p =
K(1+logV), where K is then identified as the bulk modulus. Eq. 5•284 is symmetrical as required in hyperelas-
ticity shown in Eq. 5•281.
We observe that in the above extremely simplified case of finite elasticity, the elasticity tensor a is a strong
non-linear function of deformation (expressed in J and b) and metric tensor (g), even though the bulk modulus K
and the shear modulus µ have been naively assumed as constant with respect to deformation and thermal effect.
This non-linearity is required if the constitutive equation is to be covariant, and is to be consistent with the sim-

1. J.C. Simo, 1988, “A Framework for Finite Strain Elastoplasticity Based on Maximum Plastic Dissipation and the Multi-
plicative Decompositions: Part I. Continuum Formulation.” Computer Methods in Applied Mechanics and Engineering, vol.
66, p. 199-219. “Part II. Computational Aspects.”, vol. 68, p.1-31.

602 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity
ple notion of hyperelasticity, where a stored energy function must exist. Covariance is also required in the plas-
ticity discussed below, in which we use the Lie derivative to measure the stress rate in spatial configuration.

We have come to a long way to define the elastoplasticity in the finite deformation range. However, the com-
plicated mathematical expression in Eq. 5•284 is only to implement the simplest ideas of “objectivity” and
“restorable elastic energy”. The algebraic structures in the infinitesimal deformation simply does not carry over
to the finite deformation. The idiosyncrasy in finite deformation range, if not respected, will lead only to absur-
dity!

Multiplicative Decomposition of Elastic and Plastic Deformations


The formal additive decomposition (the Green-Naghdi decomposition) is

E = Ee + Ep => D = De + Dp Eq. 5•286

where E is the finite Lagrangian strain tensor, and D is the strain rate tensor. The constitutive equation for such
additive decomposition is usually cast in a rate-form. However, the notion of hyperelasticity is not consistent
with a rate expression. The incremental objective algorithm, historically, is developed to ensure the frame-indif-
ference nature of the path-dependent stress-strain integration scheme, and hyperelasticity is achieved through
algorithmic approximation. The semantics of the Eq. 5•286 implies that the elastic and plastic deformations
occurred simultaneously. De and Dp are unknown to be solved simultaneously. An additional (stronger) assump-
tion, on top of the formal additive decomposition, will help us resolve the elastic and plastic parts of deformation
separately. This additional assumption is that the elastic and plastic deformation occur in succession. The defor-
mation gradient, occurring in two consecutive steps, is naturally expressed as a multiplicative decomposition
(Lee decomposition).

F = F e Fp Eq. 5•287

The superscript “e” indicates elastic part, and “p” indicates the plastic part. The tensor (Fe)-1, therefore, is the
deformation gradient which responses to the elastically released stress. The released intermediate configuration
is thus introduced. In multiplicative decomposition, one is able to compute elastic response exactly (by mere
function evaluation as opposed to algorithmic approximation in the incremental objective algorithm). This addi-
tional assumption of multiplicative decomposition is also justifiable from scientific point of view. It has been
argued from two perspectives. Firstly, from theoretical development of how elastic deformation of a crystalline
material is lead to dislocation (i.e., plasticity as the macroscopic manifestation of crystal structural dislocation).
Secondly, electron-microscope study that demonstrates how this micro-mechanism can occur.

Plastic Metric Tensor and the Form of Elastoplastic Free Energy


From the definition of the Eulerian strain tensor e, we see that e = 1/2 (g - b-1), and similarly one can defined
e = 1/2 (g - (be)-1
e ). Since the total strain is the summation of the elastic and plastic strain e ≡ ee + ep, we have ep
= e - e = 1/2 ((b ) - b-1 ). Comparing this result to the definitions of the Eulerian strain (e) and the plastic Eule-
e e -1

rian strain (ep), we observe that (be)-1 plays the role of the plastic metric tensor gp. That is, for the elastic Eule-
rian strain ee, the elastic Finger deformation tensor (be)-1 is the deformation from the “identity” g, while for the

Workbook of Applications in VectorSpace C++ Library 603


Chapter 5 Advanced Finite Element Methods
plastic Eulerian strain ep, the Finger deformation tensor b-1 is the deformation from the “identity” gp =(be)-1. We
have the transition from the initial to the intermediate and then to the final configurations by: g → (be)-1 → b-1.

Principle of Maximum Plastic Dissipation and Plastic Flow Law


Keeping the plastic metric tensor gp = (be)-1 in mind, we first discuss the form of the elastoplastic free
energy, and then we derive the constitutive equation in rate form as follows. The most general form of a pure
mechanical elastoplastic free energy is Ψ = Ψ(g, Fe, F). That is to be dependent on the spatial metric tensor g
and deformations. Since there are two parts—elastic and plastic, the free energy also depend on the intermediate
deformation gradient Fe and the final deformation gradient F (= FeFp). (taking {Fe, F} is equivalent to taking
{Fe, Fp}). The first restriction is the intermediate configuration represented by Fe should be invariant under
proper orthogonal transformation. This reduce Fe to (be)-1, recall this is the elastic Finger deformation tensor.
That is Ψ(g, Fe, F) → Ψ(g, (be)-1, F). An alternative view of this is Ψ(g, gp, F), if we consider gp = (be)-1. The
second restriction is covariance under arbitrary diffeomorphisms. If we allow the transformation ξ* = φ*, that is
pull-back, we obtain, in material configuration, Ψ(C, Cp) (Note that φ*g = C, φ*gp = Cp, and φ*F = I).
Recall we have pointed out that the Cauchy stress rate is not objective (Note that the spatial tensor quantities
are, in general, not covariant with respect to diffeomorphism). The covariant tensor quantities are those spatial
quantities taken as the Lie derivative. Recall the Kirchhoff stress τ = J σ = 2 ρRef ∂Ψ ⁄ ∂g , and the Lie deriva-
tive of g is Lv g = 2 d. We take the Lie derivative of τp, the plastic relaxation stress, and the Lie derivative of
∂Ψ ⁄ ∂g with respect to the last two variables, the metric gp (=(be)-1) and F to constitute the plastic law. That is

e –1
∂ Ψ ( g, ( b ) , F ) ∂2Ψ ∂2Ψ
τp = 2 ρRef Lv( ------------------------------------------ ) = - 2 ρ Ref  ------------------------- 
e –1
Lv e – 1- L v ( ( b ) ) + -------------- L v ( F ) Eq. 5•288
∂g ∂g∂(b ) ∂ g ∂F

such that
e –1
∂ 2 Ψ ( g, ( b ) , F )
Lv τ p
= - 4 ρ Ref --------------------------------------------
e –1 - : dp Eq. 5•289
∂g∂(b )
e –1
Recall Lv(g) = 2d, and Lv(F) is zero, since φ*F = I. Note that L v ( ( b ) ) = L v ( g p ) = 2dp, the spatial rate of
plastic deformation tensor, where d is the symmetrical part of the velocity gradient.

Volume-Preserving Plastic Flow Rule


For an uncoupled hyperelastic case, with elastic volumetric response, the plastic relaxation stress is

e –1
∂ 2 Ψ ( g, ( b ) , F )
Lv τ = - J
p -2 / 3 e –1
e –1 - : L v ( ( b ) ) ],
dev [ 2ρRef-------------------------------------------- Eq. 5•290
∂g∂(b )

where g = J –2 / 3 g . The yield function for the associative flow rule is

e –1
φ ( g, ( b ) , q, F ) ≤ 0 Eq. 5•291

604 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity
where q is the plastic internal variable. The Lagrangian functional with the inequality constraint φ ≤ 0 is

e –1
∂ Ψ ( g, ( b ) , F )
γ φ ( g, ( b ) , q, F )
p e –1 · e –1
≡ ------------------------------------------
e –1 : Lv ( ( b ) ) + Eq. 5•292
∂( b )

· ·
where γ is the plastic consistency parameter. γ is also the Lagrangian multiplier in constrained optimization.
The minimization condition of the Lagrangian functional is the Euler-Lagrange equations (with virtual variation
of g )


------ p = 0, Eq. 5•293
∂g

and the Kuhn-Tucker condition, the celebrated trio in inequality constrained optimization is

· e –1 · e –1
γ ≥ 0 , φ ( g, ( b ) , q, F ) ≤ 0 , and γ φ ( g, ( b ) , q, F ) = 0 Eq. 5•294

the Euler-Lagrange equation Eq. 5•292 gives the definition of the plastic relaxation stress as

∂φ
Lv τp = γ· 2 J dev ------
–2 / 3
Eq. 5•295
∂g

This is the plastic flow law basing on Hill’s principle of maximum plastic dissipation as derived in Simo[1988]1.
For a specific case of isotropic-kinematic hardening J2 plasticity with uncoupled hyperelasticity, the problem
is defined as

τ≡K log J g + J –2 / 3 µdev [ b e ] Eq. 5•296

ξ ≡ dev [ τ ] – J –2 / 3 dev [ α ] Eq. 5•297

φ ≡ ξ – 2--- κ ( e p ) Eq. 5•298


3

The corresponding flow rule from maximum plastic dissipation principle is

· ξ 1
µ J –2 / 3 dev [ L v b e ] = – 2µγ n̂ , where n̂ ≡ -------- , µ ≡ --- µ J –2 / 3 tr [ b e ] , and tr [ L v b e ] = 0 Eq. 5•299
ξ 3

The hardening law, with isotropic-to-kinematic proportion ratio β ∈ [ 0, 1 ] is

1. J.C. Simo, 1988, “A Framework for Finite Strain Elastoplasticity Based on Maximum Plastic Dissipation and the Multipli-
cative Decompositions: Part I. Continuum Formulation.” Computer Methods in Applied Mechanics and Engineering, vol. 66,
p. 199-219. “Part II. Computational Aspects.”, vol. 68, p.1-31.

Workbook of Applications in VectorSpace C++ Library 605


Chapter 5 Advanced Finite Element Methods

· 2·
κ ( e p ) = κ0 + β h’e p , where e p = --- γ Eq. 5•300
3

· h'
J – 2 / 3 dev [ L v α ] ≡ 2 µγ ------- ( 1 – β ) n̂ , where µ ≡ µ – J –2 / 3 tr [ α ] , and tr [ L v α ] = 0 Eq. 5•301

Consistent Tangent Moduli


The finite deformation counterpart of the consistent tangent moduli is derived in Simo[1988]1.

µ ≡ µ – --- tr [ F u α ( F u ) ]
1 1 ˆ ˆ T
µ ≡ --- µ tr [ b̂ ne +TR
1 ], Eq. 5•302
3 3

1 2
TR
a dev n+1 ≡ 2 µ I – --- ( g ⊗ g ) – --- ( s TR ⊗ g + g ⊗ s TR ) Eq. 5•303
3 3

≡ 2 µ I – --- ( g ⊗ g ) – --- ( ξ TR ⊗ g + g ⊗ ξ TR )
TR 1 2
h dev n+1 Eq. 5•304
3 3

with scaling factors as

2 µγ n + 1 1 h’ κ’ , h’
- + ------- δ ≡ f 2µ – -----  1 + ------ – 1 --- γ n + 1 , δ ≡ 2
f0 ≡ ------------------- 1 4
ξn + 1
TR
-, f 1 ≡ ----- – f 0
δ0
, δ ≡ 1 + ------
0 3µ 3µ 1 1 δ0  3µ 3 2 ξnTR+ 1 f 1 Eq. 5•305

The deviatoric part of the consistent tangent moduli Eq. 5•285 is modified accordingly with superscript “s”
denote symmetrized

ep
a dev n+1 ≡ a dev
TR
n+1
TR
– f 0 h dev n+1 – δ1 ( n̂ ⊗ n̂ ) – δ 2 ( n̂ ⊗ dev [ n̂ 2 ] ) s Eq. 5•306

Three-Field Hu-Washizu Variational Principle for Finite Deformation Elastoplasticity


The three-field Hu-Washizu variational principle leads to the B-formulation in infinitesimal deformation
case. However, it has been found by Simo et. al.[1985]2, that B-formulation does not carry over to the finite
deformation case. Many “garbage” terms generated because of the non-linearity, if one insists to cast the formu-
lation in B-matrix. Retaining standard formulation Simo[1988]3 advocates the standard strain-displacement
matrix (B-matrix) formulation. The Lagrangian functional for the three-field Hu-Washizu variational principle is

1. J.C. Simo, 1988, “A Framework for Finite Strain Elastoplasticity Based on Maximum Plastic Dissipation and the Multi-
plicative Decompositions: Part I. Continuum Formulation.” Computer Methods in Applied Mechanics and Engineering, vol.
66, p. 199-219. “Part II. Computational Aspects.”, vol. 68, p.1-31.
2. J.C. Simo, R.L. Taylor, and K.S. Pister, 1985, “Variational and Projection Methods for the Volume Constraint in Finite
Deformation Elasto-Plasticity”, Computer Methods in Applied Mechanics and Engineering, vol. 51, p. 177-208.

606 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity

Π ( φ, Θ, p ) ≡ ∫ [ U ( Θ ) + Ψ ( E, E p ) + p ( J – Θ ) ] dΩ +Πext Eq. 5•307


Proceed as in the linear case for the Hu-Washizu variational principle, the Euler-Lagrange equations are a set of
simultaneous equations and is reduced to displacement field only formulation. Linearization with respect to vari-
ations at tn+1, we obtain the tangent stiffness as (η is the variation about φn+1)

1 T
--- ∇ η :a: ∇ u = ∇ T η : [ σ ∇ u ] + ∇ T η : [ p ( 1 ⊗ 1 – 2I ) + a dev
ep ] :∇ u Eq. 5•308
J

The first term in the right-hand-side is the geometrical stiffness. The second term, which depends on the material
moduli, can be cast in the B-matrix formulation with an averaged volumetric term as

∫ B T [ adev
ep
n+1 ( Θ ) N’ N’]vol ( φ n + 1 )
– 2pI ] B dV + [ U’’
T
Eq. 5•309
Ωe

U(Θ) is the volumetric part of the free energy function, where Θ is the element mean dilatation, and N’ is the
averaged derivatives of shape function over the element.

5.4.4 Implementations of Stress-Strain Path Integration for Finite Deformation


In the stress-strain path integration scheme for the small deformation case, we introduce the closest-point
projection method to implement the radial return mapping algorithm. We mentioned that the cutting-plane
method will be used in this section. The point of departure of these two methods is the cutting-plane method bas-
ing on its integration at φn+1, while the closest-point projection method basing on its integration at φn.

sTRn+1
elastic predictor
plastic corrector
sn+1 elastic predictor sTRn+1
plastic corrector

sn sn sn+1
Yield Surface Yield Surface

cutting-plane closest-point projection

Figure 5•23 Cutting-plane method and closest-point projection method.

3. J.C. Simo, 1988, “A Framework for Finite Strain Elastoplasticity Based on Maximum Plastic Dissipation and the Multipli-
cative Decompositions: Part I. Continuum Formulation.” Computer Methods in Applied Mechanics and Engineering, vol. 66,
p. 199-219. “Part II. Computational Aspects.”, vol. 68, p.1-31.

Workbook of Applications in VectorSpace C++ Library 607


Chapter 5 Advanced Finite Element Methods
In the cutting-plane method, the spatial metric g is taken to coincide with identity of a Cartesian coordinate
system. The intermediate configuration needed to be inverted from stress through the constitutive law at the end
of each time step. With this stress inversion, the covariance of the cutting-plane method follows directly from the
covariance of the constitutive law. The secret of the trade is, at the trial elastic step, by definition, no plastic flow
is assumed. Plastic internal variables remain unchanged. So, push-forward of nothing is nothing, therefore,
push-forward operation can be skipped. At the plastic step, the plastic flow occurs at fixed current configuration
φn+1. Again, adroitly avoid the need for push-forward. However, the stress inversion step for the intermediate
configuration turns out to be very complicated.

In the closest-point projection method, the integration is done at φn then push-forward by Fu (the incremental
deformation gradient) to the current configuration φn+1. The push-forward of the intermediate configuration and
its internal plastic variables at the finite deformation range makes the algorithm not only incremental objective,
but covariant.

Computationally, the stress inversion step of cutting-plane method is the trade-off for the push-forward step
in the closest-point projection method. The algorithm for the closest-point-projection is aesthetically more satis-
fying but requires the introduction of the advanced covariant concept.

Implementation 1: Cutting-Plane Method


The project file for the implementation 1 is “test_finite_deformation.exe” in project workspace file “fe.dsw”.

Deformation Gradient from Shape Derivatives at φn+1 and the Mean Dilatational Approximation : The shape
derivative code (see Program Listing 5•22) is different from the infinitesimal case that it is evaluated at x = φn+1,
in place of X = φ0. The spatial metric tensor g = i (spatial identity), instantaneously coincide with a Cartesian
coordinates system. Recall x = X + u,

F = dx / dX = d(X+u) / dX = I + (du / dx) (dx / dX) = I + (grad u) F Eq. 5•310

The lower case “grad”, in convention, denotes derivatives with respect to spatial coordinates x. Post-multiply by
F-1, we have

F -1 = I - grad u Eq. 5•311

We first compute F-1 from grad u. The inverse of F-1 gives the deformation gradient, F. And evaluate the Jaco-
bian from the determinant of the deformation gradient as J = det(F). Remember for the following computation
all the available quantities are center around x = φn+1.
The mean dilatation Θ is

Θ=v/V Eq. 5•312

where the current volume is v = ∫ d v , and the initial volume is V = ∫ ------ , both integration are done at the cur-
dv
φ ( Ωe ) φ ( Ωe ) J
rent configuration φn+1.

The volumetric store energy function U(Θ)

608 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity

C0 u(nen, ndf, (double*)0);


for(int i = 0; i < nen; i ++)
for(int j = 0; j < ndf; j++) u[i][j] = ul[i*ndf+j];
H1 x = N*(xl+u); xn+1 = X + u
H0 Nx = d(N) * d(x).inverse();
J dv(d(x).det());
. . .
. . .
{ F -1 = I - grad u
H0 F_inv = I_22 - (~u)*Nx;
F = F_inv.inverse();
F = (F -1)-1
j = F.det(); J = det(F)
}
H0 unit(qp); unit = 1.0; V = ∫ dv
------ , and v = ∫φ( Ω )dv
C0 VOL = (1.0/j) | dv, φ( Ω e ) J e
vol = unit | dv,
theta = vol / VOL; Θ=v/V
C2 THETA(theta), K
U = K_*((log(THETA)).pow(2))/2.0; U ( Θ ) = ---- ( log Θ ) 2
p = d(U);
2
H0 J_two_third = exp(log(j)*2.0/3.0); p = U' = dU / dΘ, and J 2 / 3

Listing 5•22 Deformation gradient and mean dilatation approximation evaluated from configuration at x
= φn+1.

K
U ( Θ ) = ---- ( log Θ ) 2 Eq. 5•313
2

where K is the bulk modulus. p = U' = dU / dΘ, and U'' will be used in the calculation of tangent stiffness.
Again with VectorSpace C++ Library the evaluation of these derivatives is quite simple. We just need to declare
U and Θ to be a function and an independent variable, respectively, of C2 class. Alternatively, with such simple
equation, you might want to do the differentiation by hand, and code the result directly. In addition, because
pow(int) function only takes integer exponent, J2/3 is expressible as functions of exp() and log() by the equation
J2/3 = elog(J)2/3.
The update procedure for implementation 1 is to map the history data {Θn, pn, Jn, Fn, σn, (Fp)-1n, epn, αn} at
tn to {Θn+1, pn+1, Jn+1, Fn+1, σn+1, (Fp)-1n+1, epn+1, αn+1} at tn+1. We omit the programming details, since the
implementation to construct object-oriented private data members is completely parallel to that of the infinitesi-
mal case.

Elastic-Predictor and Plastic-Corrector: The elastic predictor (see Program Listing 5•23) assumes that the trial
plastic deformation gradient at tn+1 is Fnp +TR 1 ≡ F n ; i.e., no new plastic flow occurs. Therefore, the internal plas-
p

tic variables remain unchanged. Although the configuration has changed from φn to φn+1, push forward of zero
(no change of internal plastic variables) is zero. Then, the trial elastic deformation gradient at the current config-
p –1
1 ≡ F n + 1 ( F n ) . The deviatoric part of the Kirchhoff stress is calculated from the constitutive
uration is F ne +TR
equation, which has already been chosen to be covariant, as

s TR ≡ dev ( τ nTR+ 1 ) = µ dev ( b̂ ne +TR


1 ) Eq. 5•314

Workbook of Applications in VectorSpace C++ Library 609


Chapter 5 Advanced Finite Element Methods
where b̂ ne +TR –2 / 3 e
1 = Jn + 1 bn + 1
is the trial elastic volume-preserving left-Cauchy-Green tensor and s = {sxx, syy, szz,
sxy} is expressed in engineering convention as a vector. The covariance of the constitutive equation leads
directly to the covariant nature of the stress-strain path integration algorithm.
The formal additive decomposition de + dp = d must always hold no matter plastic flow occurs or not. The
additive decomposition is not contradictory to the multiplicative decomposition when plastic flow occurs. The
multiplicative decomposition is viewed as an additional (stronger) assumption, or as an additional equation, on
top of the additive decomposition. If the yield condition is violated, the plastic flow occurs at the fixed current
configuration φn+1 (with d remains unchanged). dp gains from de as a retribution for violating the consistency
condition, and the very value of dp is also determined from the consistency condition with the plastic-corrector
per se. With this interpretation, since φn+1 is fixed during the plastic deformation, no push-forward is necessary.
The stress-strain path integration algorithm is naturally covariant as long as the elastic-predictor step is covari-
ant.
The plastic-corrector phase therefore reduces to an infinitesimal case around φn+1. It’s algorithm is exactly
the same as in the infinitesimal case. Therefore the implementation aspect is skipped here. The beauty of the
Implementation 1 is that in both the elastic-predictor and the plastic-corrector the push-forward is tacitly
avoided.

H0 s(4, (double*)0, qp); s = {sxx, syy, szz, sxy} is dev(τTRn+1)


H0 mu_bar;
{
1 ≡ F n , and F n + 1 ≡ F n + 1 ( F n )– 1
Fnp +TR p e TR p
F_p_inv = F_p_inv_n;
H0 TR_F_e = F * F_p_inv,
TR_b_e = INTEGRABLE_MATRIX("int, int, Quadrature", 3, 3, qp),
ref_TR_b_e =INTEGRABLE_MATRIX("int, int, H0&, int, int, Quadrature",
be = Fe (Fe)T
2, 2, TR_b_e, 0, 0, qp);
ref_TR_b_e = TR_F_e *(~TR_F_e); b̂ ne +TR –2 / 3 e
1 = Jn + 1 bn + 1
TR_b_e[2][2] = 1.0;

dev ( τ TR n + 1 ) = µ dev ( b̂ ne +TR


H0 TR_b_hat_e = TR_b_e / J_two_third,
dev_TR_b_hat_e = TR_b_hat_e - (1.0/3.0) * tr(TR_b_hat_e) * I_33, 1 )
dev_TR_tau = mu_ * dev_TR_b_hat_e;
mu_bar &= (mu_/3.0) * tr(TR_b_hat_e);
s[0] = dev_TR_tau[0][0]; s[1] = dev_TR_tau[1][1]; 1
s[2] = dev_TR_tau[2][2]; s[3] = dev_TR_tau[0][1]; µ ≡ --- µ tr [ b̂ ne +TR
1 ]
3

Listing 5•23 Elsatic predictor assume no plastic flow occurs. The deviatoric Kirchhoff stress s, in engi-
neering convention, is evaluated from the consititutive law

Intermediate Configuration: We mentioned that the covariance of this stress-strain path integration algorithm
follows directly from the covariance of the constitutive law. We did not mention how do we get the (Fpn)-1 in the
above algorithm to furnish the covariance requirement.

We first describe a tactic change on stress inversion algorithm, from Simo et al[1985]1, adopted in the imple-
mentation of this workbook. At the initial step of the incremental loading step (Fp0)-1 can be assumed to be an
identity matrix, for no deformation has occurred. The tensor (Fp0)-1 remains unchanged during the same time

1. J.C. Simo, R.L. Taylor, and K.S. Pister, 1985, “Variational and Projection Methods for the Volume Constraint in Finite
Deformation Elasto-Plasticity”, Computer Methods in Applied Mechanics and Engineering, vol. 51, p. 177-208.

610 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity
p
step. When we get a converged result, we will like to end the first time step, (F 1)-1 will be computed from the
deviatoric part of the Kirchhoff stress tensor through constitutive equation (where the covariance nature of the
stress-strain path integration algorithm is relied on). We defer the description of how to compute this step later.
However, the value of (Fpn+1)-1 is most likely to be computed at the element level. In the element level, we do not
p
know beforehand that if the result is going to converge. One way to do it is to compute (F n+1)-1 at every itera-
p
tion, so it will always be ready. This is certainly very costly, especially when the computation of (F n+1)-1 is very
complicated. The intermediate configuration computation step is labelled “Step 3” follows the “Step 1 and 2” of
the elastic-predictor / plastic-corrector steps in Simo et al.[1985, p. 199]. The other way around is to compute
(Fpn)-1 at the beginning of every time step tn+1, except at the first time step (when it is assumed to be identity).
This also has the advantage that at the last time step we do not need to compute (Fpn)-1, since there is no next time
step which needs to use this value. Therefore, we code the stress inversion of the intermediate configuration step
before the elastic-predictor / plastic-corrector steps. It is then more appropriate to think of this step as “Step 0” to
get (Fpn)-1.
The step to compute (Fpn)-1 is very complicated (see Program Listing 5•24). This is the price to pay for avoid-
ing the push-forward operations (see Implementation 2) demanded by covariance requirement. “Step 0” is subdi-
vided into following sub-steps:

(a) get the Kirchhoff stress from the Jacobian and the Cauchy stress— τ n = J σ n from Eq. 5•275.

(b) compute dev ( b̂ ne ) from the deviatoric part of the constitutive law— dev ( b̂ ne ) = --- dev ( τn ) from Eq. 5•296.
1
µ

(c) compute b̂ ne from its deviatoric part dev ( b̂ne ) and requiring det ( b̂ne ) = 1.

(d) compute the elastic left-Cauchy-Green tensor from the elastic volume-preserving left-Cauchy-Green tensor—
b ne = J – 2 / 3 b̂ ne by definition.

(e) compute the left-stretch tensor ( Vne ) from the left-Cauchy-Green tensor ( bne )— Vne = b ne .

(f) set Fne = V ne , this is valid for isotropic case, and compute F pn = F n (F en)-1 from the multiplicative decompo-
sition (Eq. 5•287). Then, (F pn)-1 can be obtained.
Except for sub-steps (c) and (e), the other sub-steps are straight forward without explanation. The essence of
sub-step (c), for those who familiar with solid mechanics, is similar to the calculation of principle deviatoric
stress from a deviatoric stress. See C.Y. Fung[1965, p. 80]1, or L.E. Malvern[1969, p. 91]2.
Kinematic split of the elastic volume-preserving left-Cauchy-Green tensor b̂ ne is

1
b̂ ne = dev ( b̂ ne ) + Ξ , where Ξ = --- tr ( b̂ ne ) . Eq. 5•315
3

The objective of the problem at hand is to compute the spherical part Ξ , then adds to the given deviatoric part,
requiring that det ( b̂ ne ) = 1 —add trace to a unimodular deviatoric tensor. Assuming plain strain, we define

1. “Foundations of Solid Mechanics”, Prentice-Hall, Inc.


2. “Introduction to the Mechanics of a Continuous Medium” Prentice-Hall, Inc.

Workbook of Applications in VectorSpace C++ Library 611


Chapter 5 Advanced Finite Element Methods

if(new_time_flag) { τn = Jσn
H0 dev_b_hat_n_e = INTEGRABLE_MATRIX("int, int, Quadrature", 3, 3, qp),

dev ( τ n ) = τ n – 1--3- tr ( τn )
dev_tau_n = INTEGRABLE_MATRIX("int, int, Quadrature", 3, 3, qp),
tau_n = J_n*sigma_n, tr_tau_n = tau_n[0]+tau_n[1]+tau_n[2];
dev_tau_n[0][0] = tau_n[0] - (1.0/3.0)*tr_tau_n; dev_tau_n[1][1] = tau_n[1]-(1.0/3.0)*tr_tau_n;
dev ( b̂ ne ) = --- dev ( τ n )
dev_tau_n[2][2] = tau_n[2] - (1.0/3.0)*tr_tau_n; dev_tau_n[0][1] = dev_tau_n[1][0] = tau_n[3]; 1
dev_b_hat_n_e = dev_tau_n / mu_; µ
H0 bp = dev_b_hat_n_e, theta_0(qp),
J2 = (bp[0][0].pow(2)+bp[1][1].pow(2)+bp[2][2].pow(2))/2.0 + bp[0][1].pow(2);
#if defined(__NUMERICAL_ROOT_FINDING)
for(int i = 0; i < qp.no_of_quadrature_point(); i++) { 1
C0 J2_q = J2.quadrature_point_value(i), theta_0_q = theta_0.quadrature_point_value(i), J 2 ≡ --- [ ( b’11 ) 2 + ( b’22 ) 2 + ( b’33 ) 2 ]
2
bp_q=bp.quadrature_point_value(i), J3_prime = (J2_q-bp_q[2][2].pow(2))*bp_q[2][2] + 1.0;
int MAX_ITERATION_NO = 50, count = 0; double EPSILON = 1.e-6;
+ ( b’12 ) 2 + ( b’13 ) 2 + ( b’23 ) 2
C1 X(1.0), f; C0 d_X(0.0);
do { J’3 = J 3 – I 3
f &= X.pow(3) - J2_q*X - J3_prime; d_X = -((C0)f)/d(f); ((C0)X) += d_X;
} while((double)norm(d_X) > EPSILON && ++count < MAX_ITERATION_NO);
if(count == MAX_ITERATION_NO) ofs << f ( Ξ ) = Ξ 3 – J 2 Ξ – J’3
"Warning: No convergence achieved for solution of a cubic algebraic equation!" << endl;
f
theta_0_q = ((C0)X); d Ξ = – ----- , and updating by Ξ += d Ξ
} df
#else
for(int i = 0; i < qp.no_of_quadrature_point(); i++) { Closed-form solution
C0 J2_q = J2.quadrature_point_value(i), theta_0_q = theta_0.quadrature_point_value(i);
if((double)J2_q < 1.e-6) theta_0_q = 1.0;
else {
C0 bp_q=bp.quadrature_point_value(i), Octahedral deviatoric tensor
J3_prime=(J2_q-bp_q[2][2].pow(2))*bp_q[2][2]+1.0,
2J
bp_oct = sqrt((2.0/3.0)*J2_q), temp = (-J2_q/3.0).pow(3)+(J3_prime/2.0).pow(2);
b oct = -------2-
if((double)temp > 0.0) { 3
double a1, a2,
arg1=(double)(J3_prime/2.0+sqrt(temp)),arg2=(double)(J3_prime/2.0-sqrt(temp)); marginal conditions
if(fabs(arg1) < 1.e-6)a1=0.0; else a1=((arg1>=0.0) ? 1.0 : -1.0) * exp(logl(fabs(arg1))/3.0);
if(fabs(arg2) < 1.e-6)a2=0.0; else a2=((arg2>=0.0) ? 1.0 : -1.0) * exp(logl(fabs(arg2))/3.0);
theta_0_q = a1 + a2; J’3 3 3 / 2 2 J’3
} else { cos 3α = ------  ---- = ----------------
-
C0 cos_3alpha=J3_prime * sqrt(2.0) / bp_oct.pow(3), alpha = acos(cos_3alpha)/3.0; 2  J 2 3
b oct
theta_0_q = sqrt(2.0) * bp_oct * cos(alpha);
}
} J
} Ξ = 2 ----2- cos α
#endif
3
{
H0 b_hat_n_e = dev_b_hat_n_e + theta_0 * I_33; b̂ ne = dev ( b̂ ne ) + Ξ
H0 b_n_e = J_two_third * b_hat_n_e;
H0 V_n_e = INTEGRABLE_MATRIX("int, int, Quadrature", 2, 2, qp);
H0 I_b = b_n_e[0][0]+b_n_e[1][1],
1
I_b =b_n_e[0][0]*b_n_e[1][1]-b_n_e[0][1]*b_n_e[1][0]; V = ----------------------------------- ( b + IIb I )
V_n_e[0][0] = (b_n_e[0][0]+sqrt(II_b)) / sqrt(I_b+2.0*sqrt(II_b)); ( I b + 2 II b )
V_n_e[1][1] = (b_n_e[1][1]+sqrt(II_b)) / sqrt(I_b+2.0*sqrt(II_b));
V_n_e[0][1] = b_n_e[0][1] / sqrt(I_b+2.0*sqrt(II_b));
V_n_e[1][0] = b_n_e[1][0] / sqrt(I_b+2.0*sqrt(II_b));
H0 F_n_e = V_n_e;
F_p_inv_n = F_n_e * F_n.inverse();
F ne = V ne , F pn = F n (F en)-1, (F pn)-1
}

Listing 5•24 Computation of the intermediate configuration (F pn)-1.

612 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity
b’11 b’12 0
dev ( b̂ ne ) ≡ b’21 b’22 0 , where b’33 = 1 Eq. 5•316
0 0 b’33

Following standard procedure for finding principle values, it can be shown that the principle deviations b’ of Eq.
5•316 satisfy

b’3 – J 2 b’ – J 3 = 0 , and also Ξ 3 – J 2 Ξ – ( J 3 – I 3 ) = 0 Eq. 5•317

Numerical root-finding procedure can be applied to second part of Eq. 5•317. This is turned on by a macro defi-
nition “__NUMERICAL_ROOT_FINDING”. With the aid of the VectorSpace C++ Library this numerical pro-
cedure is straight forward without any explanation (see Chapter 2 for introduction). However, a closed-form
expression for this cubic algebraic equation is even more desirable. Considering that the last two equations are
almost identical. The solution for b’ can be use for solution of Ξ if one defines J' 3 = J 3 – I3 ; i.e., J'3 is used in
place of J3 in the formula for solving b’ . According to Malvern p. 922., the solution of the cubic algebraic equa-
tion is obtained by substituting

J Eq. 5•318
Ξ = 2 ----2- cos α
3

into Eq. 5•317. We get,

J 3
2  ----- [ 4 cos3 α – 3 cos α ] = J’3 ,
2
3 Eq. 5•319

With the trigonometrical identity

cos 3α = 4 cos3 α – 3 cos α Eq. 5•320

the value of cos 3α from Eq. 5•321 is

J' 3 3 3/2 2 J' 3 2J


cos 3α = ------  ----- = ----------------
- , where b oct = -------2- Eq. 5•321
2  J 2 3
b oct 3

The octahedral b (boct, the counterpart of the octahedral shear stress τo in C.Y. Fung1) does not really need to be
introduced, if one use the first identity only (as in L.E. Malvern, p.922). We can first invert for α from Eq. 5•321
e
then get to Ξ by Eq. 5•318. After solving for the value of Ξ , b̂ n is obtained from Eq. 5•315.
The sub-step (e) is the steps that one usually uses to compute numerical values of polar decomposition from a
deformation gradient tensor. We only need the middle step, for b = FFT is already available, and after we get V

1. “Foundations of Solid Mechanics”, Prentice-Hall, Inc.


2. “Introduction to the Mechanics of a Continuous Medium” Prentice-Hall, Inc.

Workbook of Applications in VectorSpace C++ Library 613


Chapter 5 Advanced Finite Element Methods
we do not proceed to get R. We refer to J.E. Marsden and T.J.R. Hughes[1983, p. 55]1. With the Cayley-Hamil-
ton theorem from linear algebra, for a two-dimensional case,

V 2 – Iv V + IIv I = 0 Eq. 5•322

where Iv = tr V, and IIv = det V. The solution of this equation can be shown as

1
V = ----------------------------------- ( b + II b I ) Eq. 5•323
( I b + 2 II b )

Since we have assumed plain strain, this two-dimensional formula is sufficient.

Consistent Tangent Moduli, Tangent Stiffness and Residual: The consistent tangent moduli (Eq. 5•284 with
deviatoric part Eq. 5•285 modified by Eq. 5•302-Eq. 5•306) is having without the push-forward and set g = i
(since the current configuration is described in an instantaneous Cartesian coordinates). The implementation is
transparent (see Program Listing 5•25). Some nuisance in the code is caused by the use of engineering conven-
tion of the deviatoric Kirchhoff stress (s) as a vector instead of its counterpart dev τ as a matrix. Naturally, Vec-
torSpace C++ Library, designed for general purpose numerical computation, does not have specific operators
defined for s. Therefore, we throw the VectorSpace C++ Library out of the window, s need to be computed com-
ponent by component just as you would do in C or Fortran. A better implementation will be to use object-ori-
ented method to extend a new derived class for this “engineering 1-D matrix”.

Implementation 2: Closest-point-projection Method


The project file for the implementation 2 is “test_finite_deformation_covariant.exe” in project workspace
file “fe.dsw”.

We mentioned that the beauty of implementation 1 is that no push-forward is necessary. Covariance is


achieved by choosing wisely the configuration for the stress-strain path integration algorithm without having to
do operations regarding to the covariant issue. However, “Step 0” (or “Step 3” in Simo et al.[1985]2) in imple-
mentation 1 is lengthy and costly. This is the price we pay for outsmarting the covariance requirement. This step
is, at least, untidy comparing to the rest of the implementation 1. In implementation 2 (based on Simo[1988]3),
we face the covariance directly and get rid of the above sloppy details. This implementation not only leads to a
neat algorithm, but its simplicity also reflects the theoretical satisfaction. The algorithm is more general and
applicable to other problems. We do not base the algorithm on maneuvers that are relevant only to the specific
problem at hand. We emphasize that the numerical results of the two implementation are almost the same, differ

1. “Mathematical Foundations of Elasticity”, Prentice-Hall, Inc.


2. J.C. Simo, R.L. Taylor, and K.S. Pister, 1985, “Variational and Projection Methods for the Volume Constraint in Finite
Deformation Elasto-Plasticity”, Computer Methods in Applied Mechanics and Engineering, vol. 51, p. 177-208.
3. J.C. Simo, 1988, “A Framework for Finite Strain Elastoplasticity Based on Maximum Plastic Dissipation and the Multi-
plicative Decompositions: Part I. Continuum Formulation.” Computer Methods in Applied Mechanics and Engineering, vol.
66, p. 199-219. “Part II. Computational Aspects.”, vol. 68, p.1-31.

614 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity

...
C0 n2(4, (double*)0);
n̂ 2
n2[0] = n[0]*n[0]+n[3]*n[3]; n2[1] = n[3]*n[3]+n[1]*n[1];
n2[2] = n[2]*n[2]; n2[3] = n[3]*(n[0]+n[1]); tr( n̂ 2 )
C0 trn2 = n2[0]+n2[1]+n2[2];
C0 dev_n2(4, (double*)0); dev_n2 = n2;
for(int i = 0; i < 3; i++) dev_n2[i] -= trn2/3.0;
dev( n̂ 2 ) = n̂ 2 - 1/3 tr( n̂ 2 )
C0 mu_2bar = mu_bar_q - (alpha_n_q[0]+alpha_n_q[1]+alpha_n_q[2])/3.0,
2µγ n + 1
µ ≡ µ – --- tr ( α )
f_0 = 2.0*mu_bar_q*lambda_q / ZAI_norm_q, 1
f0 ≡ ------------------
-
delta_0 = (1.0+d_H_alpha/3.0/mu_+d(KAPA)/3.0/mu_2bar),
f_1 = (1.0/delta_0-f_0),
3 , ξn + 1 ,
TR

delta_1 = f_1*2.0*mu_2bar -
(1.0/delta_0*(1.0+d_H_alpha/3.0/mu_)-1.0)*(4.0/3.0)*lambda_q, 1 h’ κ’
f 1 ≡ ----- – f 0 δ0 ≡ 1 + ------ + ------
delta_2 = 2.0*ZAI_norm_q*f_1; δ0 , 3µ
C0 TR_h_dev = 2.0 * mu_2bar * (I_mu - (One%One)/3.0) - 3µ
(2.0/3.0)*(ZAI_q%One+One%ZAI_q);
a_dev_q = TR_a_dev_q - f_0 * TR_h_dev - delta_1*(n%n) -
δ 1 ≡ f1 2µ – -----  1 + ------ – 1 --- γ n + 1
(delta_2/2.0)*(n%dev_n2+dev_n2%n); 1 h’ 4
} else { δ0 3µ 3
a_dev_q = TR_a_dev_q;
ofs << " Elastic state: element # " << en << ", quadrature point # " << i

}
<< ", yield ratio: " << yield_ratio_q << endl;
δ2 ≡ 2 ξnTR+ 1 f1
sigma_q = s_q/J_q + p*One;
1
} TR
a dev n+1 ≡ 2µ I – --- ( g ⊗ g ) –
. . . 3
2 TR
– --- ( s ⊗ g + g ⊗ s TR )
3
1
TR
h dev n+1 ≡ 2µ I – --- ( g ⊗ g )
. . . 3

– --- ( ξ ⊗ g + g ⊗ ξ TR )
2 TR
3

. . .
ep
a dev n+1 ≡ a nTR
+ 1 – f 0 h n + 1 – δ 1 ( n̂ ⊗ n̂ )
TR

– δ 2 ( n̂ ⊗ dev [ n̂ 2 ] ) s

C0 stiff_geometrical, stiff_material; σ = J-1 τ = s / J + p 1


{

∇Tη : [ σ ∇u ]
C0 e = BASIS("int", ndf),
E = BASIS("int", nen),
U = (e%e)*(E%E);
H0 fact = wx*(sigma[0]*~wx+sigma[3]*~wy) + wy*(sigma[3]*~wx+sigma[1]*~wy);
stiff_geometrical &= (+(fact*U[0][0]+ fact*U[1][1])) |dv; ∫ B T [ a dev
ep – 2pI ]B dv
} φ ( Ωe ) n+1
{
C0 dN_bar = (( (~wx) || (~wy) ) | dv) / vol; T
stiff_material &= ( ((~B) * (a_dev/j + p*((One%One) - 2.0*I_mu)) * B) | dv ) + [ U’’( Θ ) N’ N’]vol [ φ n + 1 ]
+ dd(U) * theta * ((~dN_bar)*dN_bar) * vol;
}
stiff &= stiff_geometrical + stiff_material; ∫ B T σ dv
force &= -((~B)*sigma)|dv; φ ( Ωe )

Listing 5•25 Consistent tangent moduli, tangent stiffness and residual.

Workbook of Applications in VectorSpace C++ Library 615


Chapter 5 Advanced Finite Element Methods
only up to round-off error, let alone the qualitative results of the problem. In the following only the differences in
the two implementations are discussed.
The stress-strain path integration method is the closest-point-projection. The over-all algorithm is therefore
closer to the infinitesimal case, comparing to the Implementation 1. Conceptually, Implementation 2 only differs
from infinitesimal case in that now the finite deformation kinematics is used. With the closest-point-projection
method the integration step is to update the history data {Θn, pn, Jn, Fn, σn, b̂ e n, e pn, αn} at tn to {Θn+1, pn+1,
Jn+1, Fn+1, σn+1, b̂ en+1,e pn+1,αn+1} at tn+1. For updating intermediate configuration, b̂ is used in place of (F p)-1
e
p -1
in the history data of the implementation 1. In implementation 1, (F ) relies on covariance of constitutive equa-
tion to keep the algorithm covariant (the infamous “Step 0” in Implementation 1). In the present implementation,
b̂ is covariant by simply push-forward to φn+1 with the volume-preserving incremental deformation gradient
e
ˆ
Fu .
Simo[1988]1 listed six steps for closest-point-projection method described in the following two sections.
ˆ
Volume-Preserving Incremental Deformation Gradient ( F u ) from Shape Derivatives at φn or φ0 : Step 1:
ˆ
geometric update with volume-preserving incremental deformation gradient— F u . Note a given incremental dis-
placement at the current and referential configuration is ∆u(φn(X)) = ∆U(X). The current configuration is update
through φn+1 = φn + ∆U. The deformation gradient tensor is updated by Fn+1 = Fn + GRAD ∆U = [I + ∇n∆u] Fn =
ˆ
Fu Fn, where Fu = [I + ∇n∆u], is the incremental deformation gradient. Fu = det(Fu) -1 / 3 Fu, is the volume-pre-
serving incremental deformation gradient tensor. There are two ways to do this. The macro definition
__REFERENCE_DERIVATIVES turn on code segment that implements shape derivatives base on φo.
The first way computes the shape derivatives from X = φo. Then the deformation gradient F is obtained first
through F = Fn + GRAD ∆U. Then the incremental deformation gradient is computed from Fu = F Fn-1. The sec-
ond way computes the shape derivatives from xn = φn. Then reversing the process, first get the incremental
deformation gradient by Fu = I + grad ∆u. Then the deformation gradient F is obtained from F = Fu Fn.The com-
ˆ
putation of J, Ju and F u are simply obtained from the definitions.

Incremental Covariant Algorithm: Step 2-Step 5 These steps include computing the elastic-predictor, the plas-
tic-corrector, the radial return mapping algorithm, and the consistent tangent moduli. They are the same as in the
ˆ
Implementation 1, except we need to push-forward b̂ n and αn by F u in the elastic-predictor and the radial
e

return mapping algorithm. In the elastic predictor step,

αnTR+ 1 ≡ dev [ Fˆ u αn ( Fˆ u )
T ˆ ˆ T
] , and b̂ nTR
+ 1 ≡ F u b̂ n ( F u ) Eq. 5•324

In the radial return mapping step,

ˆ T
µ ≡ µ – --- tr [ F u α n ( F u ) ]
1 ˆ
Eq. 5•325
3

1. J.C. Simo, 1988, “A Framework for Finite Strain Elastoplasticity Based on Maximum Plastic Dissipation and the Multi-
plicative Decompositions: Part I. Continuum Formulation.” Computer Methods in Applied Mechanics and Engineering, vol.
66, p. 199-219. “Part II. Computational Aspects.”, vol. 68, p.1-31.

616 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity

H0 F_u_bar = INTEGRABLE_MATRIX("int, int, Quadrature", 3, 3, qp);


{
C0 du(nen, ndf, (double*)0);
∆u(φ(X)) = ∆U(X)
for(int i = 0; i < nen; i ++) Method 1: based on φo
for(int j = 0; j < ndf; j++) du[i][j] = delta_ul[i*ndf+j]; X = φo
#if defined(__REFERENCE_DERIVATIVES)
H1 X = N*(xl);
d N / d X = (d N / dξ) (d X / dξ)-1
H0 NX = d(N) * d(X).inverse(); F = Fn + GRAD ∆U
= Fn + ∆U (d N / dX)
F = F_n + (~du)*NX; T
H0 F_u = F * F_n.inverse(); -1
#else
Fu = F Fn
H1 x_n = N*(xl+u-du); Method 2: based on φn
H0 Nx_n = d(N) * d(x_n).inverse(); x = φn
H0 F_u = I_22 + (~du)*Nx_n;
F = F_u * F_n;
d N / dx = (d N / dξ) (d x / dξ)-1
#endif Fu = I + grad ∆u = I + ∆uT (d N / dx)
j = F.det(); F = Fu Fn
H0 J_u = F_u.det();
H0 F_u_bar_22 = 1.0/exp(log(J_u)/3.0) * F_u;
J = det F
F_u_bar[0][0] = F_u_bar_22[0][0]; Ju = det Fu
F_u_bar[1][1] = F_u_bar_22[1][1]; Fu = Ju-1/3 Fu
F_u_bar[0][1] = F_u_bar_22[0][1];
F_u_bar[1][0] = F_u_bar_22[1][0];
F_u_bar[2][2] = 1.0/exp(log(J_u)/3.0);
} J2/3
H0 J_two_third = exp(log(j)*2.0/3.0);
H0 g(4, (double*)0, qp);
{
∂Z r ∂Z s
H0 Zx = (~xl)* Nx; - --------- δ
g mn = ---------
g[0] = Zx(0)*Zx(0); ∂x m ∂x n rs
g[1] = Zx(1)*Zx(1);
g[2] = 1.0;
∂Z 1 ∂Z 1 ∂Z 2 ∂Z 2 ∂Z 3 ∂Z 3
g[3] = Zx(0)*Zx(1); = ---------
- --------- + ---------- --------- + ---------- ---------
} ∂x m ∂x n ∂x m ∂x n ∂x m ∂x n

Listing 5•26 Incremental deformation gradient and spatial metric tensor.

This definition of µ is also used in the consistent tangent moduli. The radial return mapping also include updat-
ing history data

2
e np + 1 = e np + --- γ n + 1 Eq. 5•326
3

αn + 1 = α TR n + 1 + -----
h’

- 2µγ n + 1 n̂ n + 1 Eq. 5•327

s n + 1 = s TRn + 1 – 2µγn + 1 n̂ n + 1 Eq. 5•328

The spatial metric tensor g is now in place of 1 in “Implementation 1” used in implementation 1. The present
implementation is straight forward except, again, minor nuisance caused by engineering convention that is not
supported by VectorSpace C++ Library.

Workbook of Applications in VectorSpace C++ Library 617


Chapter 5 Advanced Finite Element Methods
Update intermediate configuration: Step 6: Update intermediate configuration is brand new but neat com-
pared to implementation 1. Thus, we can afford to compute this value in each iteration no matter the result will
attain convergence or not.

2µγ
1 – --------- tr [ b̂ n + 1 ] n̂ n + 1
b̂ ne + 1 = b̂ ne +TR e TR
Eq. 5•329

The finite deformation formulation for the implementation 1 for cutting-plane method is implemented in
project “finite_elastoplasticity” and the implementation 2 for closet-point-projection method is implemented in
project “covariant_finite_elasticity”. Both projects are in project file “fe.dsw”. Figure 5•24 shows the strip with
circular whole under 0.33 % (6 incremental steps) and 0.56 % (10 incremental steps) vertical stretching Figure
5•24a and b, respectively. At 0.33% stretching the deformation mode is similar to the infinitesimal case up to
0.56 % stretching (Figure 5•24c). At 0.56 % stretching the high yield ratio area is confined to the bottom edge of
the model. This is the “necking” process developed. After 1/10 (180 incremental steps) vertical stretching is
shown in Figure 5•25a. Necking is developed into a clear stage.

Line Search: The time consuming nonlinear iterative algorithm becomes clearly a problem. 180 incremental
step, say with average of five iteration for each incremental step means that we will have computation time that
is about a thousand times that of a linear problem. The problem is even more serious if we consider to stretch the
strip with circular hole problem up to 1/3 as shown in Figure 5•25b. That will need about four thousand times
that of a linear problem. A lot of computing time will be required. One immediate solution is to increase incre-
mental loading 10 times by using δu = 0.1 instead of δu = 0.01 for each incremental loading step. However, with
such magnitude of incremental loading, the nonlinear iteration fails to converge even for the first time step. As
we recalled from Chapter 2 the classic Newton method is powerful that its convergent rate is quadratic. How-
ever, the Newton method often leads to wild search path and may easily fail to converge. Therefore, a line search
method with golden section can be use to regulate the search path of the Newton method (see page 125 in Chap-
ter 2). This is implemented as the follows

1 Matrix_Representation::Assembly_Switch = Matrix_Representation::ALL;
2 mr.assembly();
3 new_time_flag = FALSE; // flag to turn off Step 3 of the finite deformation procedure, after first iteration
4 C0 p = ((C0)(mr.rhs())) / ((C0)(mr.lhs()));
5 energy = fabs( (double)(p * ((C0)(mr.rhs())) ) );
6 if(count == 1) { // first iteration only
7 for(int j = 86; j <= 92; j++) {
8 gh[j][1] += d_gh[j][1];

b_e_hat_q[0][0] = TR_b_e_hat_q[0][0]-mu_2bar_gamma/(3.0*mu_)*tr(TR_b_e_hat_q)*n[0];
b_e_hat_q[1][1] = TR_b_e_hat_q[1][1] - mu_2bar_gamma/(3.0*mu_)*tr(TR_b_e_hat_q)*n[1];
b_e_hat_q[2][2] = TR_b_e_hat_q[2][2] - mu_2bar_gamma/(3.0*mu_)*tr(TR_b_e_hat_q)*n[2];
2µγ
1 – --------- tr [ b̂ n + 1 ] n̂ n + 1
b̂ ne + 1 = b̂ ne +TR
b_e_hat_q[0][1] = b_e_hat_q[1][0] = e TR
TR_b_e_hat_q[0][1] - mu_2bar_gamma/(3.0*mu_)*tr(TR_b_e_hat_q)*n[3]; 3µ

Listing 5•27 Update intermediate configuration.

618 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity

3.50 & up
3.00
2.50
2.00
1.50
1.00
0.75
0.50

(a) finite formulation (c) infinitesimal formulation 0.25


(b) finite formulation
0.33 % stretching 0.56 % stretching 0.56 % stretching 0.00
Figure 5•24 (a) and (b) yield ratios of finite formulation computation after 0.33 % and
0.56 % stretching. At 0.56 % streching the plastic zone has developed into a “necking”
process. (c) comparing to the yield ratio from previous infinitesimal formulation at 0.56
% stretching, which is still under the same deformation mode as that in (a).

(a) stretching 1/10


(b) stretching 1/3
vertically vertically

Figure 5•25 The further development of the “necking” of the strip with (a) 1/10
vertical stretching, and (b) 1/3 vertical stretching.

Workbook of Applications in VectorSpace C++ Library 619


Chapter 5 Advanced Finite Element Methods
9 delta_gh[j][1] = d_gh[j][1];
10 d_gh[j][1] = 0.0;
11 }
12 uh = gh;
13 delta_uh = delta_gh;
14 d_uh = d_gh;
15 }
16 if(count != 1 && energy > energy_old) { // abandant the current solution and activate line search
17 cout << " energy: " << energy << endl;
18 U_h uh_temp(ndf, oh); U_h delta_uh_temp(ndf, oh); // save current total displacement
19 for(int j = 0; j < uh_temp.total_node_no(); j++) { // and total increment of displacement
20 uh_temp[j] = uh[j]; delta_uh_temp[j] = delta_uh[j];
21 }
22 double alpha = 0.0, // line search parameter
23 left = 0.0, right = 1.0, length = right-left; // initial bracket
24 int line_search_counter = 0;
25 do {
26 (C0)(mr.rhs()) = 0.0;
27 double golden_alpha; alpha = golden_alpha = (left + 0.618 * length);
28 for(int j = 0; j < uh_temp.total_node_no(); j++) { // restore
29 uh[j] = uh_temp[j]; delta_uh[j] = delta_uh_temp[j];
30 }
31 delta_uh += alpha*p; // update total increment of displacement
32 uh += alpha*p; // update total displacement
33 Matrix_Representation::Assembly_Switch = Matrix_Representation::RHS;
34 mr.assembly(); // form residual vector (rhs)
35 double golden_residual = norm( (C0)(mr.rhs()) );
36 C0 mr_rhs_temp = (C0)(mr.rhs());
37 (C0)(mr.rhs()) = 0.0;
38 double left_alpha; alpha = left_alpha = (left + 0.382 * length);
39 for(int j = 0; j < uh_temp.total_node_no(); j++) { // restore
40 uh[j] = uh_temp[j]; delta_uh[j] = delta_uh_temp[j];
41 }
42 delta_uh += alpha*p; // update total increment of displacement
43 uh += alpha*p; // update total displacement
44 Matrix_Representation::Assembly_Switch = Matrix_Representation::RHS;
45 mr.assembly(); // form residual vector (rhs)
46 double left_residual = norm( (C0)(mr.rhs()) );
47 if(golden_residual < left_residual) {
48 left = left + 0.382 * length;
49 alpha = golden_alpha;
50 (C0)(mr.rhs()) = mr_rhs_temp;
51 } else {
52 right = left + 0.618 * length;
53 alpha = left_alpha;

620 Workbook of Applications in VectorSpace C++ Library


Finite Deformation Elastoplasticity
54 }
55 length = right-left;
56 cout << line_search_counter << " alpha: " << alpha <<
57 " current energy: " << fabs( (double)((alpha*p)*((C0)(mr.rhs()))) ) << endl;
58 } while (line_search_counter++ < 10);
59 du = alpha * p;
60 for(int j = 0; j < uh_temp.total_node_no(); j++) { // restore
61 uh[j] = uh_temp[j]; delta_uh[j] = delta_uh_temp[j];
62 }
63 } else du = p;
64 residual_norm = norm((C0)(mr.rhs()));
65 energy_old = energy;
66 if(count == 1) energy_0 = energy;
67 cout << " energy: " << fabs( (double)(du*((C0)(mr.rhs()))) ) << endl;
68 (C0)(mr.lhs()) = 0.0;
69 (C0)(mr.rhs()) = 0.0;

The line search method is activated by setting macro definition “__GOLDEN_SECTION_LINE_SEARCH” at


compile time in project “finite_elastoplasticity” in project workspace file “fe.dsws”. With this line search method
the incremental loading can be set at δu = 0.1. The result in Figure 5•25b is obtained with only 60 incremental
loading steps.

Workbook of Applications in VectorSpace C++ Library 621


Chapter 5 Advanced Finite Element Methods

622 Workbook of Applications in VectorSpace C++ Library


le 198
Index Matrix 22
Scalar 5
Subvector and Submatrix 46
Symbols Vector 11
! 30 axiom of locality 599
(weak formulati 242 axisymmetric problem 234
: 375, 388 axisymmetrical problem 302
__lhs() 283, 336
__rhs() 336 B
~ 15 back stress 576, 579
“__rhs( 283 back substitution 30
“make( 291 backward difference 229, 254
banded sparse matrix 285
Numerics base point 95
1-norm 17 basic feasible solution 113
2-D problem 351 basic set 113, 137
2-norm 17 basic set method 113
basic solution 113
A basic variable 114
Basis 1, 48
a priori imformation 69
basis 382
abstraction 291
basis expression 213
acceleration 253, 256, 336, 342
extended 50
active set 113
primary 50
active set method 116, 118
Basis_Submatrix 52
adjacent extreme point 114
behavior of an object 270
algebra
bending moment 207, 305
C2 type 104
BFGS method 134
tangent bundle 96
bilinear 4-node element 361, 464
algorithm 270
bilinear 4-nodes element 352
aliases 62
bilinear four-node element 267
aliasing 398
bilinear functiona 201
analytical geometry 191
bilinear interpolation function 177
ANSI/ISO C++ 1
bisection 125
approximation basis function 202, 216, 234, 259, 266
block( ) 192
arc length method 186
block() 356
area coordinates 454
B-matrix formulation 375
arithmetic operator 5
Bode’s integration rule 205, 255
array of double 9
body 95, 593
array of pointers to double 9
boundary condition
assignment 12
Dirichlet 63
assignment by reference 5, 6
essential 63
assignment by value 5, 6
homogeneous 63
assignment operator 12, 24
mixed 63
associative flow law 577, 599
natural 63
auchy stress rate 591
Nuemann 63
augmented Lagrangian method 162
boundary condition of the first kind 63
augmented lagrangian method 154
boundary condition of the second kind 63
automatic mesh generation 192
boundary condition of the third kind 63
autonomous virtual constructor 290
boundary element method 187, 246, 265
Integrable_Tangent_Bundle 184
boundary integral 269
Integrable_Tangent_of_Tangent_Bundle and
boundary integral equation 242
Integrable_Vector_of_Tangent_of_Tangent_Bund

Workbook of Applications in Vector Space C++ Library 623


boundary integration 283 colatitude 189
boundary solution method 242 column selector 25
Broyden-Fletcher-Goldfarb-Shanno method 134 column-pivoting 37
Bubnov-Galerkin method 233, 234, 238, 242, 312 column-wise conatenation operator 12
bulk modulus 577, 609 column-wise vector 9
by reference 12, 62 column-wise-concatenation
by value 12 one-by-one 14
column-wise-concatenation operator 7, 26
combined Newton and steepest descent method 129, 157
C compilation firewall 289
C++ library complete order of polynomial 400
standard 5 completeness requirement 268, 400
C_0 4 complete-pivoting 31
C0 constructor 2 composite class 291
C0 space 1 Computer Axial Tomography 75
C0 type 1 concatenation operator 12
Basis 48 condition nubmer 31
Matrix 21 conductivity matri 253
Scalar 3 conductivity matrix 336
Subvector and Submatrix 42 conjugate direction 131
Vector 11 conjugate gradient method 131, 156, 585
C0-continuity 453 consistency condition 576, 581
C1 configuration 593 consistent mass matrix 253, 336, 339, 365
C1 space 95 consistent tangent moduli 573, 578, 581, 606, 614
C1 type 95 constant average acceleration 257, 259
Vector_of_Tangent_Bundle 99 constitutive equation 376
C1-continuity 332, 451 constitutive equations 468, 475, 599
C2 space 95, 104 constrained optimization 95, 137, 605
C2 type 95, 104 constraint optimization 458
CAD 270 constraint type selector 276, 298
calculus 96, 165, 186, 600 constraint value selector 276, 298
CAT 75 constructor 2
catc 5 C0 2
Cauchy stress 574, 578, 600 dedicate
Cauchy stress tensor 375 Subvector and Submatrix 42
Cauchy stress’s associate tensors 599 dedicated
Cayley-Hamilton theorem 614 default
central difference 229, 254 Vector 10
central difference scheme 63 Matrix 21
change of variable 208 Scalar 2
Cholesky decomposition 31, 129, 235, 277 Tangent_Bundle 97
class 270, 271 Vector 9
classic Newton method 151, 156 discretized global domain 274
classic Newton’s method 123 element 271
classic Newton-Raphson method 573 Integrable_Matrix 180
classical Newton’s method 335 Integrable_Scalar 170
client-server package 293 Integrable_Tangent_of_Tangent_Bundle and
clique 286 Integrable_Vector_of_Tangent_of_Tangent_Bundle
closest-point projection method 608 198
closest-point-projection 577 Integrable_Vector 176
Cn space 1 Matrix 21
Cn transformation 591 node 271
code evolution 348 Scalar 2
code reuse 269, 270 Subvector and Submatrix 42

624 Workbook of Applications in Vector Space C++ Library


virtual 2, 3 data structure 270
autonomous 46 dynamic array 273
Scalar 5 database engine 293
Vector 11 database integrity 357
Subvector and Submatrix 46 database language 293
Vector 11 database schema 274
consturctor Davidon-Fletcher-Powell method 134
Vector 9 dd() 105
continuity equation 368 deciated constructor
continuity requirement 268, 306, 312 Integrable_Vector 176
continuous block decomposition 69
Subvector and Submatrix 43 Cholesky 31, 129, 235
continuum elastoplastic tangent moduli 577 LU 29, 60
continuum tangent moduli 578 modified Cholesky 31, 32
convected time derivative 597 QR 33
convergence acceleration matrix 484 singular value 91
coordinate bases 593 dedicated constructor 2, 9, 21, 97
coordinate tranformation rule 191 C0 type 3
coordinate transformation method 187 Integrable_Tangent_Bundle 183
coordinate transformation rule 167, 187, 267, 296, 300, 352 Integrable_Tangent_of_Tangent_Bundle and
coordinate-free tensorial formulation 375, 388 Integrable_Vector_of_Tangent_of_Tangent_Bundle
co-rotational formulation 400 198
co-rotational stress rate 591 Tangent_of_Tangent_Bundle 105
costructor Vector_of_Tangent_Bundle 100
virtual def 376, 389
autonomous 22 default dedicated constructor 10
covariance 72, 590, 603, 614 default matrix solver 30
covariance matrix 87 defer evaluation 96
covariance of energy balance 599 deflection of the beam 259
covariant 597, 608 deformation gradien 608
create() 291 deformation gradient 593
cubic Hermit element 309 degree of entrance 285
curvature 207, 305 degree of freedoms 267
cutting-plane method 577, 608 demotion 287
cylindrical coordinates 302 dependency breaker 285
dependency graph 285
D dependency relation 270
determinant 31
d2() 105
deviatoric stress 579
damping parameter 339
deviatoric stress tensor 376
data abstraction 18, 270
deviatoric-spherical kinematic split 596
finite difference 67
DFP method 134
H0 type 165, 169
diagonal-pivoting 31
node 271
diffeomorphism 591
Scalar 2
differentiable object 96
Tangent_Bundle 96
differential equation 170
Tangent_of_Tangent_Bundle 104
differential geometry 95
Vector_of_Tangent_of_Tangent_Bundle 108
Dirac delta functio 227
data abstrcation
Dirac delta function 220, 233
Vector 9
directional derivatives 201
data definition language 274
Dirichlet boundary condition 63, 202, 203, 205, 295, 352
data manipulation language 274
Dirichlet boundary conditions 223
data query language 274
discretized global domain 272, 285
data resolution matrix 71
discretized global free degree of freedoms 274

Workbook of Applications in Vector Space C++ Library 625


dispatch 270, 277 element subroutine 277
displacement 253, 256 element tangent stiffness matrix 328, 331
distributed load 310, 322 Element Type Register 281
distributed loadin 315 element type register 290, 298
distributed loading 316 Element_Formulation 277, 278, 281
distribution rule 16, 18, 29 element_node_number 272
div 375, 389 element_type_number 272, 291
divergence operator 375 encapsulate 270
divergence theorem of Gauss 223, 351, 375, 452 encapsulated 289
domain integral 269 encapsulation mechanism 271
double energy norm 576
array of 9 engineering convention 41, 377
array of pointers to 9 equality constraint 113
double contraction 375, 388 equivalence theorem 491
double contraction operator 392 equivalent nodal force 282
Doyle-Erickson formula 600, 602 escalation 287
dual abstraction 97 essential boundary condition 63, 65, 268, 274, 307
H1 and H2 types 183 Euclidean norm 17, 29
Integrable_Scalar 169 Eulerian strain tensor 594, 603
Integrable_Vector 176 Euler-Lagange equations 475
Tangent_of_Tangent_Bundle 104 Euler-Lagrange equations 118, 145, 163, 210, 218, 312, 318, 324, 468,
Vector_of_Tangent_of_Tangent_Bundle 108 605, 607
dual problem 151 exception handling 5
dyad 20, 28 explicit shape function 442
Dynamic_Array 273 extended basis expression 50
extended formula 166
extremum point 118
E
e slack variable 114
EF 285 F
eigenfunction 85 factor loading 91
eigenvalue 85, 235 fe.lib 265
eigenvalue problem 37, 84, 234 Finger deformation tensor 594, 604
eigenvector 87, 235 finite deformation 590
elastic coefficients 377 finite difference 63, 84
elastic Finger deformation tensor 603 finite difference method 67, 265
elastic predictor 581, 609 finite difference stencil 64
elasticities 600 finite element 267
elasticity tensor 600 finite element approximation 268, 269, 291, 352
elastic-predictor 577 finite element library
elastic-predictor / plastic-corrector 611 fe.lib 270
elastoplastic free energy 604 finite element method 41, 187, 265
elastoplasticity 573 finite element space 388, 389, 392
electric circuit 60 first derivative information 127
element 266, 271 first Piola-Kirchhoff 598
element force vector 269, 279, 295, 307, 317, 322, 352, 461 first Piola-Kirchhoff stress tensor 599, 600
element formulation 285, 324 first-order condition 118, 331
element formulation definition language 277 first-order optimal condition 145
element nodal coordinates 282 fixed and free variables 299
element node number 267 fixed current configuration 610
element residual vector 328, 331 fixed degree of freedoms 283
element selector 25, 274 fixed nodal values 267
element stiffness matrix 269, 279, 295, 307, 352, 377, 378, 388, flat-interface 40
461, 485, 491 Fletcher-Reeves formula 131, 156

626 Workbook of Applications in Vector Space C++ Library


flexure rigidity 84, 207, 305, 309
flop-count 37 Dirichlet 298
flow chart 270 Neumann 298
force vector 253, 336 global degree of freedom number 283
formal additive decomposition 603, 610 global discretized domain 299
Fortran 270 global equation number 270
Fortran-style selector 25 global force vecto 270
forward declaration 287 global Newton-Raphson Iteration 574
forward difference 229, 254 global Newton-Raphson iteration 573, 574
forward elimination 30 global node number 272
forwarding 279 global stiffness matrix 270
Fourier law 351, 363, 451, 464 global variable number 270
fourth-order differentail equation 206 golden section 125, 127, 335
fourth-order differential equation 218, 223, 305 golden section line search 335
fourth-order tensor 378, 600 grad 375
framework-based package 293 gradient operator 375
free degree of freedom number 283 gradient projection method 137, 141
free energy function 574 graph level structure 286, 287
free function 389 Green deformation tensor 594
free-format 4 Green elastic material 598
Frobenius norm 29, 39 Green function method 220, 225
frontal method 285 Green strain tensor 594
function Green’s first identity 242
Integrable_Matrix 181 Green’s function 170, 246
Integrable_Scalar 174 Green’s second identit 242
Integrable_Vector 178 Green-Naghdi decomposition 603
Matrix 22 GUI interface 293
Scalar 5
Subvector and Submatrix 47 H
Tangent_Bundle 98 H0 space 165
Tangent_of_Tangent_Bundle 105 H0 type 165
Vector 12 H1 space 165
Vector_of_Tangent_Bundle 102 H1 type 165, 183
Vector_of_Tangent_of_Tangent_Bundle 109 H2 space 165
functional analysis 165 H2 type 165, 183
fundamental solution 246 harmonic basis function 243
fundamental theorem of linear programming 113, 138 harmonic function 242
heat capacitiy matrix 253
G heat capacity matrix 336
Galerkin formulation heat conduction 63, 451
non-linear 327 heat conduction element formulation 278
Galerkin method 233, 268 heat flux 351, 451
Galerkin weak formulation 295 heat flux nodal projection method 364
Galerkin weighting 365 heat flux on gauss point 363
garbage collection 6, 12 heat source 351
Gauss elimination 60 heat source vector 336
Gaussian quadrature 167 HeatQ4 278
generalizaed inverse 35 Hellinger-Reissner variational principle 469
generalized eigenvalue problem 235 Hermite cubics 332, 339
generalized Hooke’s law 377 Hessian 31, 118, 129, 157, 459, 586
generalized inverse 77 Hessian matrix 109, 145
geometrical stiffness 607 hidden complexities 271
gh_on_Gamma_h 275 higher-order patch test 486

Workbook of Applications in Vector Space C++ Library 627


Hilbert space 165 INTEGRABLE_SCALAR 174
history data 579 Integrable_Scalar 169
Hölder norm 17 Integrable_Submatri 181
homogeneous boundary condition 63 Integrable_Submatrix 169, 215, 385
homogeneous essential boundary condition 201 Integrable_Subvector 169, 181, 215
hourglass element 464 INTEGRABLE_TANGENT_BUNDLE 188
hourglass mode 398, 464 Integrable_Tangent_Bundle 183
hourglass stiffness 465 Integrable_Tangent_of_Tangent_Bundle 198
Householder transformation 33 Integrable_Vector 169, 176
Hu-Washizu variational principle 475, 606 Integrable_Vector_of_Tangent_Bundle 183
hyperbolic equation 256, 259, 336, 339 Integrable_Vector_of_Tangent_of_Tangent_Bundle 198
hyperbolic-parablic equation 258 integrating by parts 242, 452
hyperbolic-parabolic equation 256 integration by parts 223, 312, 375
hyperelasticity 599, 600 integration operator 174
hypersingularity of the boundary integral equation 251 interface 579
internal force divergence term 574
internal heat source 63, 451
I internal plastic variables 609
ill-conditioned 37, 154 interpolation function 191, 267
impact of change 269 invariant 601
impenetrability of matter 593 inverse of the Hessian 588
implementation 579 irreducible formulation 208, 305, 306, 339
inactive constraint 118 irreversible thermodynamics 576
incompressible fluid 368 irrotational 368
incompressible formulation 491 isomorphism 591
incompressible u-p formulation 485 isoparametric element 189, 267, 296
incompressible u-p-ev formulation 495 isotropic hardening 576
increment of the solution 328 isotropic hardening law 584
incremental boundary condition 574, 576 isotropic material 376, 602
incremental displacement 573
incremental loading algorithm 573
incremental objective algorithm 603 J
incremental solution 279 J2 plasticity 596, 599, 605
indefinit 31 Jacobian matrix 109, 169, 352
index-array Jaumann rate 599
Matrix 18 Jaumann stress rate 591
indicial notation 16 jump condition 170
indicial notation formulation 375, 377, 384
inequality constrained nonlinear programming 116 K
inequality constrained optimization 605 key 357
inequality constrained proble 145 key-stoning 398
inequality constrained problem 141 kinematic hardening 576, 584
inequality constraint 118 kinematic split 596
infinite norm 18 Kirchhoff stress 600, 602
infinitesimal position vector 593 Kirchhoff stress tensor 599
infinitesimal strain tensor 377, 596 Kirchhoff’s laws 61
inf-sup condition 459 Kronecker delta 377
inheritance 270, 277 Kuhn-Tucker condition 119, 605
initial basic variables 114 kurtosis 231
initial condition 254, 259
initial value problem 253
inner product 165
L
Integrable 215 L2 space 165
Integrable_Matrix 169, 180, 391 Ladyzhenskaya-Babuska-Brezzi condition 459

628 Workbook of Applications in Vector Space C++ Library


Lagragian 4-to-9-node element 362 M
Lagrange 318 manifold 96
Lagrange Method 145 mass matrix 256, 280
Lagrange multiplier 318 mateiral strain tensor 594
active set method 118 material metric tensor 597
Lagrange multiplier formulation 208, 209, 305, 318 material rate of deformation tensor 597
lagrange multiplier formulation 325 material_type_number 272
Lagrange multiplier method 76, 141, 318 mathematical abstraction
Lagrangian 9-node element 361, 405 finite element method 266
Lagrangian element 189 mathematical abstraction of finite element method 271
Lagrangian functional 145, 218, 306, 312, 475, 605 MATRIX 22
active set method 118 Matrix 1, 18
Lagrangian multiplier 605 reference 21
Lagrangian strain tensor 594 matrix algebra 16
Lamé constants 376, 378 matrix decompsotion operator 30
Laplace operator 235, 242, 302 matrix form 202
latitude 189 matrix representation 283, 285, 299
LBB-condition 322, 459 matrix substructuring 343, 344, 453
least sqaures problem 34 Matrix_Representation_Couple 347
least squares formulation 331 maximum column norm 29
non-linear 327 maximum column sum 28
least squares method 240 maximum norm 18
least-squares 365 maximum plastic dissipation 576
Lebniz rule 96 maximum row sum 28
Lee decomposition 603 mean dilatation 596, 607, 608
left Cauchy-Green tensor 594, 597 member data 2
left polar decomposition 595 member function 2
left stretch tensor 595 memory management 18
level structure 286 Mentor Graphics 270
levelization idiom 287 method of moment 231
Lie derivatives 597 metric tensor 593
limitation principle by Fraeijs de Veubeke 461 minor symmetry 377
line search 335, 618 Mixed boundary condition 295
line search direction 125 mixed Boundary Condition 225
linear acceleration 258 mixed boundary condition 63, 65, 202, 204, 223
linear algebr 213 mixed formulation 208, 218, 305, 312, 343, 451, 491
linear functional 201 model parameters 69
linear interpolation function 296 model resolution matrix 78
linear least squares 69 modified Cholesky decomposition 31, 32, 157, 164
linear line element 296 moment of inertia 310
linear programming 113 moment of momentum balance principle 599
list data structure 281, 290 Moore-Penrose generalized inverse 40
Listing 1•1 Morley’s triangular plate element 446
Program 1 MR 285
local Newton-Raphson iteration 578 multiplicative decomposition 603, 610
locking 323, 398 multiplier update method 154
logic operator 5
lower trangular matrix 29
LU decomposition 29, 60 N
lumped mass matrix 365 natural boundary condition 63, 65, 269, 274, 282, 306, 307, 452
l-value 17 natural coordinates 267
nearly incompressible formulation 491
nen 266, 282
nested dissection 285

Workbook of Applications in Vector Space C++ Library 629


Neumann boundary condition 223, 224, 295, 352 utility 1
Newmark coefficient 258, 339 objected-oriented programming 3
Newmark method 258 objective 608
Newton’s Formula 574 objective functional 113
Newton’s formula 40, 109, 217, 578 objectivity 603
Newton-Raphson method 101, 123 object-oriented analysis 270, 285
nodal force 453 object-oriented design 270, 285
nodal heat flux 364 object-oriented modeling 345
nodal load 321 object-oriented paradigm 270
nodal loading 315, 364 object-oriented programmin 277
nodal stresses 395 object-oriented programming 265, 269, 270, 348
nodal value 267 Oldroyd rate 599
Nodal_Value 274 Omega_eh 272, 290
node 271 Omega_h 272, 274
node number 271 one-by-one column-wise-concatenation 14
node selector 274 one-by-one column-wise-concatenation operator 26
node_number_array 272 one-by-one row-wise concatenation 20
Nominal_Basis_Submatrix 54 one-by-one row-wise-concatenation 25
Nominal_Basis_Subvector 50, 52 operator 2, 12
Nominal_Submatrix 43, 108 arithmetic 5
Nominal_Subvector 43 assignment
non-basic set 137 Matrix 24
non-basic variables 114 column-wise-concatenation 7
non-conforming element 268 concatenation 12
non-linear differential equation 237 column-wise 12
non-linear optimization 113 row-wise 12
nonlinear optimization 137 Integrable_Matrix 181
nonlinear ordinary differential equation 327 Integrable_Scalar 174
non-linear problem 100, 277 Integrable_Vector 178
norm integration 174
1 17 logic 5
2 17 Matrix 22
Euclidean 17, 29 matrix decomposition 30
Frobenius 29, 39 positive unary 27
infinite 18 projection 48, 49
maximum 18 Scalar 5
maximum column 29 Subvector and Submatrix 47
p 17 symbolic 5, 7
spectral 28, 39 Tangent_Bundle 98
sum 17 Tangent_of_Tangent_Bundle 105
normal equations 35 Vector 12
normality rule 576 Vector_of_Tangent_Bundle 102
nsd 282 Vector_of_Tangent_of_Tangent_Bundle 109
Nuemann boundary condition 63 optimization 574
null space method 149, 150, 458, 460 constrained 95
number of element nodes 282 non-linear 113
number of spatial dimensio 282 unconstrained 95
numerical optimization 95 orthogonal 460
numerically differentiable object 95 orthogonal basis function 202
orthogonal complement 157
orthogonal matrix 33
O orthogonal property 35
object orthogonal tensor 594
primary 1

630 Workbook of Applications in Vector Space C++ Library


P primary casting 11, 27, 108, 215
primary object 1
parabolic equation 253, 336
H0 169
partial quasi-newton method 588
principal components analysis 87
partial-pivoting 31
principal discretization 347
penalty function formulation 208, 216, 305, 324
principal right-hand-side 347
penalty method 153
principle deviatoric stress 611
penalty objective functional 153, 154
principle of determinism for stress 590
penalty parameter 153
principle of material frame indifference 590, 600
perturbed Lagrangian functional 163
principle of maximum plastic dissipation 604, 605
perturbed Lagrangian method 162
priority ranking 4
Petrov-Galerkin method 233, 234, 238
private 270
physical element 267
private member function 339
piecewise cubic Hermit shape function 306
procedure programming method 286
Piola transformation 598, 600
procedure programming paradigm 277
pitch fork bifurcation 87
Program
pivoting
Listing 1•10 73
column 37
Listing 1•13 85
complete 31
Listing 1•15 91
diagonal 31
Listing 1•2 12
partial 31
Program Listing 1•1 1
row 31
Program Listing 1•10 73
plain strain 614
Program Listing 1•13 85
plane elasticity 375
Program Listing 1•15 91
plane strain 378
Program Listing 1•2 6, 12, 65
plane stress 378, 384
Program Listing 2•1 101
plastic consistency parameter 577
Program Listing 2•10 129
plastic metric tensor 603, 604
Program Listing 2•11 132
plastic relaxation stress 578, 604
Program Listing 2•12 134
plastic strain 576, 579
Program Listing 2•13 137
plastic-corrector 577, 581
Program Listing 2•14 138
p-norm 17
Program Listing 2•17 147
point-collocation method 227
Program Listing 2•18 149
pointer-arithmetics 9
Program Listing 2•19 151
Poisson equation 223, 242
Program Listing 2•2 109
Poisson ratio 584
Program Listing 2•20 155
Poisson’s ratio 378
Program Listing 2•21 156
Polak-Ribiere formula 131
Program Listing 2•22 156
polar decomposition theorem 595
Program Listing 2•23 159
polyconvex 602
Program Listing 2•24 159
polymorphic mechanism 298
Program Listing 2•25 162
polymorphism 270, 277, 278
Program Listing 2•26 164
positive definite 31, 145
Program Listing 2•3 116, 119
positive semi-definite 31
Program Listing 2•36 217
positive unary operator 27
Program Listing 2•7 123
post-processing 363, 364, 393, 394
Program Listing 2•8 126
potential flow 368
Program Listing 3•1 171
potential function 600
Program Listing 3•10 209
power
Program Listing 3•11 212
fractional argument 8
Program Listing 3•12 214
preferred coordinate system 597
Program Listing 3•13 217
pressure 376
Program Listing 3•14 219
primal problem 151
Program Listing 3•15 221
primary basis expression 50
Program Listing 3•16 223

Workbook of Applications in Vector Space C++ Library 631


Program Listing 3•17 225 operator 7
Program Listing 3•18 235 proper 465
Program Listing 3•19 238 protected 270
Program Listing 3•2 172 pseudoinverse 40
Program Listing 3•21 244 public 270
Program Listing 3•22 248 public interface 275
Program Listing 3•23 249 pull-back 597, 598
Program Listing 3•24 250 push forward 609
Program Listing 3•25 251 push-forward 593, 597, 598, 600
Program Listing 3•26 251 pythagorean law 186
Program Listing 3•27 255
Program Listing 3•28 259 Q
Program Listing 3•4 192 QR 69
Program Listing 3•5 192 QR decomposition 33, 69, 149, 277
Program Listing 3•6 196 quadratic convergent rate 579
Program Listing 3•7 203 quadratic interpolation function 187, 267, 300
Program Listing 3•8 204 quadratic line element 300
Program Listing 3•9 206 quadratic objective functional 145
Program Listing 4•1 296 quadratic programming 145, 458, 585
Program Listing 4•10 336, 338 quadrature 165
Program Listing 4•11 339 Gaussian 167
Program Listing 4•12 348 quasi-Newton BFGS method 585, 588
Program Listing 4•13 354 quasi-Newton condition 134
Program Listing 4•14 370 quasi-Newton method 133
Program Listing 4•15 380
Program Listing 4•16 400
Program Listing 4•2 300
R
Program Listing 4•22 443 radial return mapping algorithm 573, 577, 607
Program Listing 4•23 448 radial return mapping method 579, 581
Program Listing 4•5 313 radius 189
Program Listing 4•6 319 Raleigh damping 339
Program Listing 4•7 325 ran 31
Program Listing 4•8 328 range space method 149, 458, 459
Program Listing 4•9 332 range space projection 36
Program Listing 5•1 454 rank deficiency 149
Program Listing 5•19 574, 579 rank deficient 37
Program Listing 5•2 462 rank one update 134
Program Listing 5•20 579 rapid proto-typing 68, 388
Program Listing 5•22 608 Rayleigh-Ritz Method 202
Program Listing 5•23 609 Rayleigh-Ritz method 216, 242
Program Listing 5•3 466 reaction 282, 330, 393, 453
Program Listing 5•4 470 reduced gradient 137
Program Listing 5•5 476 reduced gradient method 137
Program Listing 5•6 486 reduced integration 464
Program Listing 5•8 497 reference element 267
program template 293 reference Matrix 21
programming by specification 270, 392, 579 reference matrix 454
programming paradigm 265 reference Vector 10, 12
projected gradient 151 reference-counting 6
projected Hessian 151 referential coordinate 167
projection 365 referential coordinates 267
projection operator 49 register element type 277
promotion regular increment
C0 type to H0 type 174 Subvector and Submatrix 43

632 Workbook of Applications in Vector Space C++ Library


regular increment submatrix 380 Matrix 25
relational database 271, 357 Submatrix 45
relative cost 114 Subvector 45
released intermediate configuration 603 Subvector and Submatrix 42
rep_ptr->make() 291 Vector 12
replacement mulitplication and division 17 Vector_of_Tangent_Bundle 101
residual 227 self-adjoint linear operator 201
residual norm 576 self-adjoint operato 242
residual vector 279, 573 self-adjoint operator 269
response gradient boundary integral equation 247 shape sensitivity 400
reuse of code 269 shear force 207, 305
reverse engineer 68 shear locking 397
revised simplex method 116 shear modulus 376, 577
Riemannian spatial metric tensor 600 sigular value decomposition 87
right Cauchy-Green tensor 594, 596, 597 similarity transformation 344, 458
right polar decomposition 595 simple body 593
right stretch tensor 595 simplex method 116
rogram Listing 2•9 128 simplex-tableau 116
root finding 100 Simpson’s rule 165, 255
root-finding 578, 581, 613 simultaneous linear equations 60
Rosenbrock’s function 123 singular value decomposition 37, 38, 91, 149, 277
rot 376 skew symmetric 591
row selector 25 skewness 231
row-pivoting 31 skew-symmetric 376
row-sum lumped mass matrix 367 slowness 75
row-wise concatenation operator 12, 20 smoothing 365
row-wise vector 9 Sobolev space 165
row-wise-concatenation operator 25 solenoidal 368
solution phas 283
S solvability condition 225
source term 170
s heat source vector 253
space
saddle-point problem 152, 459
C0 1
SCALAR 3
C1 95
Scalar 1, 2
C2 95
scalar inner product 16, 28
Cn 1
search direction 127, 335
spatial dimension 271
second derivative function 105
spatial elasticity tensor 602
second order differential equation 295
spatial metric tensor 597
second Piola-Kirchhoff stress tensor 599
spatial rate of deformation tensor 597
second-order condition 118
spatial strain tensor 594
second-order differential equation 202, 223
spectral norm 28, 39
second-order optimal condition 145
spectral representation 594
selector
spherical (polar) coordinates 189
column
spin tensor 591
Matrix 25
standard C++ library 5, 273
Submatrix 45
standard form 113, 120
element 274
state of an objec 270
Matrix 25
static condensation 344
Submatrix 45
static equilibrium 84
Fortran-style 25
static, linear finite element problem 281
Integrable_Vector 176
steepest descent method 127, 142
node 274
stiffness matri 253
row
stiffness matrix 336, 339

Workbook of Applications in Vector Space C++ Library 633


stored energy function 602 top-down 286
strain measure 592 toroidal free 368
strain tensor 376, 377 total displacement 576
strain-displacement matrix formulation 378 total incremental boundary condition 576
strain-displacement relations 468, 475 total incremental deviatoric strain 581
stream function 368 total incremental displacement 576, 581
stream function formulation 370 tr 375
stress rate 592 trace operator 375
stress tensor 377 Transient Problem 253, 336
stresses on Gaussian point 393 transient problem 277, 280
stress-strain path 577, 607 ~ 15
strong form 201 transpose 9
structural dynamics 336 vector 9
subdomain collocation method 268 transverse deflection 305
subdomain-collocation method 230 transverse loading 207, 305
Submatrix 1, 41 trapezoidal rule 165, 253
Submatrix of a Submatrix 45 trial hourglass mode 465
subordinate discretization 347 triangular area coordinates 267, 442
subordinate right-hand-side 347 trnaspose 15
Subvector 1, 10, 41 Truesdell rat 599
Subvector of Subvector 45 try 5
sucessive over relaxation 68 try-catch 5
sum norm 17 ts member function 270
summation convention 16 two-point tensor 593
surface and volume integration 189 type
SVD 37 C0 1
symbolic operator 5, 7 C1 95
symmetric eigenvalue problem 235 Vector_of_Tangent_Bundle 99
symmetric positive definite tensor 594 C2 95, 104
symmetrical positive definitive 458 H0 165
H1 165
H2 165
T type_list 281, 290
tangent bundle 95 type-complied langauge 40
tangent bundle algebra 96
tangent map 593
tangent moduli 574 U
tangent plane 118, 157 U_h 274, 275
tangent space 593 unconstrained optimization 95, 123
tangent stiffness matrix 279, 328, 573 unconstrained problem 153
TANGENT_BUNDLE 98 uncoupled volumetric and deviatoric responses 602
Tangent_Bundle 95 underdetermined problem 76
TANGENT_OF_TANGENT_BUNDLE 105 unimodular deviatoric tensor 611
Tangent_of_Tangent_Bundle 104 unique solution 153
Taylor expansion 100, 257, 328 unit covariance matrix 72
tensor algebra 16 unit tenso 376
tensor product 15, 20, 28, 299, 593 update procedure 609
the Jacobian 167, 169, 352, 593, 599, 608 upper triangular matrix 29, 33
The Program Listing 3•3 187 utility integrable object 181
thermal diffusivity 63, 351 utility object 1, 169
thermal diffusivity matrix 452
tie node 359 V
time domai 253 value-array
time recurrence formula 254 Matrix 18

634 Workbook of Applications in Vector Space C++ Library


variable dedicated constructor 100 weighted residual statement 306, 351, 452
Integrable_Scalar 170 weighted residuals statement 327
Tangent_of_Tangent_Bundle 105 weighted-residual method 227
Vector_of_Tangent_of_Tangent_Bundle 108 weighted-residual statement 230, 240, 269, 331, 365
variable interpolation 352 weighting function 227, 246
variables 285 Wheatstone bridge 60
variance 71, 231
variation 201, 306 X
variational formulation 201 x-hourglass mode 398
variational method 277 xmsg 5
variational methods 165, 201
variational principle 269
VECTOR 11 Y
Vector 1 Young’s Modulus 584
reference 10, 12 Young’s modulus 310, 378
vector 9
column-wise 9
row-wise 9
Vector of Tangent Bundle 99
Vector of Tangent of Tangent Bundle 108
VECTOR_OF_TANGENT_BUNDLE 102
Vector_of_Tangent_Bundle 95
VECTOR_OF_TANGENT_OF_TANGENT_BUNDLE 109
Vector_of_Tangent_of_Tangent_Bundle 104, 228
VectorSpace C++ Library 1
velocity 342
velocity potential 369
velocity potential formulation 373
virtual 270
virtual constructor 2, 3, 11
Integrable_Tangent_Bundle 184
Integrable_Tangent_of_Tangent_Bundle and
Integrable_Vector_of_Tangent_of_Tangent_Bund
le 198
Subvector and Submatrix 46
viscous damping 256
volume-preserving 596
volume-preserving incremental deformation gradient tensor 616
volume-preserving left Cauchy-Green tensor 602
volume-preserving plastic flow rule 604
volume-preserving right Cauchy Green tensor 596
volume-preserving right Cauchy-Green tensor 602
volumetric strain-displacement relation 495
von Mises yield criterion 576
vorticity 368

W
W0,2 space 165
W1,2 space 165
W2,2 space 165
weak form 201
weak formulation 233, 327
weighted residual method 253, 268

Workbook of Applications in Vector Space C++ Library 635

Você também pode gostar