Fortran Example – Mathieu functions and characteristics¶
Here we consider a set of functions that can not (generally) be written in closed form. The Mathieu functions, \(y(x)\), are defined as the solutions of the differential equation:
where \(a,q\in\mathbb{R}\). This looks pretty nasty! The equation is linear, but has non-constant coefficients and two parameters to account for. This equation is well understood analytically, but we’re only going to look at it computationally. In fact we can use some ideas from the third homework!
These functions arise in a few places throughout physics and engineering. Below we will cast this as an eigenvalue problem, similar to those you may find in quantum mechanics. Supposedly these functions also arise in control systems, though I have less experience there. If you would like to read more you can consult the wikipedia page, and the entry in the DLMF.
Re-casting the problem¶
When posed over the whole real line the above equation will have non-trivial solutions for all choices of \(a\) and \(q\). However, we will look for only the solutions that are \(2\pi-\) periodic. In this case we will choose \(q\) freely, and \(a\) will be fixed to a discrete (but still infinte) set of values. To make this more clear, we can re-write the above equation as an eigenvalue problem:
where we can see that \(a\) and \(y\) form an eigenvalue/eigenfunction pair for the operator in the parentheses on the left.
How can we deal with that complicated looking differential operator? We already know that computers (mostly) can not do calculus, nor can they do infinitely many things. Let’s apply the same technique as in the second homework.
First, we’ll fix \(\mathbf{x}\) to be an array of equispaced grid points on \([0,2\pi)\). Then let \(\mathbf{T}\) be the discrete Fourier transformation matrix for this grid (as defined in HW3). A discrete version of the above equation can be written as:
where \(\mathbf{V}\) is a diagonal matrix with entries \(\lbrace2q\cos(2x_1),2q\cos(2x_2),2q\cos(2x_3),\ldots\rbrace\) and \(\mathbf{D}_k\) is a diagonal matrix with the squares of the wavenumbers as entries \(\lbrace 0,1,1,4,4,9,9,\ldots\rbrace\).
In the code we’ll pack everything inside the square braces into one matrix, \(\mathbf{H}\), and use LAPACK to obtain the eigenvalue decomposition.
The code¶
The code to solve this problem is split into several files:
mathieu.f90
: This defines the main program and is responsible for calling the other modules to set things up, calling LAPACK, and writing the solutions to disc.diffop.f90
: This defines thediffop
module which is responsible for building the matrices (discrete differential operators) we need.problemsetup.f90
: This holds the problem specific parameters, like the number of grid points, the value for \(q\), and what to label the output files. This also has routines to read an input file so that we don’t need to re-compile to try different parameter values.utility.f90
: This defines useful constants like \(pi\) and the precision.
There are also some helper files to make our life easier when using the code:
Makefile
: This, of course, lets the programmake
do all of the compilation work for us.mathieu.init
: This defines the settings that get read in by the module fromproblemsetup.f90
. Changing values here allow us to examine the effect of various parameter choices without needing to re-compile each time.parStudy.sh
: This is a very simple shell script that will batch out many jobs to perform a small parameter study.plotEVals.py
andplotEFuncs.py
: These are basic python plotting scripts so that we can visualize the results from the code.
All of these can be obtained in this tarball
which can be uncompressed by calling tar -xvzf MathieuFunctions.tar.gz
.
Note
This example relies on a couple of the routines from HW3. You’ll need to insert your solutions from there for the code above to work!!
The mathieu.f90
file¶
1! File: mathieu.f90
2! Author: Ian May
3! Purpose: Compute Mathieu functions of the first kind numerically
4
5program mathieu
6 use utility, only : fp, pi
7 use problemsetup, only : Npts, qIdx, outFile, problemsetup_Init
8 use diffop, only : diffop_Mathieu
9 implicit none
10
11 ! Size of transform
12 ! Problem data
13 integer :: info,lwork ! dgeev return code and size of workspace
14 real (fp) :: dx ! grid spacing
15 real (fp), allocatable :: x(:) ! grid points
16 real (fp), allocatable :: wr(:), wi(:) ! real and imaginary parts of e-vals
17 real (fp), allocatable :: work(:) ! Workspace for dgeev
18 real (fp), allocatable :: H(:,:), vr(:,:), vl(:,:) ! operator, right e-vecs, left e-vecs
19
20 ! Loop variables
21 integer :: i,j
22
23 ! Read in the input file
24 call problemsetup_Init('mathieu.init')
25
26 ! Set local variables
27 lwork = 8*Npts
28 dx = 2*pi/Npts
29
30 ! Allocate arrays to match problem size
31 allocate(x(Npts))
32 allocate(wr(Npts))
33 allocate(wi(Npts))
34 allocate(work(lwork))
35 allocate(H(Npts,Npts))
36 allocate(vl(Npts,Npts))
37 allocate(vr(Npts,Npts))
38
39 ! Set up physical domain
40 do i=1,Npts
41 x(i) = (i-1)*dx
42 end do
43
44 ! Get Mathieu operator
45 call diffop_Mathieu(qIdx,x,H)
46
47 ! Use Lapack to find the eigenvalues/eigenvectors of H
48 call dgeev('N','V',Npts,H,Npts,wr,wi,vl,1,vr,Npts,work,lwork,info)
49
50 ! Open the output file and write the columns
51 open(20,file=outFile,status="replace")
52 do i=1,Npts
53 write(20,*) x(i), wr(i), wi(i), (vr(i,j), j=1,Npts)
54 end do
55 close(20)
56
57 ! Deallocate all used space
58 deallocate(x)
59 deallocate(wr)
60 deallocate(wi)
61 deallocate(work)
62 deallocate(H)
63 deallocate(vl)
64 deallocate(vr)
65end program mathieu
The diffop.f90
file¶
1module diffop
2
3 use utility, only : fp, pi
4
5 implicit none
6 private
7
8 public :: diffop_Mathieu
9
10contains
11
12 ! subroutine: diffop_SecondDeriv
13 ! purpose: Create second derivative operator over a given (periodic) grid
14 ! inputs: x -- Rank 1 array holding grid positions, assumed periodic and equispaced
15 ! outputs: D -- Square rank 2 array for the discrete second derivative operator
16 subroutine diffop_SecondDeriv(x,D)
17 implicit none
18 real (fp), intent(in) :: x(:)
19 real (fp), intent(in out) :: D(:,:)
20
21 ! Local variables
22 integer :: M, N, i
23 real (fp) :: om, dx, k
24 real (fp) :: T(size(D,1),size(D,2)), Tinv(size(D,1),size(D,2))
25
26 ! Set sizes and base wavenumber
27 M=size(D,1)
28 N=size(D,2)
29 dx = x(2)-x(1)
30 om = 2*pi/(N*dx)
31
32 ! Fill transformation matrices
33 ! T comes from HW3
34 ! Tinv is already contracted with the diagonal scaling
35 T(1,:) = ! Something?
36 Tinv(:,1) = 0.0_fp
37 do i=2,M,2
38 k = i*om/2
39 T(i,:) = ! Another ?
40 Tinv(:,i) = -k**2*cos(k*x)
41 if (i+1 <= M) then
42 T(i+1,:) = ! One more...
43 Tinv(:,i+1) = -k**2*sin(k*x)
44 end if
45 end do
46
47 ! Last column of T needs re-scaling for even M
48 if (mod(M,2)==0) then
49 T(M,:) = T(M,:)/2.0_fp
50 end if
51
52 ! Multiply together to get spectral differentiation matrix
53 ! Recall Tinv already has diagonal scaling in it
54 D = matmul(Tinv,T)
55 end subroutine diffop_SecondDeriv
56
57 ! subroutine: diffop_Mathieu
58 ! purpose: Create the Mathieu differential operator for a given index over a given
59 ! (periodic) grid
60 ! inputs: q -- Mathieu index
61 ! x -- Rank 1 array holding grid positions, assumed periodic and equispaced
62 ! outputs: H -- Square rank 2 array for the discrete Mathieu operator
63 subroutine diffop_Mathieu(q, x, H)
64 implicit none
65 real (fp), intent(in) :: q
66 real (fp), intent(in) :: x(:)
67 real (fp), intent(in out) :: H(:,:)
68
69 ! Local variables
70 integer :: N, j
71 real (fp) :: V(size(x))
72 N = size(x)
73
74 ! Set H to the negative second derivative operator
75 call diffop_SecondDeriv(x,H)
76 H = -1.0_fp * H
77
78 ! Add the Mathieu potential along the diagonal
79 V = 2*q*cos(2*x)
80 do j=1,N
81 H(j,j) = H(j,j) + V(j)
82 end do
83 end subroutine diffop_Mathieu
84
85end module diffop
What are these allocate
and deallocate
statements all about? Since the
size of the grid and matrices are specified inside an input file, the memory
requirements for these arrays can’t be known at compile time. The allocate
and deallocate
commands dynamically make room in memory for these arrays
at compile time. We’ll revisit this idea in the chapter on C.
The problemsetup.f90
file¶
1! File: problemsetup.f90
2! Author: Ian May
3! Purpose: Define problem specific information
4! Comments: The read_initFile* subroutines were taken from Prof. Lee's
5! Newton's method example code
6
7module problemsetup
8
9 use utility, only : fp, maxFileLen, maxStrLen
10
11 implicit none
12 private
13
14 integer, public :: Npts ! Number of grid points to use in the discretization
15 real (fp), public :: qIdx ! Index of Mathieu functions we are looking for
16 character(len=maxStrLen), public :: runName, outFile ! Name of the run and the output file the code should write
17
18 public :: problemsetup_Init
19
20contains
21
22 ! subroutine: problemsetup_Init
23 ! purpose: Set module variables to values defined in an input file
24 ! inputs: inFile -- String with name of input file to be read
25 ! outputs: <none>
26 subroutine problemsetup_Init(inFile)
27 implicit none
28 character(len=*), intent(in) :: inFile
29
30 ! Fill in default values
31 Npts = 31
32 qIdx = 36.0_fp
33
34 ! Read problem resolution
35 Npts = read_initFileInt(inFile,'num_points')
36
37 ! Read the Mathieu index
38 qIdx = read_initFileReal(inFile,'q_index')
39
40 ! Set the name of the run from the number of points and index
41 write(runName,"('Mathieu_',I0.3,'_',I0.2)") Npts,floor(qIdx)
42 print *, 'Running problem: ', runName
43
44 ! Set the output file, note that // does string concatenation
45 outFile = 'data/' // trim(runName) // '.dat'
46 end subroutine problemsetup_Init
47
48 ! function: read_initFileReal
49 ! purpose: Pull one real value from an input file
50 ! inputs: inFile -- String holding the name of the input file
51 ! varName -- String that names the variable, this must be first entry on a line
52 ! outputs: varValue -- Real value that will hold the result from the input file
53 function read_initFileReal(inFile,varName) result(varValue)
54
55 implicit none
56 character(len=*),intent(IN) :: inFile,varName
57 real (fp) :: varValue
58
59 integer :: i,openStatus,inputStatus
60 real :: simInitVars
61 character(len=maxStrLen) :: simCharVars
62 integer :: pos1,pos2
63
64 open(unit = 10, file=inFile, status='old',IOSTAT=openStatus,FORM='FORMATTED',ACTION='READ')
65
66 do i=1,maxFileLen
67 read(10, FMT = 100, IOSTAT=inputStatus) simCharVars
68 pos1 = index(simCharVars,varName)
69 pos2 = pos1 + len_trim(varName)
70 if (pos2 > len_trim(varName)) then
71 read(simCharVars(pos2 + 1:),*) simInitVars
72 varValue = simInitVars
73 endif
74 end do
75
76 close(10)
77
78100 FORMAT(A, 1X, F3.1)
79
80 end function read_initFileReal
81
82 ! function: read_initFileInt
83 ! purpose: Pull one integer value from an input file
84 ! inputs: inFile -- String holding the name of the input file
85 ! varName -- String that names the variable, this must be first entry on a line
86 ! outputs: varValue -- Integer value that will hold the result from the input file
87 function read_initFileInt(inFile,varName) result(varValue)
88
89 implicit none
90 character(len=*),intent(IN) :: inFile,varName
91 integer :: varValue
92
93 integer :: i,openStatus,inputStatus
94 integer :: simInitVars
95 character(len=maxStrLen) :: simCharVars
96 integer :: pos1,pos2
97
98 open(unit = 11, file=inFile, status='old',IOSTAT=openStatus,FORM='FORMATTED',ACTION='READ')
99
100 do i=1,maxFileLen
101 read(11, FMT = 101, IOSTAT=inputStatus) simCharVars
102 pos1 = index(simCharVars,varName)
103 pos2 = pos1+len_trim(varName)
104 if (pos2 > len_trim(varName)) then
105 read(simCharVars(pos2+1:),*)simInitVars
106 varValue = simInitVars
107 endif
108 end do
109
110 close(11)
111
112101 FORMAT(A, 1X, I5)
113
114 end function read_initFileInt
115
116end module problemsetup
The utility.f90
file¶
1! File: utility.f90
2! Author: Ian May
3! Purpose: Define useful constants
4
5module utility
6
7 implicit none
8
9 integer, parameter :: fp = selected_real_kind(15)
10 integer, parameter :: maxFileLen=50, maxStrLen=200
11 real (fp), parameter :: pi = acos(-1.0_fp)
12
13contains
14
15end module utility
The Makefile
¶
1FC = gfortran
2FFLAGS = -Wall -Wextra -Wno-surprising -fPIC -fmax-errors=1 -g -fcheck=all -fbacktrace -J ./obj
3LFLAGS = -llapack
4
5ODIR=obj
6
7_OBJ = utility.o problemsetup.o diffop.o mathieu.o
8OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
9
10mathieu.ex: $(OBJ)
11 $(FC) -o $@ $^ $(LFLAGS) $(FFLAGS)
12
13$(ODIR)/%.o: %.f90
14 @mkdir -p $(ODIR)
15 $(FC) -c -o $@ $< $(FFLAGS)
16
17.PHONY: clean
18
19clean:
20 rm -f mathieu.ex obj/* *~
Note the special flag -J ./obj
in the Fortran flags section.
This redirects all object and module files into the obj/
directory to keep
our build directory somewhat clean. You can try putting the *.f90
files
into a src/
subdirectory as well.