External Libraries for Scientific Computing¶
Advantages of using external libraries¶
Very rarely will you write software in total isolation, and frequently you will want to rely on existing implementations for some components of your code. This lets you focus on the overall purpose of the code while letting others write and maintain some general purpose routines. Common examples are numerical linear algebra routines, (binary format) I/O, parallel load distribution and communication, etc.
This approach not only saves you effort and time, but also keeps your code computationally efficient and compatible across various platforms (as long as the external libraries are continuously supported and maintained).
About BLAS and LAPACK¶
In this section we learn how to install external software packages. We are going to search for them, compile them to produce libraries, then include and link those libraries so that you may call them from your code.
Two of the most popular examples of external libraries, especially for scientific computing, are BLAS (Basic Linear Algebra Subroutines) and LAPACK (Linear Algebra PACKage). Linear algebra is a fundamental building block of numerical methods, and hence these libraries are fundamental building blocks for many scientific codes.
They are both freely-available and allow users to call various linear algebra subroutines in their programs. Indeed, linear algebra powers a great deal of scientific computing.
The BLAS package is a reference Fortran library. It is a collection of fundamental linear algebra routines that comprise standard building blocks for vector and matrix operations. These basic operations have been heavily optimized and are incredibly fast. For this reason, many numerical software packages (including LAPACK, LINPACK, OCTAVE, MATLAB, Mathematica, NumPy, and R) adopt BLAS and develop their linear algebra software as BLAS-compatible libraries.
LAPACK is an improved version of LINPACK, which provides standard numerical linear algebra routines using BLAS as a building block for its implementation. That is to say, routines in LAPACK perform computations by calling the routines from BLAS.
With BLAS and LAPACK installed you will have access to all of the implemented linear algebra routines from your own code, including:
solving systems of linear equations,
solving least squares problems,
eigenvalue decompositions,
singular value decompositions,
random number generation
and more…
The naming convention for the subroutines is a little old-school. The LAPACK name conventions) page has more information on the topic.
See also:
Installing BLAS and LAPACK¶
Since LAPACK routines call the BLAS routines, you have to install the BLAS library first before installing LAPACK.
There is one important question to ask before beginning: Where do you want to install and run the packages? Do you want to install them system-wide? For only one user?
If you are a Linux or MacOS user then there is chance that these libraries are already present on your machine. Otherwise they can easily be obtained from your system’s package manager, which might look like:
$ # On Ubuntu or Debian
$ sudo apt install libblas-dev liblapack-dev
$ # Or on Arch derivatives
$ sudo pacman -S lapack blas
Remark You can often install the LAPACK documentation directly to your machine. This will provide man-pages for the LAPACK routines which can be quite helpful
If you are a Mac user, then it is quite likely that BLAS and LAPACK
are already present on your machine. Try looking in the /usr/lib
directory for entries containing lapack in their name:
$ cd /usr/lib/
$ ls -al
$ ...
$ liblapack.dylib -> ../../System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib
The ->
means that the default provided library file liblapack.dylib
in
/usr/lib/
is a symbolic link to the file called libLAPACK.dylib
buried
under that long path.
To further check if you have the BLAS and LAPACK properly installed already, download the code below:
1!! /codes/lapack/solve1.f90
2!! This routine solves Ax=b using DGESV routine in LAPACK.
3!!
4!! Recall that the naming convention follows (see http://www.netlib.org/lapack/lug/node26.html)
5!! D: double precision
6!! GE: general type of matrix
7!! SV: simple driver by factoring A and overwriting B with the solution x
8!!
9!! See also:
10!! a. https://software.intel.com/sites/products/documentation/doclib/mkl_sa/11/mkl_lapack_examples/dgesv.htm
11!! b. http://www.netlib.org/clapack/old/double/dgesv.c
12!!
13
14
15program solve1
16 implicit none
17 integer, parameter :: n=3, fp = selected_real_kind(15)
18 real (fp), dimension(n) :: x,b
19 real (fp), dimension(n,n) :: a
20 integer :: i, info, lda, ldb, nrhs
21 integer, dimension(n) :: ipiv
22
23 a = reshape((/1._fp, 2._fp, 2._fp, 0._fp, -4._fp, -6._fp, 0._fp, 0._fp, -1._fp/), (/n,n/))
24 b = (/3._fp,-6._fp,1._fp/)
25 x = b
26
27 nrhs = 1
28 lda = n
29 ldb = n
30
31 call dgesv(n, nrhs, a, lda, ipiv, x, ldb, info)
32
33 print*, 'solving Ax = b'
34 do i = 1,n
35 print *, i, x(i)
36 end do
37
38end program solve1
and compile solve1.f90
using the LAPACK library flag -llapack
(actually you can check out this flag by compiling any Fortran code
which doesn’t even call any LAPACK routines)
$ gfortran solve1.f90 -o solve1.ex -llapack
If you see a message such as
ld: library not found for -llapack
then you’ll need to install the BLAS/LAPACK on your machine.
Note
I strongly recommend installing BLAS and LAPACK through your package manager.
If you can not install BLAS and LAPACK from your package manager, then proceed below for installing from source. The ubiquity of BLAS and LAPACK make them commonly available, however other libraries that you may want to use will only be available as source. The instructions below for LAPACK illustrate a fairly common procedure for this type of installation.
BLAS installation from source:¶
Note
The current Lapack source distribution includes BLAS. This step is retained in case you need it, but most people should skip ahead to the next section.
You need to first install the BLAS library. You can download a tarball
blas-3.10.0.tgz
directly from the BLAS website.Untar it using:
$ tar -xvf blas-3.10.0.tgz
Upon untarring, you will see a BLAS source file directory.
cd
into it and verify that the contents ofmake.inc
match your system. Then compile the full library by calling$ make all
You have now created a static BLAS library that you can link to your code.
Note
If you wish to learn more about the command ar
on
Linux/Unix, please check out the following articles:
article-ar-fortran,
article-ar-C,
lib-name.a convention.
Installing LAPACK from source:¶
As before, you can download a tarball
lapack-3.10.0.tgz
from the LAPACK website.$ cd $HOME/packages $ tar -xvzf lapack-3.10.0.tar.gz
Go to the directory
$ cd lapack-3.10.0 $ cp make.inc.example make.inc
Edit
make.inc
if needed. Here you can specify what compiler to use, what flags to use, and whether or not the included BLAS library should also be built.You’re now ready to compile LAPACK by typing
$ make
or you can run make in a parallel mode
$ make -j <N>
where N = 2, 4, or any number up to the number of threads that your machine has.
Note
There is a primary Makefile
which calls make.inc
in the
same directory. You may want to take a look at the makefile to
see how it works internally.
Note
Note that -lblas
option does not look for blas.o
, but
libblas.a
for a statically linked version of the library,
or libblas.so
for a shared library.
Note
As just mentioned,
the correct way to link a static library (i.e., *.a
files) is using -l
,
but that only works if the library can be found on the search path, $PATH
.
On the other hand, you can direct the compiler to correct location by using
the -L
flag followed by the directory.
On the other hand, if you’re interested in linking a shared
library (i.e., *.so
files), you define an environment variable, LD_LIBRARY_PATH
which include the directory paths to the target shared library (see
more howto-sharedLib).
Also see an article on their differences article-shared-static.
Note
Also the order matters. The linker processes its input files in the order in which they appear on the command line – left to right. See article.
Also read:
Testing your installation¶
With these you should be able to run the codes below:
1!! /codes/lapack/solve1.f90
2!! This routine solves Ax=b using DGESV routine in LAPACK.
3!!
4!! Recall that the naming convention follows (see http://www.netlib.org/lapack/lug/node26.html)
5!! D: double precision
6!! GE: general type of matrix
7!! SV: simple driver by factoring A and overwriting B with the solution x
8!!
9!! See also:
10!! a. https://software.intel.com/sites/products/documentation/doclib/mkl_sa/11/mkl_lapack_examples/dgesv.htm
11!! b. http://www.netlib.org/clapack/old/double/dgesv.c
12!!
13
14
15program solve1
16 implicit none
17 integer, parameter :: n=3, fp = selected_real_kind(15)
18 real (fp), dimension(n) :: x,b
19 real (fp), dimension(n,n) :: a
20 integer :: i, info, lda, ldb, nrhs
21 integer, dimension(n) :: ipiv
22
23 a = reshape((/1._fp, 2._fp, 2._fp, 0._fp, -4._fp, -6._fp, 0._fp, 0._fp, -1._fp/), (/n,n/))
24 b = (/3._fp,-6._fp,1._fp/)
25 x = b
26
27 nrhs = 1
28 lda = n
29 ldb = n
30
31 call dgesv(n, nrhs, a, lda, ipiv, x, ldb, info)
32
33 print*, 'solving Ax = b'
34 do i = 1,n
35 print *, i, x(i)
36 end do
37
38end program solve1
1# codes/lapack/linear/makefile
2
3FC = gfortran
4FFLAGS = -O2 -g -Wall -Wextra
5LFLAGS = -llapack
6#LFLAGS = -L ../lapack-3.10.0 -llapack
7
8.PHONY: clean all
9
10all: solve1.ex randomsys1.ex randomsys2.ex randomsys3.ex
11
12# this is correct
13solve1.ex: solve1.o
14 $(FC) solve1.o $(LFLAGS) -o solve1.ex
15
16# this can be a bug depending on your gfortran version and your platform
17# solve1.ex: solve1.o
18# $(FC) $(LFLAGS) solve1.o -o solve1.ex
19
20randomsys1.ex: randomsys1.o init_random_seed.o
21 $(FC) randomsys1.o init_random_seed.o $(LFLAGS) -o randomsys1.ex
22
23randomsys2.ex: randomsys2.o init_random_seed.o
24 $(FC) randomsys2.o init_random_seed.o $(LFLAGS) -o randomsys2.ex
25
26randomsys3.ex: randomsys3.o init_random_seed.o
27 $(FC) randomsys3.o init_random_seed.o $(LFLAGS) -o randomsys3.ex
28
29%.o : %.f90
30 $(FC) $(FFLAGS) -c $<
31
32clean:
33 rm -f *.o *.ex
Note in the above Makefile
, it is important to include
$(LFLAGS)
after solve1.o
so that solve1.o
can refer
to the libraries. Not doing so can cause issues on some systems.
The following programs construct and solve random linear systems of various sizes.
1! Initialize the random number generator using current time,
2! so a new sequence of random numbers is generated each
3! execution time.
4
5! Taken from http://gcc.gnu.org/onlinedocs/gfortran/RANDOM_005fSEED.html
6
7SUBROUTINE init_random_seed()
8 INTEGER :: i, n, clock
9 INTEGER, DIMENSION(:), ALLOCATABLE :: seed
10
11 CALL RANDOM_SEED(size = n)
12 ALLOCATE(seed(n))
13
14 CALL SYSTEM_CLOCK(COUNT=clock)
15
16 seed = clock + 37 * (/ (i - 1, i = 1, n) /)
17 CALL RANDOM_SEED(PUT = seed)
18
19 print *, "Using random seed = ", seed
20 print *, " "
21
22 DEALLOCATE(seed)
23END SUBROUTINE init_random_seed
1! /codes/lapack/random/randomsys1.f90
2
3program randomsys1
4 implicit none
5 integer, parameter :: nmax=1000, fp = selected_real_kind(15)
6 real (fp), dimension(nmax) :: b, x
7 real (fp), dimension(nmax,nmax) :: a
8 real (fp) :: err
9 integer :: i, info, lda, ldb, nrhs, n
10 integer, dimension(nmax) :: ipiv
11
12 ! initialize random number generator seed
13 ! if you remove this, the same numbers will be generated each
14 ! time you run this code.
15 call init_random_seed()
16
17 print *, "Input n ... "
18 read *, n
19 if (n<1 .or. n>nmax) then
20 print *, "n must be positive and no greater than ",nmax
21 stop
22 endif
23 call random_number(a(1:n,1:n))
24 call random_number(x(1:n))
25 b(1:n) = matmul(a(1:n,1:n),x(1:n)) ! compute RHS
26
27 nrhs = 1 ! number of right hand sides in b
28 lda = nmax ! leading dimension of a
29 ldb = nmax ! leading dimension of b
30
31 call dgesv(n, nrhs, a, lda, ipiv, b, ldb, info)
32
33 ! Note: the solution is returned in b
34 ! and a has been changed.
35
36 ! compare computed solution to original x:
37 print *, " x computed rel. error"
38 do i=1,n
39 err = abs(x(i)-b(i))/abs(x(i))
40 print "(ES12.6,' | ',ES12.6,' | ',ES12.6)", x(i),b(i),err
41 end do
42
43end program randomsys1
1! /codes/lapack/random/randomsys2.f90
2
3program randomsys2
4 implicit none
5 integer, parameter :: fp = selected_real_kind(15)
6 real (fp), dimension(:),allocatable :: x,b
7 real (fp), dimension(:,:),allocatable :: a
8 real (fp) :: err
9 integer :: i, info, lda, ldb, nrhs, n
10 integer, dimension(:), allocatable :: ipiv
11
12 ! initialize random number generator seed
13 ! if you remove this, the same numbers will be generated each
14 ! time you run this code.
15 call init_random_seed()
16
17 print *, "Input n ... "
18 read *, n
19
20 allocate(a(n,n))
21 allocate(b(n))
22 allocate(x(n))
23 allocate(ipiv(n))
24
25 call random_number(a)
26 call random_number(x)
27 b = matmul(a,x) ! compute RHS
28
29 nrhs = 1 ! number of right hand sides in b
30 lda = n ! leading dimension of a
31 ldb = n ! leading dimension of b
32
33 call dgesv(n, nrhs, a, lda, ipiv, b, ldb, info)
34
35 ! Note: the solution is returned in b
36 ! and a has been changed.
37
38 ! compare computed solution to original x:
39 print *, " x computed rel. error"
40 do i=1,n
41 err = abs(x(i)-b(i))/abs(x(i))
42 print "(ES12.6,' | ',ES12.6,' | ',ES12.6)", x(i),b(i),err
43 end do
44
45 deallocate(a,b,ipiv)
46
47end program randomsys2
1! /codes/lapack/random/randomsys3.f90
2
3program randomsys3
4 implicit none
5 integer, parameter :: fp = selected_real_kind(15)
6 real (fp), dimension(:),allocatable :: x,b,work
7 real (fp), dimension(:,:),allocatable :: a
8 real (fp) :: errnorm, xnorm, rcond, anorm, colsum
9 integer :: i, info, lda, ldb, nrhs, n, j
10 integer, dimension(:), allocatable :: ipiv
11 integer, allocatable, dimension(:) :: iwork
12 character, dimension(1) :: norm
13
14 ! initialize random number generator seed
15 ! if you remove this, the same numbers will be generated each
16 ! time you run this code.
17 call init_random_seed()
18
19 print *, "Input n ... "
20 read *, n
21
22 allocate(a(n,n))
23 allocate(b(n))
24 allocate(x(n))
25 allocate(ipiv(n))
26
27 call random_number(a)
28 call random_number(x)
29
30 b = matmul(a,x) ! compute RHS
31
32 ! compute 1-norm needed for condition number
33
34 anorm = 0.d0
35 do j=1,n
36 colsum = 0.d0
37 do i=1,n
38 colsum = colsum + abs(a(i,j))
39 enddo
40 anorm = max(anorm, colsum)
41 enddo
42
43
44 nrhs = 1 ! number of right hand sides in b
45 lda = n ! leading dimension of a
46 ldb = n ! leading dimension of b
47
48 call dgesv(n, nrhs, a, lda, ipiv, b, ldb, info)
49
50 ! compute 1-norm of error
51 errnorm = 0.d0
52 xnorm = 0.d0
53 do i=1,n
54 errnorm = errnorm + abs(x(i)-b(i))
55 xnorm = xnorm + abs(x(i))
56 enddo
57
58 ! relative error in 1-norm:
59 errnorm = errnorm / xnorm
60
61
62 ! compute condition number of matrix:
63 ! note: uses A returned from dgesv with L,U factors:
64
65 allocate(work(4*n))
66 allocate(iwork(n))
67 norm = '1' ! use 1-norm
68 call dgecon(norm,n,a,lda,anorm,rcond,work,iwork,info)
69
70 if (info /= 0) then
71 print *, "*** Error in dgecon: info = ",info
72 endif
73
74 print 201, n, 1.d0/rcond, errnorm
75201 format("For n = ",i4," the approx. condition number is ",ES10.3,/,&
76 " and the relative error in 1-norm is ",ES10.3)
77
78 deallocate(a,b,ipiv)
79 deallocate(work,iwork)
80
81end program randomsys3
Some more references: