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:

(1)\[\frac{d^2y}{dx^2} + \left(a-2q\cos(2x)\right)y(x) = 0\]

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:

(2)\[\begin{split}\begin{cases} \left(2q\cos(2x) - \frac{d^2}{dx^2}\right)y = ay \\ y(0) = y(2\pi) \end{cases}\end{split}\]

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:

(3)\[\left[\mathbf{V} - \mathbf{T}^{-1}\mathbf{D}_k\mathbf{T}\right]\mathbf{y} = a\mathbf{y}\]

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 the diffop 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 program make do all of the compilation work for us.

  • mathieu.init: This defines the settings that get read in by the module from problemsetup.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 and plotEFuncs.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

Download this code

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

Download this code

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

Download this code

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

Download this code

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/* *~

Download this code

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.