5
\$\begingroup\$

As a little helper I recently had to write a code that solves the 1-D Euler equations. As it serves my purpose well I though others could make use of it as well. The homepage of the code can be found here, including a download containing the Makefile.

This version of the code is in this Github repository.

File gees.f90:

program gees
 use mod_fluxcalc
 implicit none
 real(8), dimension(:),allocatable :: p,v
 real(8), dimension(:,:), allocatable :: u,f, utild, uold
 real(8) :: temps, dt, tend, dx, odt, pi, c0
 integer :: i, nt, it, nx, io, lout
 character(len=13) :: fname
 write(*,*) 'Welcome to GEES'
 write(*,*) 'your friendly GPL Euler Equation solver'
 write(*,*) 'written 2012 by Arno Mayrhofer (www.amconception.de)'
 write(*,*)
 write(*,*) 'Number of grid points:'
 read(*,*) nx
 write(*,*) nx
 write(*,*) 'End time:'
 read(*,*) tend
 write(*,*) tend
 write(*,*) 'Time-step size'
 read(*,*) dt
 write(*,*) dt
 write(*,*) 'Output dt:'
 read(*,*) odt
 write(*,*) odt
 write(*,*) 'Speed of sound:'
 read(*,*) c0
 write(*,*) c0
 ! number of time-steps
 nt = int(tend/dt+1d-14)
 ! grid size
 dx = 1D0/real(nx,8)
 pi = acos(-1d0)
 ! list ouput index
 lout = 1
 allocate(p(-1:nx+1),v(-1:nx+1),f(nx,2),u(-1:nx+1,2), &
 utild(-1:nx+1,2), uold(-1:nx+1,2))
 ! init
 do i=1,nx-1
 u(i,1) = 1d3+cos(2*pi*real(i,8)/real(nx,8))*10d0 !rho
 u(i,2) = 0d0 !u(i,1)*0.1d0*sin(2*pi*real(i,8)/real(nx,8)) !rho*v
 enddo
 ! set p,v from u and calcuate boundary values at bdry and ghost cells
 call bcs(u,p,v,nx,c0)
 ! file output index
 io = 0
 ! simulation time
 temps = 0d0
 ! output
 write(fname,'(i5.5,a8)') io,'.out.csv'
 open(file=fname,unit=800)
 write(800,*) 'xpos,p,rho,v'
 do i=-1,nx+1
 write(800,'(4(a1,e20.10))') ' ', real(i,8)/real(nx,8),',', p(i),',', u(i,1),',', v(i)
 enddo
 close(800)
 io = io+1
 ! loop over all timesteps
 do it=1,nt
 ! list output
 if(real(nt*lout)*0.1d0.lt.real(it))then
 write(*,*) 'Calculated ', int(real(lout)*10.), '%'
 lout = lout + 1
 endif
 temps = temps + dt
 uold = u
 ! First Runge-Kutta step
 ! calculate flux at mid points using u
 call fluxcalc(u,v,f,nx,c0)
 do i=1,nx-1
 ! calc k1 = -dt/dx(f(u,i+1/2) - f(u,i-1/2))
 utild(i,:) = -dt/dx*(f(i+1,:)-f(i,:))
 ! u^n = uold^n + 1/6 k1
 u(i,:) = uold(i,:) + utild(i,:)/6d0
 ! utild = uold^n + 1/2 k_1
 utild(i,:) = uold(i,:) + utild(i,:)*0.5d0
 enddo
 ! calculate p,v + bcs for uold + 1/2 k1 = utild
 call bcs(utild,p,v,nx,c0)
 ! Second Runge-Kutta step
 ! calculate flux at mid points using utild
 call fluxcalc(utild,v,f,nx,c0)
 do i=1,nx-1
 ! calc k2 = -dt/dx(f(utild,i+1/2) - f(utild,i-1/2))
 utild(i,:) = -dt/dx*(f(i+1,:)-f(i,:))
 ! u^n = u^n + 1/6 k2
 u(i,:) = u(i,:) + utild(i,:)/6d0
 ! utild = uold^n + 1/2 k_2
 utild(i,:) = uold(i,:) + utild(i,:)*0.5d0
 enddo
 ! calculate p,v + bcs for uold + 1/2 k2 = utild
 call bcs(utild,p,v,nx,c0)
 ! Third Runge-Kutta step
 ! calculate flux at mid points using utild
 call fluxcalc(utild,v,f,nx,c0)
 do i=1,nx-1
 ! calc k3 = -dt/dx(f(utild,i+1/2) - f(utild,i-1/2))
 utild(i,:) = -dt/dx*(f(i+1,:)-f(i,:))
 ! u^n = u^n + 1/6 k3
 u(i,:) = u(i,:) + utild(i,:)/6d0
 ! utild = uold^n + k_3
 utild(i,:) = uold(i,:) + utild(i,:)
 enddo
 ! calculate p,v + bcs for uold + k3 = utild
 call bcs(utild,p,v,nx,c0)
 ! Fourth Runge-Kutta step
 ! calculate flux at mid points using utild
 call fluxcalc(utild,v,f,nx,c0)
 do i=1,nx-1
 ! calc k4 = -dt/dx(f(utild,i+1/2) - f(utild,i-1/2))
 utild(i,:) = -dt/dx*(f(i+1,:)-f(i,:))
 ! u^n = u^n + 1/6 k4
 u(i,:) = u(i,:) + utild(i,:)/6d0
 enddo
 ! calculate p,v + bcs for (uold + k1 + k2 + k3 + k4)/6 = u
 call bcs(u,p,v,nx,c0)
 ! output
 if(abs(temps-odt*real(io,8)).lt.abs(temps+dt-odt*real(io,8)).or.it.eq.nt)then
 write(fname,'(i5.5,a8)') io,'.out.csv'
 open(file=fname,unit=800)
 write(800,*) 'xpos,p,rho,v'
 do i=-1,nx+1
 write(800,'(4(a1,e20.10))') ' ', real(i,8)/real(nx,8),',', p(i),',', u(i,1),',', v(i)
 enddo
 close(800)
 io = io + 1
 endif
 enddo
end program gees

fluxcalc.f90:

module mod_fluxcalc
contains
subroutine fluxcalc(u, v, f, nx, c0)
 implicit none
 integer, intent(in) :: nx
 real(8), dimension(-1:nx+1), intent(inout) :: v
 real(8), dimension(-1:nx+1,2), intent(inout) :: u
 real(8), dimension(nx,2), intent(inout) :: f
 real(8), intent(in) :: c0
 integer :: i, j
 real(8), dimension(2) :: ur, ul
 real(8) :: a, pl, pr, nom, denom, ri, rim1
 real(8), dimension(4) :: lam
 ! calculate flux at midpoints, f(i) = f_{i-1/2}
 do i=1,nx
 do j=1,2
 ! calculate r_{i} = \frac{u_i-u_{i-1}}{u_{i+1}-u_i}
 nom = (u(i ,j)-u(i-1,j))
 denom = (u(i+1,j)-u(i ,j))
 ! make sure division by 0 does not happen
 if(abs(nom).lt.1d-14)then ! nom = 0
 nom = 0d0
 denom = 1d0
 elseif(nom.gt.1d-14.and.abs(denom).lt.1d-14)then ! nom > 0 => r = \inf
 nom = 1d14
 denom = 1d0
 elseif(nom.lt.-1d-14.and.abs(denom).lt.1d-14)then ! nom < 0 => r = 0
 nom = -1d14
 denom = 1d0
 endif
 ri = nom/denom
 ! calculate r_{i-1} = \frac{u_{i-1}-u_{i-2}}{u_i-u_{i-1}}
 nom = (u(i-1,j)-u(i-2,j))
 denom = (u(i ,j)-u(i-1,j))
 ! make sure division by 0 does not happen
 if(abs(nom).lt.1d-14)then
 nom = 0d0
 denom = 1d0
 elseif(nom.gt.1d-14.and.abs(denom).lt.1d-14)then
 nom = 1d14
 denom = 1d0
 elseif(nom.lt.-1d-14.and.abs(denom).lt.1d-14)then
 nom = -1d14
 denom = 1d0
 endif
 rim1 = nom/denom
 ! u^l_{i-1/2} = u_{i-1} + 0.5*phi(r_{i-1})*(u_i-u_{i-1})
 ul(j) = u(i-1,j)+0.5d0*phi(rim1)*(u(i ,j)-u(i-1,j))
 ! u^r_{i-1/2} = u_i + 0.5*phi(r_i)*(u_{i+1}-u_i)
 ur(j) = u(i,j) -0.5d0*phi(ri )*(u(i+1,j)-u(i ,j))
 enddo
 ! calculate eigenvalues of \frac{\partial F}{\parial u} at u_{i-1}
 lam(1) = ev(v,u,c0,i-1, 1d0,nx)
 lam(2) = ev(v,u,c0,i-1,-1d0,nx)
 ! calculate eigenvalues of \frac{\partial F}{\parial u} at u_i
 lam(3) = ev(v,u,c0,i , 1d0,nx)
 lam(4) = ev(v,u,c0,i ,-1d0,nx)
 ! max spectral radius (= max eigenvalue of dF/du) of flux Jacobians (u_i, u_{i-1})
 a = maxval(abs(lam),dim=1)
 ! calculate pressure via equation of state:
 ! p = \frac{rho_0 c0^2}{xi}*((\frac{\rho}{\rho_0})^xi-1), (xi=7, rho_0=1d3)
 pr = 1d3*c0**2/7d0*((ur(1)/1d3)**7d0-1d0)
 pl = 1d3*c0**2/7d0*((ul(1)/1d3)**7d0-1d0)
 ! calculate flux based on the Kurganov and Tadmor central scheme
 ! F_{i-1/2} = 0.5*(F(u^r_{i-1/2})+F(u^l_{i-1/2}) - a*(u^r_{i-1/2} - u^l_{i-1/2}))
 ! F_1 = rho * v = u_2
 f(i,1) = 0.5d0*(ur(2)+ul(2)-a*(ur(1)-ul(1)))
 ! F_2 = p + rho * v**2 = p + \frac{u_2**2}{u_1}
 f(i,2) = 0.5d0*(pr+ur(2)**2/ur(1)+pl+ul(2)**2/ul(1)-a*(ur(2)-ul(2)))
 enddo
end subroutine
! flux limiter
real(8) function phi(r)
 implicit none
 real(8), intent(in) :: r
 phi = 0d0
 ! ospre flux limiter phi(r) = \frac{1.5*(r^2+r)}{r^2+r+1}
 if(r.gt.0d0)then
 phi = 1.5d0*(r**2+r)/(r**2+r+1d0)
 endif
 ! van leer
! phi = (r+abs(r))/(1d0+abs(r))
end function
! eigenvalue calc
real(8) function ev(v,u,c0,i,sgn,nx)
 implicit none
 real(8), dimension(-1:nx+1), intent(inout) :: v
 real(8), dimension(-1:nx+1,2), intent(inout) :: u
 integer, intent(in) :: i, nx
 real(8), intent(in) :: sgn, c0
 ! calculate root of characteristic equation
 ! \lambda = 0.5*(3*v \pm sqrt{5v^2+4c0^2(\frac{\rho}{\rho_0})^{xi-1}})
 ev = 0.5d0*(3d0*v(i)+sgn*sqrt(5d0*v(i)**2+4d0*c0**2*(u(i,1)/1d3)**6))
 return
end function
subroutine bcs(u,p,v,nx,c0)
 implicit none
 integer, intent(in) :: nx
 real(8), intent(in) :: c0
 real(8), dimension(-1:nx+1), intent(inout) :: p,v
 real(8), dimension(-1:nx+1,2), intent(inout) :: u
 integer :: i
 ! calculate velocity and pressure
 do i=1,nx-1
 ! v = u_2 / u_1
 v(i) = u(i,2)/u(i,1)
 ! p = \frac{rho_0 c0^2}{xi}*((\frac{\rho}{\rho_0})^xi-1), (xi=7, rho_0=1d3)
 p(i) = 1d3*c0**2/7d0*((u(i,1)/1d3)**7d0-1d0)
 enddo
 ! calculate boundary conditions
 ! note: for periodic boundary conditions set 
 ! f(0) = f(nx-1), f(-1) = f(nx-2), f(nx) = f(1), f(nx+1) = f(2) \forall f
 ! for rho: d\rho/dn = 0 using second order extrapolation
 u(0,1) = (4d0*u(1,1)-u(2,1))/3d0
 u(-1,1) = u(1,1)
 u(nx,1) = (4d0*u(nx-1,1)-u(nx-2,1))/3d0
 u(nx+1,1) = u(nx-1,1)
 ! for p: dp/dn = 0 (thus can use EOS)
 p(0) = 1d3*c0**2/7d0*((u(0,1)/1d3)**7d0-1d0)
 p(-1) = 1d3*c0**2/7d0*((u(-1,1)/1d3)**7d0-1d0)
 p(nx) = 1d3*c0**2/7d0*((u(nx,1)/1d3)**7d0-1d0)
 p(nx+1) = 1d3*c0**2/7d0*((u(nx+1,1)/1d3)**7d0-1d0)
 ! for v: v = 0 using second order extrapolation
 v(0) = 0d0
 v(-1) = v(2)-3d0*v(1)
 v(nx) = 0d0
 v(nx+1) = v(nx-2)-3d0*v(nx-1)
 ! for rho*v
 u(0,2) = u(0,1)*v(0)
 u(-1,2) = u(-1,1)*v(-1)
 u(nx,2) = u(nx,1)*v(nx)
 u(nx+1,2) = u(nx+1,1)*v(nx+1)
end subroutine
end module

The code is covered in comments as it should allow the beginner to understand the inner workings fairly easily. If you have any comments on what could be done differently let me know. I hope this code is bug free, but in case there is still one in the hiding tell me and I'll fix it.

Implementation details:
Time-stepping: 4th order explicit Runge Kutta
Flux calculation: MUSCL scheme (Kurganov and Tadmor central scheme)
Flux limiter: Ospre
Boundary conditions: Second order polynomial extrapolation
Equation of state: Tait
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Mar 25, 2012 at 19:31
\$\endgroup\$
0

2 Answers 2

4
\$\begingroup\$

A few points I'd do different:

  • use the name of the procedures/modules also in their end statement
  • not writing enddo and endif in a single word, but seperately. There are newer language constructs, where writing them together is not allowed, and separating them all is more consistent
  • as mentioned in a comment above, use selected_real_kind instead of a hardwired 8
  • use>,<, == instead of .gt., .lt. and .eq.
  • provide some explanation on routines arguments and the purpose of each routine
  • indent module routines
  • use spaces around the condition of if-clauses
  • use the private statement in the module and explicitly mark visible entities with the public keyword
  • turn the initialization and the time loop into subroutines each
answered Oct 12, 2014 at 8:28
\$\endgroup\$
2
  • \$\begingroup\$ Thank you for your comments. I recently got some feedback via email and hope to incorporate also your suggestions. The code is now on github so if you want to contribute feel free to send pull requests. \$\endgroup\$ Commented Feb 4, 2015 at 1:33
  • \$\begingroup\$ Referenced your comment here: github.com/Azrael3000/gees/issues/1 and already made some progress \$\endgroup\$ Commented Feb 4, 2015 at 2:07
1
\$\begingroup\$
  1. Isolate chunks of code that are independent from the rest in separate functions.
  2. \$\pi\$ is a constant that should be declared as a parameter, where it can be initialized with a call to acos.
  3. Use more descriptive self-documenting names for your variables
  4. Do not use explicit numbers for i/o units. Use "newunit" instead
  5. dble(n) is less cluttery than real(n,8)
  6. Avoid magic numbers (e.g., "0.1": what's that?)
  7. Reduce the lifespan of variables across the code
  8. Do not repeat code
  9. Use defensive programming against invalid input

I rewrote the main following most of these criteria, except for the name of some variables, which is not clear from the context what they exactly are. Such variables are apparently fundamental but have names a single-character long (u, v, p, ...), which is far from ideal. As a result of this treatment, the complexity of the code is drastically reduced. Among the other things, the Runge-Kutta propagator could be collapsed to a single step in a loop, instead of being repeated four times, with minimal variations that are easily reproduced by appropriate initialization and exit conditions.

For folks not familiar with the simple syntax of modern Fortran, I suggest quick modern Fortran tutorial.

module mod_constants
 real(kind(1d0)), parameter :: PI = acos(-1.d0)
end module mod_constants
program gees
 use, intrinsic :: iso_fortran_env, only : OUTPUT_UNIT
 use mod_fluxcalc
 use mod_constants
 implicit none
 integer , parameter :: N_OUTPUT_LISTINGS = 10
 real(kind(1d0)), dimension(:) , allocatable :: p, v
 real(kind(1d0)), dimension(:,:), allocatable :: u, f
 real(kind(1d0)) :: Time, TimeStep, GridStepSize, SaveTimeInterval, SoundSpeed, DiscretizationSpeed
 integer :: nTimeSteps, iTime, nGridPoints, SaveTimeIterations, ListProgressIterations
 logical :: IsLastIteration, TimeToSave, ListProgress
 
 call PrintCredits()
 call FetchParameters( nGridPoints, nTimeSteps, TimeStep, SaveTimeInterval, SoundSpeed)
 
 GridStepSize = 1.d0 / dble( nGridPoints )
 DiscretizationSpeed = GridStepSize / TimeStep
 SaveTimeIterations = max( nint( SaveTimeInterval / TimeStep ), 1 )
 ListProgressIterations = max( nTimeSteps / N_OUTPUT_LISTINGS, 1 )
 
 allocate(p(-1:nGridPoints+1))
 allocate(v,source=p)
 allocate(f(nGridPoints,2))
 allocate(u(-1:nGridPoints+1,2))
 call SetInitialConditions( u, nGridPoints )
 ! set p,v from u and calcuate boundary values at bdry and ghost cells
 call bcs ( u, p, v, nGridPoints, SoundSpeed )
 call SaveResults( u, p, v, nGridPoints )
 
 Time = 0.d0
 do iTime = 1, nTimeSteps
 Time = Time + TimeStep
 call RungeKuttaStepper( u, v, f, nGridPoints, SoundSpeed, DiscretizationSpeed )
 IsLastIteration = iTime == nTimeSteps
 TimeToSave = mod( iTime, SaveTimeIterations ) == 0 
 if( TimeToSave .or. IsLastIteration ) call SaveResults( u, p, v, nGridPoints )
 ListProgress = mod( iTime, ListProgressIterations ) == 0 
 if( ListProgress ) write(OUTPUT_UNIT,"(a,i0,a)") ' Calculated ',Percentage(iTime,nTimeSteps),"%"
 enddo
contains
 pure integer function Percentage(i,n) result(iRes)
 integer, intent(in) :: i, n
 iRes = int(dble(i)/dble(n)*100.d0)
 end function Percentage
 
 subroutine PrintCredits()
 use, intrinsic :: iso_fortran_env, only : OUTPUT_UNIT
 character(len=*), parameter :: FMT="(a)"
 write(OUTPUT_UNIT,FMT) ' Welcome to GEES'
 write(OUTPUT_UNIT,FMT) ' your friendly GPL Euler Equation solver'
 write(OUTPUT_UNIT,FMT) ' written 2012 by Arno Mayrhofer (www.amconception.de)'
 write(OUTPUT_UNIT,*)
 end subroutine PrintCredits
 subroutine RequestParameters( nGridPoints, nTimeSteps, TimeStep, SaveTimeInterval, SoundSpeed )
 use, intrinsic :: iso_fortran_env, only : INPUT_UNIT, OUTPUT_UNIT
 integer , intent(out) :: nGridPoints, nTimeSteps
 real(kind(1d0)), intent(out) :: TimeStep, SaveTimeInterval, SoundSpeed
 character(len=*), parameter :: FMT="(a)"
 real(kind(1d0)) :: TimeEnd
 real(kind(1d0)), parameter :: MINIMUM_TIME_STEP = 1.d-10
 write(OUTPUT_UNIT,FMT,advance="no") ' Number of grid points:'
 read (INPUT_UNIT,*) nGridPoints
 write(OUTPUT_UNIT,FMT,advance="no") ' End time:'
 read (INPUT_UNIT,*) TimeEnd
 write(OUTPUT_UNIT,FMT,advance="no") ' Time-step size:'
 read (INPUT_UNIT,*) TimeStep
 write(OUTPUT_UNIT,FMT,advance="no") ' Output dt:'
 read (INPUT_UNIT,*) SaveTimeInterval
 write(OUTPUT_UNIT,FMT,advance="no") ' Speed of Sound:'
 read (INPUT_UNIT,*) SoundSpeed
 if( TimeStep < MINIMUM_TIME_STEP )then
 write(OUTPUT_UNIT,"(a,e14.6)") ' Time step too small. Reset to ',MINIMUM_TIME_STEP
 TimeStep = MINIMUM_TIME_STEP
 endif
 nTimeSteps = max(nint(TimeEnd/TimeStep),1)
 end subroutine RequestParameters
 subroutine SetInitialConditions( Density, n )
 use mod_constants
 real(kind(1d0)), intent(out) :: Density(:,:)
 integer , intent(in) :: n
 integer :: i
 do i = 1, nGridPoints - 1
 Density(i,1) = 1.d3 + cos( 2.d0 * PI * dble(i) / dble(nGridPoints) ) * 10d0 !rho
 Density(i,2) = 0.d0 !u(i,1)*0.1d0*sin(2*pi*real(i,8)/real(nx,8)) !rho*v
 enddo
 end subroutine SetInitialConditions
 subroutine RungeKuttaStepper( u, v, f, nGridPoints, SoundSpeed, DiscretizationSpeed )
 real(kind(1d0)), intent(inout) :: u(:,:), v(:), f(:,:)
 integer , intent(in) :: nGridPoints
 real(kind(1d0)), intent(in) :: SoundSpeed, DiscretizationSpeed
 integer , parameter :: N_RUNGE_KUTTA_STEPS = 4
 logical , save :: FIRST_CALL = .TRUE.
 real(kind(1d0)), allocatable, save :: utild(:,:), uold(:,:)
 integer :: i, iStep
 if( FIRST_CALL )then
 allocate(utild,source=u)
 allocate(uold ,source=u)
 FIRST_CALL = .FALSE.
 endif
 uold = u
 utild = u
 do iStep = 1, N_RUNGE_KUTTA_STEPS
 call fluxcalc(utild,v,f,nGridPoints,SoundSpeed)
 do i=1,nGridPoints-1
 utild(i,:) = - (f(i+1,:)-f(i,:)) / DiscretizationSpeed
 u(i,:) = u(i,:) + utild(i,:) / 6.d0
 utild(i,:) = uold(i,:) + utild(i,:) * 0.5d0
 enddo
 if( iStep == N_RUNGE_KUTTA_STEPS )exit
 call bcs(utild,p,v,nGridPoints,SoundSpeed)
 enddo
 call bcs(u,p,v,nGridPoints,SoundSpeed)
 end subroutine RungeKuttaStepper
 subroutine SaveResults( u, p, v, nGridPoints )
 implicit none
 integer , intent(in) :: nGridPoints
 real(kind(1d0)), intent(in) :: p(:), u(:,:), v(:)
 integer, save :: nCall = 0
 character(len=13) :: FileName
 integer :: uid, iostat, i
 character(len=100) :: iomsg
 write(FileName,'(i5.5,a8)') nCall,'.out.csv'
 open(newunit = uid , &
 file = FileName, &
 status ="unknown", &
 iostat = iostat , &
 iomsg = iomsg )
 if( iostat /= 0 )then
 write(ERROR_UNIT,*) trim(iomsg)
 error stop
 endif
 write(uid,*) 'xpos,p,rho,v'
 do i=-1,nGridPoints+1
 write(uid,'(4(a1,e20.10))') ' ', dble(i)/dble(nGridPoints),&
 ',', p(i),',', u(i,1),',', v(i)
 enddo
 close(uid)
 nCall = nCall + 1
 end subroutine SaveResults
end program gees
answered Jan 17, 2021 at 3:20
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.