Abstract
CFD Julia is a programming module developed for senior undergraduate or graduate-level coursework which teaches the foundations of computational fluid dynamics (CFD). The module comprises several programs written in general-purpose programming language Julia designed for high-performance numerical analysis and computational science. The paper explains various concepts related to spatial and temporal discretization, explicit and implicit numerical schemes, multi-step numerical schemes, higher-order shock-capturing numerical methods, and iterative solvers in CFD. These concepts are illustrated using the linear convection equation, the inviscid Burgers equation, and the two-dimensional Poisson equation. The paper covers finite difference implementation for equations in both conservative and non-conservative form. The paper also includes the development of one-dimensional solver for Euler equations and demonstrate it for the Sod shock tube problem. We show the application of finite difference schemes for developing two-dimensional incompressible Navier-Stokes solvers with different boundary conditions applied to the lid-driven cavity and vortex-merger problems. At the end of this paper, we develop hybrid Arakawa-spectral solver and pseudo-spectral solver for two-dimensional incompressible Navier-Stokes equations. Additionally, we compare the computational performance of these minimalist fashion Navier-Stokes solvers written in Julia and Python.
1. Introduction
Coursework in computational fluid dynamics (CFD) usually starts with an introduction to discretization techniques of partial differential equations (PDEs), stability analysis of numerical methods, the order of convergence, iterative methods for elliptic equations, shock-capturing methods for compressible flows, and development of incompressible flow solver. A lot of emphases is put on the theory of discretization, stability analysis, and different formulations of governing equations for compressible and incompressible flows. Students can gain a better understanding of CFD subject with actual hands-on programming of numerical solutions to mathematical models that present different fluid behavior. To develop a flow solver from scratch is a fairly complex task. Therefore, almost all developmental CFD courses begin with one-dimensional problems to explain theory and fundamentals and then build on to complicated problems like compressible or incompressible flow solvers.
CFD Julia is a programming module that contains several codes for problems ranging from one-dimensional heat equation to two-dimensional Navier-Stokes incompressible flow solver. Some of these problems can be included as part of programming assignments or coursework projects. We include different types of problems, different techniques to solve the same problem and try to introduce CFD using a practical approach. There are a couple of programming modules available online related to CFD. CFD Python learning module is a set of Jupyter notebooks that tries to teach CFD in twelve steps [1]. This module also contains bonus modules for numerical stability analysis, and advanced programming in Python using Numpy [2]. It starts with an introduction to Python and ends with the development of incompressible flow solver for lid-driven cavity problem and channel flow. There is an online module available for a course on numerical methods covering finite difference methods [3]. The material for this coursework was created using IPython notebooks. HyperPython is a set of IPython notebooks created to teach hyperbolic conservation laws [4]. HyperPython module covers high-resolution methods for compressible flows and also teaches how one can use the PyClaw package to solve compressible flow problems. PyClaw is a hyperbolic PDE solver in 1D, 2D, and 3D and it has several features like parallel implementation and choice of higher-order accurate numerical methods [5].
In this work, we use Julia programming language [6] as a tool to develop codes for fundamental CFD problems. Julia is a new programming language which first appeared in the year 2012 and it has gained popularity in the scientific community in recent years. There is a number of programming languages that are already available such as Fortran [7], Python [8], C/C++ [9], Matlab [10], etc. Many of the state of the art CFD codes are written in Fortran and C/C++ because of their high-performance characteristics. Python and Matlab programming languages have simple syntax and students usually use these languages with ease in their coursework. Since the developer’s language is different from the user’s language, there will always be a hindrance to developing numerical codes. Many times a user has to resort to developer’s language like C/C++ for getting the fast performance or have to use other packages to exchange information between codes written in different languages. Julia tries to solve this two-language problem. Julia language was designed to have the syntax that is easy to understand and will also have fast computational performance. Julia shows that one can have machine performance without sacrificing human convenience. Julia tries to achieve the Fortran performance through the automatic translation of formulas into efficient executable code. It allows the user to write clear, high-level, generic code that resembles mathematical formulas. Julia’s ability to combine high-performance with productivity makes it the perfect choice for scientists working in different scientific domains.
In this paper, we use finite difference discretization for different types of PDEs encountered in CFD. For all problems, we provide the main script in Julia so that reader will get familiar with the Julia syntax. We start with the heat equation as the prototype of parabolic PDE. We present an explicit forward in time numerical scheme and implicit Crank-Nicolson numerical scheme for the heat equation. We also cover the multistage Runge-Kutta numerical approach to show how we can get higher temporal accuracy by taking multiple steps between two time intervals. We derive an implicit compact Pade scheme for the heat equation. This will give the reader an understanding of high-resolution numerical methods which are widely used in the direct numerical simulation (DNS) and large eddy simulation (LES). All fundamental concepts related to finite difference numerical methods are covered in Section 2 with the help of heat equation. This will lay the foundation of CFD and will familiarize the reader with basic concepts of discretization methods which will be used further for more complex problems.
We then present the hyperbolic equation in Section 3 using the inviscid Burgers equation. Hyperbolic equations allow having a discontinuous solution. Therefore, higher-order numerical methods have been developed that can capture discontinuities such as shocks. We present weighted essentially non-oscillatory (WENO) formulation for finite difference schemes and show their capability to capture shock using one-dimensional case. We show the mathematical formulation of WENO schemes, their implementation in Julia for two different boundary conditions. We use Dirichlet boundary condition and periodic boundary condition for the inviscid Burgers equation. This will help the reader to understand how to treat different boundary conditions in CFD problems. We also present a compact reconstruction of WENO scheme which has better accuracy and resolution characteristic than the WENO scheme with the price of solving a tridigonal system. Furthermore, we present discretization of the inviscid Burgers equation in its conservative form using different finite difference grid arrangement. This type of formulation is more applicable to finite volume method. We present a method of flux splitting and constructing flux at the interface using a Riemann solver approach in Section 4. We use a Riemann solver approach to solve the one-dimensional Euler equation and is presented in Section 5. The Euler equation solver is demonstrated for Sod shock tube problem using three different Riemann solvers: Rusanov scheme, Roe scheme, and Harten-Lax-van Leer Contact (HLLC) scheme.
We present different methods for solving elliptic partial differential equations using the Poisson equation as an example in Section 6. The Poisson equation is a boundary value problem and is encountered in solution to incompressible flow problems. We show different methods for solving the Poisson equation and their implementation in Julia. We demonstrate direct solver using fast Fourier transform (FFT) for periodic boundary condition problem and fast sine transform (FST) for Dirichlet boundary condition. We also present different iterative methods for solving elliptic equations. We demonstrate the implementation of Gauss-Seidel iterative method and the conjugate gradient method in Julia. The readers will learn the application of both stationary and non-stationary iterative methods. We also show the implementation of a V-cycle multigrid solver to accelerate the convergence of iterative methods.
We show solution to incompressible Navier-Stokes equation with vorticity-streamfunction formulation in Section 7. This is one of the simplest formulations to Navier-Stokes equation as there is no coupling between the velocity and pressure. This allows us to use collocated grid and not a staggered grid for the Navier-Stokes equation. We present a periodic boundary condition case using the vortex-merger problem. The Dirichlet boundary condition case is demonstrated using the lid-driven cavity problem. We use the direct solver to solve the elliptic equation giving the relation between vorticity and streamfunction. However, any of the iterative methods can also be used. We use second-order Arakawa numerical scheme which has better energy conservation properties for the discretization of the Jacobian term. The reader will get familiar with different types of numerical methods apart from standard finite difference schemes with these examples. This type of formulation can also be used for different types of problems such as two-dimensional decaying turbulence, and Taylor green vortex problem. Additionally, we present hybrid Arakawa-spectral solver for two-dimensional incompresible Navier-Stokes equations in Section 8. The flow solver is hybridized using explicit and implicit scheme for time integration. Also, the Navier-Stokes equations are solved in its Fourier space with the nonlinear term computed in physical space using Arakawa finite difference scheme and converted to Fourier space. Section 9 details the implementation fully pseudo-spectral solver for two-dimensional Navier-Stokes equations. We show two approaches to reduce aliasing error: 3/2 padding and 2/3 padding rules. We also show the comparison between CPU time for codes written in Julia and Python for solving the Navier-Stokes equations.
CFD Julia module covers several topics pertinent to both compressible and incompressible flows. This module provides different finite difference codes that will help students learn not just the fundamentals of CFD but also the implementation of numerical methods to solve CFD problems. Using these examples, students can go about developing their solvers for more challenging problems for their coursework or research. We make all these codes available on Github and are accessible to everyone (https://github.com/surajp92/CFD_Julia). Table 1 outlines all codes available in the CFD Julia module with their Github index. Table 1 presents a summary of topics covered and numerical schemes employed for problems included in the CFD Julia module. We write results for all problems into a text file and then use separate plotting scripts to plot results. We provide installation instruction for Julia and required packages, and plotting scripts in Appendix A and Appendix B.
Table 1.
Summary of different problems included in the CFD Julia module and the description of topics covered in these selected problems. Source codes are accessible to everyone at https://github.com/surajp92/CFD_Julia.
2. Heat Equation
We start with the one-dimensional heat equation, which is one of the simplest and most basic models to introduce the concept of finite difference methods. The one-dimensional heat equation is given as
where u is the field variable, t is the time variable, and is the diffusivity of the medium. The heat equation describes the evolution of the field variable over time in a medium.
There are different methods to discretize the heat equation, such as, finite volume method [11], finite difference method [12], finite element method [13], spectral methods [14], etc. In this paper, we will study the finite difference method to discretize all partial differential equations (PDEs). The finite difference method is comparatively simpler and is easy to understand in the initial stage of learning CFD. The finite difference method converts the linear or nonlinear PDEs into a set of linear or nonlinear ordinary differential equations (ODEs). These ODEs can be solved using matrix algebra techniques.
Before starting with the derivation of finite difference scheme for the heat equation, we define a grid of points in the plane. Let and be the grid spacing in space and time direction, respectively. Therefore, for arbitrary integers n and i as shown in Figure 1. We use the notation for .
Figure 1.
Finite difference grid for one-dimensional problems.
The finite difference method uses Taylor series expansion to derive the discrete approximation for PDEs. Taylor series gives the series expansion of a function f about a point. Taylor series expansion for a function f about the point is given by:
where denotes the factorial of n, and is a truncation term, giving the difference between the Taylor series expansion of degree n and the original function. The difference between the exact solution to the original differential equation and finite difference approximation is termed as the discretization or truncation error. The leading term in the discretization error determines the accuracy of the finite difference scheme. The finite difference scheme for any PDE must have two properties: consistency and stability to ensure the convergence. We would like to suggest a textbook “Finite difference schemes and partial differential equations” [15] which explains numerical behavior of finite difference scheme in detail. In this paper, we focus on the application of the finite difference method to study problems encountered in CFD.
2.1. Forward Time Central Space (FTCS) Scheme
We derive the forward time central space (FTCS) numerical scheme for approximating the heat equation. Using the Taylor series expansion in time for the discrete field , we get
Equation (4) is the first-order accurate expression for the time derivative term in the heat equation. To obtain the finite difference formula for second-derivative term in the heat equation, we write the Taylor series expansion for and about
Adding Equations (5) and (6) and dividing by , we get the second-order accurate formula for second-derivative term in heat equation
Substituting Equations (4) and (7) in Equation (1), we get the FTCS numerical scheme for the heat equation as given below
and we can re-write Equation (8) as an explicit update formula
We denote the above formula as formula meaning that the leading term of the discretization error is first-order accurate in time and second-order accurate in space. We can obtain the higher-order formula using the larger stencil to get better approximations. If the initial solution to the heat equation is given, then we can proceed in time to get the solution at future times using Equation (9). We can observe from Equation (9), that the solution at the next time step depends only on the solution at previous time steps. These numerical schemes are called an explicit numerical scheme. They are simple to code. However, explicit numerical schemes are restricted by some stability criteria. The stability criteria restricts the maximum step size we can use in spatial and temporal direction and hence explicit numerical schemes are computationally expensive.
We will now demonstrate how one can go about finding the solution at the final time step given an initial condition. We use the computational domain and . The initial condition for our problem is . We use and for spatial and temporal discretization. First, we initialize the array u and assign each element of the array using an initial condition. The solution after one time step is calculated using Equation (9) and we will store the solution in first column of matrix un[k,i] (variable in Julia) with the shape , where is the number of divisions of the domain and is the number of time steps between initial and final time. There is no need to store the solution at every time step, and we can just store the solution at intermediate time steps in a temporary array. Here, we store the solution at every time step to see how the field u evolves. The main script of the code which computes and stores the numerical solution at every time step is given in Listing 1.
We compare the solution at final time using the analytical solution to the one-dimensional heat equation. The analytical solution for Equation (1) is given by
We also compute the absolute error at using the below formula
| Listing 1. Implementation of FTCS numerical scheme for heat equation in Julia. |
![]() |
The comparison of the exact and numerical solution using the FTCS scheme and absolute error plot are shown in Figure 2. We see that we get a very good agreement between the exact and numerical solution due to very small grid size in both space and temporal direction. If we use a larger grid size, then we will see deprecation in accuracy.
Figure 2.
Comparison of exact solution and numerical solution computed using FTCS numerical scheme. The numerical solution is computed using and .
2.2. Runge-Kutta Numerical Scheme
We saw in Section 2.1 that the accuracy of the numerical scheme depends upon the number of terms included from the Taylor series expansion. One of the ways to increase accuracy is to include more terms. The additional term involves partial derivative of which provides additional information on f at . For the time stepping, we only have information about the data at one or more time steps and the analytical form of f is not known between two time steps. Runge-Kutta methods tries to improve the accuracy of temporal term by evaluating f at intermediate points between and . The additional steps lead to an increase in computational time, but the temporal accuracy is increased.
We use a third-order accurate Runge-Kutta scheme [16] for the discretization of the temporal term in the heat equation. We use the same second-order central difference scheme for the spatial term. The truncation error of this numerical approximation of heat equation is . We move from time step to using three steps. The time integration of the heat equation using third-order Runge-Kutta scheme is given below:
We can also construct the higher-order Runge-Kutta numerical scheme using more intermediate steps. The implementation of the third-order Runge-Kutta numerical scheme in Julia is given in Listing 2. The comparison of the exact and numerical solutions using the Runge-Kutta scheme and absolute error plot is shown in Figure 3. The discretization error is less for Runge-Kutta numerical scheme compared to the FTCS numerical scheme due to a higher-order approximation of temporal term.
Figure 3.
Comparison of exact solution and numerical solution computed using third-order Runge-Kutta numerical scheme. The numerical solution is computed using and .
| Listing 2. Implementation of third-order Runge-Kutta numerical scheme for heat equation in Julia. |
![]() |
2.3. Crank-Nicolson Scheme
The Crank-Nicolson scheme is a second-order accurate in time numerical scheme. The Crank-Nicolson scheme is derived by combining forward in time method at and the backward in time method at . Similar to Equation (3), we can write the Taylor series in time for the discrete field as
The second-derivative term in heat equation at time step can be approximated using the second-order central difference scheme similar to the way we derived in Section 2.1
Substituting finite difference approximation derived in Equations (16) and (17) in the heat equation, we get
If we add Equations (8) and (18), the leading term in discretization error cancels each other due to their opposite sign and same magnitude. Hence, we get the second-order accurate in time numerical scheme. The Crank-Nicolson scheme is numerical scheme. The Crank-Nicolson scheme is given as
We observe from the above equation that the solution at time step depends on the solution at previous time step and the solution at time step. Such numerical schemes are called implicit numerical schemes. For implicit numerical schemes, the system of algebraic equations has to be solved by inverting the matrix. For one-dimensional problems, the tridiagonal matrix is formed and can be solved efficiently using the Thomas algorithm, which gives a fast direct solution as opposed to the usual for a full matrix, where N is the size of the square tridiagonal matrix. The main advantage of an implicit numerical scheme is that they are unconditionally stable. This allows the use of larger time step and grid size without affecting the convergence of the numerical scheme. An illustration of a tridiagonal system is given in Equation (20). The Thomas algorithm [17] used for solving the tridiagonal matrix is outlined in Algorithm 1.
| Algorithm 1 Thomas algorithm | |
| 1: Given | ▹ |
| 2: Allocate | ▹ Storage of superdiagonal array |
| 3: | |
| 4: for to N do | ▹ Forward elimination |
| 5: | |
| 6: | |
| 7: | |
| 8: end for | |
| 9: for to 1 do | ▹ Backward substitution |
| 10: | |
| 11:end for | |
The implementation of the Crank-Nicolson numerical scheme for the heat equation is presented in Listing 3 and Thomas algorithm in Listing 4. The comparison of exact and numerical solution computed using the Crank-Nicolson scheme, and absolute error plot are displayed in Figure 4. The absolute error between the exact and numerical solution is less for the Crank-Nicolson scheme than the FTCS numerical scheme due to smaller truncation error.
Figure 4.
Comparison of exact solution and numerical solution computed using Crank-Nicolson numerical scheme. The numerical solution is computed using and .
| Listing 3. Implementation of Crank-Nicolson numerical scheme for heat equation in Julia. |
![]() |
| Listing 4. Implementation of Thomas algorithm to solve tridiagonal system of equations in Julia. |
![]() |
2.4. Implicit Compact Pade (ICP) Scheme
We usually use the order of accuracy as an indicator of the ability of finite difference schemes to approximate the exact solution as it tells us how the discretization error will reduce with mesh refinement. Another way of measuring the order of accuracy is the modified wavenumber approach. In this approach, we see how much the modified wave number is different from the true wave number. The solution of a nonlinear partial differential equation usually contains several frequencies and the modified wavenumber approach provides a way to assess how well the different components of the solution are represented. The modified wavenumber varies for every finite difference scheme and can be found using Fourier analysis of the differencing scheme [12].
Compact finite difference schemes have very good resolution characteristics and can be used for capturing high-frequency waves [18]. In compact formulation, we express the derivative of a function as a linear combination of values of function defined on a set of nodes. The compact method tries to mimic global dependence and hence has good resolution characteristics. Lele [18] investigated the behavior of compact finite difference schemes for approximating a first and second derivative. The fourth-order accurate approximation to the second derivative is given by
Taking the linear combination of heat equation and using the same coefficients as the above equation, we get
We then use Crank-Nicolson numerical scheme for time discretization and we arrive to below equation
Simplifying above equation, we get the implicit compact Pade (ICP) scheme for heat equation
where
The ICP numerical scheme forms the tridiagonal matrix which can be solved using the Thomas algorithm to get the solution. The implementation of the ICP numerical scheme in Julia is given in Listing 5. Figure 5 shows the comparison of the exact and numerical solution and discretization error for ICP scheme. The ICP scheme is accurate scheme and hence, the discretization error is very small.
Figure 5.
Comparison of exact solution and numerical solution computed using implicit compact Pade numerical scheme. The numerical solution is computed using and .
| Listing 5. Implementation of implicit compact scheme Pade numerical scheme for heat equation in Julia. |
![]() ![]() |
3. Inviscid Burgers Equation: Non-Conservative Form
In this section, we discuss the solution of the inviscid Burgers equation which is a nonlinear hyperbolic partial differential equation. The hyperbolic equations admit discontinuities, and the numerical schemes used for solving hyperbolic PDEs need to be higher-order accurate for smooth solutions, and non-oscillatory for discontinuous solutions [19]. The inviscid Burgers equation is given below
First we use FTCS numerical scheme for discretization of the inviscid Burgers equation as follow
We use the shock generated by a sine wave to demonstrate the capability of FTCS numerical scheme to compute the numerical solution. The initial condition is . We integrate the solution from time to with and divide the computational domain into 200 grids. The numerical solution computed by FTCS at 10 equally spaced time steps between initial and final time is shown in Figure 6. It can be observed that the FTCS scheme does not capture the shock formed at and produce oscillations in the solution. It can be demonstrated that FTCS scheme is numerically unstable for advection equations.
Figure 6.
Evolution of shock with time for the initial condition using FTCS scheme.
There are several ways in which the discontinuity in the solution (e.g., shock) can be handled. There are classical shock-capturing methods like MacCormack method, Lax-Wanderoff method, and Beam-Warming method as well as flux limiting methods [20,21]. We can also use higher-order central difference schemes with artificial dissipation [22]. Low pass filtering might also help to diminish the growth of the instability modes [23]. There is also a class of modern shock capturing methods called as high-resolution schemes. These high-resolution schemes are generally upwind biased and take the direction of the flow into account. Here, in particular, we discuss higher-order weighted essentially non-oscillatory (WENO) schemes [24] that are used widely as shock-capturing numerical methods. We also use compact reconstruction weighted essentially non-oscillatory (CRWENO) schemes [25] which have lower truncation error compared to WENO schemes.
3.1. WENO-5 Scheme
We refer readers to a text by Shu [26] for a comprehensive overview of the development of WENO schemes, their mathematical formulation, and the application of WENO schemes for convection dominated problems. We will use WENO-5 scheme for one-dimensional inviscid Burgers equation with the shock developed by a sine wave. We will discuss how one can go about applying WENO reconstruction for finite difference schemes. The finite difference approximation to the inviscid Burgers equation in non-conserved form can be written as
which can be combined into a more compact upwind/downwind notation as follows
where we define and . the reconstruction of uses a biased stencil with one more point to the left, and that for uses a biased stencil with one more point to the right. The stencil used for reconstruction of left and right side state (i.e., and ) is shown in Figure 7. The WENO-5 reconstruction for left and right side reconstructed values is given as [24]
where nonlinear weights are defined as
in which the smoothness indicators are defined as
Figure 7.
Finite difference grid for one-dimensional problems where the the solution is stored at the interface of the cells. The first and last points lie on the left and right boundary of the domain. The stencil required for reconstruction of left and right side state is highlighted with blue rectangle. Ghost points are shown by red color.
We use , , and in Equation (36) to compute nonlinear weights for calculation of left side state using Equation (34) and , , and in Equation (37) to compute nonlinear weights for calculation of right side state using Equation (35). We set to avoid division by zero. To avoid repetition, we provide only the implementation of Julia function to compute the left side state in Listing 6. The right side state is computed in a similar way with coefficients for in Equation (37).
We perform the time integration using third-order Runge-Kutta numerical scheme as discussed in Section 2.2 and its implementation is similar to Listing 2. The right hand side term of the inviscid Burgers equation is computed using the point value and the derivative term is evaluated using the left and right side states at the interface. Based on the direction of flow i.e., the sign of , we either use left side state or right side state to compute the derivative. The implementation of right hand side state calculation in Julia for the inviscid Burgers equation is given in Listing 7.
| Listing 6. Implementation of left side state calculation function in Julia. |
![]() ![]() |
| Listing 7. Implementation of right hand side calculation for the inviscid Burgers equation in Julia. |
![]() |
We demonstrate two types of boundary conditions for the inviscid Burgers equation. The first one is the Dirichlet boundary condition. For Dirichlet boundary condition, we use and . For computing numerical state at the interface, we need the information of five neighboring grid points (see Figure 7). When we compute the left side state at interface, we use three points on left side (i.e., ) and two points on right side (i.e., ). Therefore, we use two ghost points on the left side of the domain and one ghost point on right side of the domain for computing left side state (i.e., at and respectively). Similarly, we use one ghost points on the left side of the domain and two ghost point on right side of the domain for computing right side state (i.e., at and respectively). We use linear interpolation to compute the value of discrete field u at ghost points. The computation of u at ghost points is given below
where u is stored from 1 to from left () to right boundary () on the computational domain respectively (The default indexing in Julia for an Array starts with 1). The Julia function to select the stencil for left side state calculation is given in Listing 8. A similar function is also used for selecting the stencil for right side state calculation.
| Listing 8. Implementation of Julia function to select the stencil for computing left side numerical state at the interface for Dirichlet boundary condition. |
![]() |
We also use the periodic boundary condition for the same problem. For periodic boundary condition, we do not need to use any interpolation formula to compute the variable at ghost points. The periodic boundary condition for two left and right side points outside the domain is given below
where u is stored from 1 to from left () to right () boundary on the computational domain, respectively.
The evolution of shock generated from sine wave with time is shown in Figure 8 for both Dirichlet and periodic boundary conditions. We use the domain between and divide it into 200 grids. We use the time step and integrate the solution from to . We save snapshots of the solution field at different time steps to see the evolution of the shock. It can be seen from Figure 8 that the WENO-5 scheme is able to capture the shock formed at at final time .
Figure 8.
Evolution of shock with time for the initial condition using WENO-5 scheme.
3.2. Compact Reconstruction WENO-5 Scheme
The main drawback of the WENO-5 scheme is that we have to increase the stencil size to get more accuracy. Compact reconstruction of WENO-5 scheme has been developed that uses smaller stencil without reducing the accuracy of the solution [25]. CRWENO-5 scheme uses compact stencils as their basis to calculate the left and right side state at the interface. The procedure for CRWENO-5 is similar to the WENO-5 scheme. However, its candidate stencils are implicit and hence smaller stencils can be used to get the same accuracy. The implicit system used to compute the left and right side state is given below
where the nonlinear weights are calculated using Equations (36) and (37), in which the linear weights are given by , , and in Equation (36), and , , and in Equation (37). The smoothness indicators are computed using Equations (38)–(40). The tridiagonal system formed using Equations (44) and (45) can be solved using the Thomas algorithm with complexity, where N is the total number of grid points. In our implementation of CRWENO-5 scheme, we write a function that takes u in the candidate stencil (i.e., ) and computes all coefficients in Equations (44) and (45). The implementation of Julia function to compute the coefficients of tridiagonal system for reconstruction of left side state is given in Listing 9. A similar function can be implemented for right side state also.
| Listing 9. Implementation of Julia function to compute coefficients used for forming the implicit system in computation of left side numerical state at the interface. |
![]() ![]() |
We demonstrate CRWENO-5 scheme for both Dirichlet and periodic boundary condition. The Julia function to form the tridiagonal system for computing left side state at the interface in CRWENO-5 is given in Listing 10. We use a similar function for computing right side state.
| Listing 10. Implementation of Julia function to form the tridiagonal system for computing left side numerical state at the interface for periodic boundary condition. |
![]() ![]() |
For periodic boundary condition, we use the relation given by Equation (43) for ghost points. The tridiagonal system formed for the periodic boundary condition is not purely tridiagonal and it has the following form
where is the tridiagonal system, is the solution field and is the right hand side of the tridiagonal system. We use Sherman-Morrison formula [17] to solve the periodic tridiagonal system given in Equation (46). The Sherman-Morrison formula can be used to solve a sparse linear system of the equation if there is a faster way of calculating , where is the modified matrix without and . We can compute the using the regular Thomas algorithm explained in Section 2.3. We can write Equation (46) as given below
where
where is arbitrary and the matrix is the tridiagonal part of the matrix , and ⊗ represents the outer product of two vectors. Then the solution to Equation (46) can be obtained using below formulas
The cyclic Thomas algorithm for solving the periodic tridiagonal system is given in Algorithm 2.
| Algorithm 2 Cyclic Thomas algorithm | |
| 1: Given | ▹ |
| 2: | ▹ Avoid subtraction error in forming |
| 3: , | ▹ Set up diagonal of the modified tridiagonal system |
| 4: for to do | |
| 5: | |
| 6: end for | |
| 7: Thomas() | ▹ Solve |
| 8: , | ▹ Set up the vector |
| 9: for to do | |
| 10: | |
| 11: end for | |
| 12: Thomas() | ▹ Solve |
| 13: f = | ▹ Form |
| 14: for to N do | |
| 15: | ▹ Calculate the solution vector |
| 16: end for | |
Once the left and right side states () are available we compute the right hand side term of the Burgers equation using Listing 7. We use third-order Runge-Kutta numerical scheme for time integration. We solve the tridiagonal system formed with periodic boundary condition using cyclic Thomas algorithm and for Dirichlet boundary condition, we use regular Thomas algorithm. The implementation of cyclic Thomas algorithm in Julia is given in Listing 11. The implementation of regular Thomas algorithm is given in Listing 4.
| Listing 11. Implementation of cyclic Thomas algorithm in Julia for periodic boundary condition domain. |
![]() ![]() |
We test the CRWENO-5 method for the same problem as the WENO-5 scheme. The evolution of shock generated from sine wave with time is shown in Figure 9 for both Dirichlet and periodic boundary conditions computed using CRWENO-5 method. We use the same parameter as the WENO-5 scheme. We store snapshots of the solution field at different time steps to see the evolution of the shock. It can be seen from Figure 9 that the CRWENO-5 scheme captures the shock formed at final time accurately.
Figure 9.
Evolution of shock with time for the initial condition using CRWENO scheme.
4. Inviscid Burgers Equation: Conservative Form
In this section, we explain two approaches for solving the inviscid Burgers equation in its conservative form. The inviscid Burgers equation can be represented in the conservative form as below
The f is termed as the flux. We need to modify the grid arrangement to solve the inviscid Burgers equation in its conservative form. The modified grid for one-dimensional problem is shown in Figure 10. Here, . The solution field is stored at the center of each cell and this type of grid is primarily used for finite volume discretization. We explain two approaches to compute the nonlinear term in the inviscid Burgers equation.
Figure 10.
Finite difference grid for one-dimensional problems where the the solution is stored at the center of the cells. The flux is also computed at left and right boundary. The stencil required for reconstruction of left and right side flux is highlighted with blue rectangle. Ghost points are shown by red color.
4.1. Flux Splitting
The conservative form of the inviscid Burgers equation allows us to use the flux splitting method and WENO reconstruction to compute the flux at the interface. In this method, we first compute the flux f at the nodal points. The nonlinear advection term of the inviscid Burgers equation is computed as
at a particular node i. The superscripts L and R refer to the positive and negative flux component at the interface and the subscripts and stands for the left and right side interface of the nodal point i. We use WENO-5 reconstruction discussed in Section 3.1 to compute the left and right side flux at the interface. First, we compute the flux at nodal points and then split it into positive and negative components. This process is called as Lax-Friedrichs flux splitting and the positive and negative component of the flux at a nodal location is calculated as
where is the absolute value of the flux Jacobian (). We chose the values of as the maximum value of over the stencil used for WENO-5 reconstruction, i.e.,
Once we split the nodal flux into its positive and negative component we reconstruct the left and right side fluxes at the interface using WENO-5 scheme using below formulas [24]
where the nonlinear weights are computed using Equations (36) and (37). Once we have left and right side fluxes at the interface we use Equation (53) to compute the flux derivative. We demonstrate the flux splitting method for a shock generated by sine wave similar to Section 3. We use the third-order Runge-Kutta numerical scheme for time integration and periodic boundary condition for the domain. For the conservative form, we need three ghost points on left and right sides, since we compute the flux at the left and right boundary of the domain also. The periodic boundary condition for fluxes at ghost points is given by
The Julia function to compute the right hand side of the inviscid Burgers equation is detailed in Listing 12. The implementation of flux reconstruction and weight computation in Julia is similar to Listings 6 and 8.
| Listing 12. Implementation of Julia function to calculate the right hand side of inviscid Burgers equation using flux splitting method. |
![]() ![]() |
4.2. Riemann Solver: Rusanov Scheme
Another approach to computing the nonlinear term in the inviscid Burgers equation is to use Riemann solvers. Riemann solvers are used for accurate and efficient simulations of Euler equations along with higher-order WENO schemes [27,28]. In this method, first, we reconstruct the left and right side fluxes at the interface similar to the inviscid Burgers equation in non-conservative form. Once we have and reconstructed, we use Riemann solvers to compute the fluxes at the interface. We follow the same procedure discussed in Section 3.1 to reconstruct the fluxes at the interface. We can also use CRWENO-5 scheme presented in Section 3.2 to determine the fluxes.
We use a simple Rusanov scheme as the Riemann solver [29]. The Rusanov scheme uses maximum local wave propagation speed to compute the flux as follows
where is the flux component using the right reconstructed state , and is the flux component using the right reconstructed state . Here, is the local wave propagation speed which is obtained by taking the maximum absolute value of the eigenvalues corresponding to the Jacobian, , between cells i and can be obtained as
or in a wider stencil as shown in Equation (55). The implementation of Riemann solver with Rusanov scheme for inviscid Burgers equation is given in Listing 13. The implementation of state reconstruction and weight computation in Julia is similar to Listings 6 and 8. Figure 11 shows the evolution of shock for the sine wave. We use the same parameters for the spatial and temporal discretization of the inviscid Burgers equation as used for the non-conservative form in Section 3.
Figure 11.
Evolution of shock from sine wave () computed using conservative form of inviscid Burgers equation. The reconstruction of the field is done using WENO-5 scheme, and flux splitting and Riemann solver approach is used to compute the nonlinear term.
| Listing 13. Implementation of Julia function to calculate the right hand side of inviscid Burgers equation using Riemann solver based on Rusanov scheme. |
![]() ![]() |
5. One-Dimensional Euler Solver
In this section, we present a solution to one-dimensional Euler equations. Euler equations contain a set of nonlinear hyperbolic conservation laws that govern the dynamics of compressible fluids neglecting the effect of body forces, and viscous stress [30]. The one-dimensional Euler equations in its conservative form can be written as
where
in which
Here, and p are the density, and pressure respectively; u is the horizontal component of velocity; e and h stand for the internal energy and static enthalpy, and is the ratio of specific heats. Equation (61) can also be written as
where is the convective flux Jacobian matrix. The matrix A is given as
where . We can write the matrix A as in which Λ is the diagonal matrix of real eigenvalues of A, i.e., . Here, L and R are the matrices of which the columns are the left and right eigenvectors of A; . The matrices L and R are given below [31]
where a is the speed of sound and is given by , and .
We use the grid similar to the one used for the conservative form of the inviscid Burgers equation and is shown in Figure 10. The semi-discrete form of the Euler equations can be written as
where is the cell-centered values stored at nodal points and are the fluxes at left and right cell interface. We use third-order Runge-Kutta numerical method for time integration. We use WENO-5 reconstruction to compute the left and sight states of the fluxes at the interface. We include three different Riemann solvers to compute the flux at interfaces to be used in Equation (66).
5.1. Roe’s Riemann Solver
First, we present the Roe solver for one-dimensional Euler equations. According to the Gudanov theorem [32], for a hyperbolic system of equations, if the Jacobian matrix of the flux vector is constant (i.e., ), the exact values of fluxes at the interfaces can be calculated as
where |Λ| is the diagonal matrix consisting of absolute values eigenvalues of the Jacobian matrix and the matrices L and R are given in Equation (65). However, Jacobian matrix is not constant (i.e., ). The Roe solver [33] is an approximate Riemann solver and it uses below formulation to find the numerical fluxes at the interface
where the bar represents the Roe average between the left and right states. In order to derive we need to know , , and and they are computed using Roe averaging procedure. In order to derive , , and Roe used Taylor series expansion of F around and points.
Neglecting higher-order terms and equation above two equations
Roe [20] derived approximate matrix such that it satisfies as . Therefore, we get the following set of equations (written in vector form)
Using Equation (72) we can compute , , and . First we reconstruct the left and right states of q at the interface (i.e., and ) using WENO-5 scheme. Then, we can compute the left and right state of the fluxes (i.e., and ) using Equation (61). The Roe averaging formulas to compute approximate values for constructing and are given below
where the left and right states are computed using WENO-5 reconstruction. The speed of the sound is computed using the below equation
The eigenvalues of the Jacobian matrix are , , and . The implementation of Roe’s Riemann solver is given in Listing 14.
| Listing 14. Implementation of Julia function to calculate the right hand side of Euler equations using Roe’s Riemann solver. |
![]() ![]() |
The implementation of WENO-5 reconstruction is similar to Listings 8 and 9. We implemented all Riemann solvers for Euler equations in such a way that we compute smoothness indicators for all equations separately. We do not incur much computational expense since our problem is one-dimensional. However, for higher-dimension usually smoothness indicator is computed only for equation (usually momentum or energy equation) and the same smoothness indicators are used for all equations. The computation of flux using Roe averaging is detailed in Listing 15.
| Listing 15. Implementation of Julia function to calculate the interface flux using Riemann solver based on Roe averaging. |
![]() ![]() |
We test the Riemann solver using Sod shock tube problem [34]. The time evolution of Sod shock tube problem is governed buy Euler equations. This problem is used for testing numerical schemes to study how well they can capture and resolve shocks and discontinuities and their ability to produce correct density profile. The initial conditions for the Sod shock tube problem are
We use third-order Runge-Kutta numerical scheme for time integration from to . We use two grid resolutions to see the capability of Riemann solver to capture the shock. The Sod shock tube problem has Dirichlet boundary condition at the left and right boundary. We use linear interpolation to find the values of q at ghost points. Figure 12 shows the density, velocity, pressure, and energy at final time using Roe solver. We consider the solution obtained with high-grid resolution as the true solution and compare it with the low-resolution results. From Figure 12, we observe that there are small oscillations near discontinuities at low-resolution. We use WENO reconstruction to compute the flux at interface and WENO scheme is not strictly non-oscillatory. However, WENO scheme does not allow oscillations to amplify to large values. To remove these oscillations further, for example, we can use characteristic-wise reconstructions [35].
Figure 12.
Evolution of density, velocity, energy, and pressure at for the shock tube problem computed using Riemann solver based on Roe averaging. The true solution is calculated with grid resolution with and the low-resolution results are for grid resolution with .
5.2. HLLC Riemann Solver
We present one of the most widely used approximate Riemann solver based on HLLC scheme [30,36]. They used lower and upper bounds on the characteristics speeds in the solution of Riemann problem involving left and right states. These bounds are approximated as
where and are the lower and upper bound for the left and right state characteristics speed. For HLLC scheme we also include middle wave of speed given by
The mean pressure is given by
The fluxes are computed as
where . Once the flux is available at the interface, we can integrate the solution in time using Equation (66). The implementation of HLLC scheme is given in Listing 16. We perform the time integration using third-order Runge-Kutta numerical scheme. Figure 13 shows the density, velocity, pressure, and energy at final time computed using Riemann solver based on HLLC scheme. The oscillations in the numerical solution calculated by the HLLC scheme is slightly less than those calculated by the Roe’s Riemann solver. The true solution is computed using grid points and and is compared with the low-resolution results for grid points and .
Figure 13.
Evolution of density, velocity, energy, and pressure at for the shock tube problem computed using Riemann solver based on Harten-Lax-van Leer Contact (HLLC) scheme. The true solution is calculated with grid resolution with and the low-resolution results are for grid resolution with .
| Listing 16. Implementation of Julia function to calculate the interface flux using Riemann solver based on HLLC scheme. |
![]() ![]() |
5.3. Rusanov’s Riemann Solver
We show the implementation of Riemann solver using Rusanov scheme. This is similar to what we discussed in Section 4.2. The Riemann solver based on Rusanov scheme is simple compared to Roe’s Riemann solver and HLLC based Riemann solver. For Euler equations, instead of solving one equation (as in inviscid Burgers equation), now we have to follow the procedure for three equations (i.e., density, velocity, and energy). We need to approximate wave propagation speed at the interface to compute flux at the interface. For Rusanov scheme, we simply use maximum eigenvalue of the Jacobian matrix as the wave propagation speed. We have , where and are computed using Roe averaging. Figure 14 shows the density, velocity, pressure, and energy at final time computed using Riemann solver based on Rusanov scheme. The oscillations in the numerical solution calculated by the Rusanov scheme is more than those calculated by the Roe’s Riemann solver or HLLC scheme based Riemann solver. One of the advantage of Rusanov scheme is that it is simple to implement and is computationally faster. The true solution is computed using grid points and and is compared with the low-resolution results for grid points and . The implementation of Riemann solver based on Rusanov’s scheme is given in Listing 17.
Figure 14.
Evolution of density, velocity, energy, and pressure at for the shock tube problem computed using Riemann solver based on Rusanov scheme. The true solution is calculated with grid resolution with and the low-resolution results are for grid resolution with .
| Listing 17. Implementation of Julia function to calculate the interface flux using Riemann solver based on Rusanov scheme. |
![]() ![]() |
6. Two-Dimensional Poisson Equation
In this section, we explain different methods to solve the Poisson equation which is encountered in solution to the incompressible flows. The Poisson equation is solved at every iteration step in the solution to the incompressible Navier-Stokes equation due to the continuity constraint. Therefore, many studies have been done to accelerate the solution to the Poisson equation using higher-order numerical methods [37], and developing fast parallel algorithms [38]. The Poisson equation is a second-order elliptic equation and can be represented as
Using the second-order central difference formula for discretization of the Poisson equation, we get
where and is the grid spacing in the x and y directions, and is the source term at discrete grid locations. If we write Equation (83) at each grid point, we get a system of linear equations. For the Dirichlet boundary condition, we assume that the values of are available when is a boundary point. These equations can be written in the standard matrix notation as
where
The boundary points of the domain are incorporated in the source term vector in Equation (84). We can solve Equation (84) by standard methods for systems of linear equations, such as Gaussian elimination [39]. However, the matrix A is very sparse and the standard methods are computationally expensive for large size of A. In this paper, we discuss direct methods based on fast Fourier transform (FFT) and fast sine transform (FST) for periodic and Dirichlet boundary condition. We also explain iterative methods to solve Equation (84). We will further present a multigrid framework which scales linearly with a number of discrete grid points in the domain.
6.1. Direct Solver
Figure 15 shows the finite difference grid for two-dimensional CFD problems. We use two different boundary conditions for direct Poisson solver: periodic and Dirichlet boundary condition. For the Dirichlet boundary condition, the nodal values of a solution are already known and do not change, and hence, we do not do any calculation for the boundary points. In case of periodic boundary condition, we do calculation for grid points between . The solution at right and top boundary is obtained from left and bottom boundary, respectively.
Figure 15.
Finite difference grid for two-dimensional problems. The calculation is done only for points shown by blue color. The solution at black points is extended from left and bottom boundary for periodic boundary condition. The solution is already available at all four boundaries for Dirichlet boundary condition.
We perform the assessment of direct solver using the method of manufactured solution. We assume certain field u and compute the source term f at each grid location. We then solve the Poisson equation for this source term f and compare the numerically calculated field u with the exact solution field u. The exact field u and the corresponding source term f used for direct Poisson solver are given below
This problem can have both periodic and Dirichlet boundary conditions. The computational domain is square in shape and is divided into 512 × 512 grid in x and y directions.
6.1.1. Fast Fourier Transform
There are two different ways to implement fast Poisson solver for the periodic domain. One way is to perform FFTs directly on the Poisson equation, which will give us the spectral accuracy. The second method is to discretize the Poisson equation first and then apply FFTs on the discretized equation. The second approach will give us the same spatial order of accuracy as the numerical scheme used for discretization. We use the second-order central difference scheme given in Equation (83) for developing a direct Poisson solver.
The Fourier transform decomposes a spatial function into its sine and cosine components. The output of the Fourier transform is the function in its frequency domain. We can recover the function from its frequency domain using the inverse Fourier transform. We use both the function and its Fourier transform in the discretized domain which is called the discrete Fourier transform (DFT). Cooley-Tukey proposed an FFT algorithm that reduces the complexity of computing DFT from to , where N is the data size [17]. In two-dimensions, the DFT for a square domain discretized equally in both directions is defined as
where is the function in the spatial domain and the exponential term is the basis function corresponding to each point in the Fourier space, m and n are the wavenumbers in Fourier space in x and y directions, respectively. This equation can be interpreted as the value in the frequency domain can be obtained by multiplying the spatial function with the corresponding basis function and summing the result. The basis functions are sine and cosine waves with increasing frequencies. Here, represents the component of the function with the lowest wavenumber and represents the component with the highest wavenumber. Similarly, the function in Fourier space can be transformed to the spatial domain. The inverse discrete Fourier transform (IDFT) is given by
where is the normalization term. The normalization can also be applied to forward transform, but it should be used only once. If we use Equation (88) in Equation (83), we get
in which we use the definition of cosine to yield
If we take the forward DFT of Equation (90), we get a spatial function . The three step procedure to develop a Poisson solver using FFT is given below
- Apply forward FFT to find the Fourier coefficients of the source term in the Poisson equation ( from the grid values )
- Get the Fourier coefficients for the solution using below relation
- Apply inverse FFT to get the grid values from the Fourier coefficients .
Implementation of Poisson solver using FFT is given in Listing 18. Figure 16 shows the exact and numerical solution for for the test problem.
Figure 16.
Comparison of exact solution and numerical solution computed using fast Fourier transform method for the Poisson equation with periodic boundary condition.
| Listing 18. Implementation of Julia function for the Poisson solver using fast Fourier transform (FFT) for the domain with periodic boundary condition. |
![]() ![]() |
We develop the Poisson solver using a second-order central difference scheme for discretization of the Poisson equation. Instead of using the discretized equation, we can directly use Fourier analysis for the Poisson equation. This will compute the exact solution and the solution will not depend upon the grid size. The only change will be the change in Equation (91). We can easily derive the equation for Fourier coefficients in spectral solver by performing forward FFT of Equation (82). The Fourier coefficients for spectral solver can be written as
We can get the solution by performing inverse FFT of the above equation. The implementation of spectral Poisson solver in Julia is outlined in Listing 19.
| Listing 19. Implementation of Julia function for the spectral Poisson solver using fast Fourier transform (FFT) for the domain with periodic boundary condition. |
![]() ![]() |
The spectral Poisson solver gives the exact solution and the error between exact and numerical solution is machine zero error. We demonstrate it by using spectral Poisson solver for three different grid resolutions. We also use the central-difference scheme (CDS) Poisson solver at these grid resolutions and compare the discretization errors for two methods. We systematically change the grid spacing by a factor of two in both x and y direction and compute the norm of the discretization error. Figure 17 shows the contour plot for the error at three grid resolutions for spectral and second-order CDS. We can notice that the error for the spectral method does not depend on the grid resolution and the value of error is of the same order as machine zero error. On the other hand, for CDS the error reduces as we increase the grid resolution. This is because the truncation error goes down with the decrease in grid spacing. Figure 18 plots the norm of the error for spectral method and CDS. The slope of the discretization error is two for CDS showing that the scheme has second-order of accuracy. This method is used in CFD to validate the code and numerical methods. For the spectral method, the norm of the error has the machine zero value for all three grid resolutions.
Figure 17.
Comparison of error for spectral method (Top) and second-order CDS (bottom) for three different grid resolutions. The error is defined as .
Figure 18.
Order of accuracy for spectral method and second-order CDS. We calculate discretization error at five grid resolutions and change the grid size by a factor of 2 in both x and y direction. The CDS Poisson solver give second-order accuracy. Spectral method give exact solution and error is of the same order as machine zero.
6.1.2. Fast Sine Transform
The procedure described in Section 6.1.1 is valid only when the problem has periodic boundary condition, i.e., the solution that satisfies . If we have homogeneous Dirichlet boundary condition for the square domain problem, we need to use the sine transform given by
where m and n are the wavenumbers. This satisfies the homogeneous Dirichlet boundary conditions that at and . If we substitute Equation (93) in Equation (83) and use trigonometric identity () we get
We can compute the spatial field using inverse sine transform given by
The three-step procedure to develop Poisson solver using sine transform can be listed as
- Apply forward sine transform to find the Fourier coefficients of the source term in the Poisson equation ( from the grid values )
- Get the Fourier coefficients for the solution using below relation
- Apply inverse sine transform to get the grid values from the Fourier coefficients .
The implementation of Poisson solver for problems with homogeneous Dirichlet boundary condition using the sine transform is given in Listing 20. Figure 19 displays the comparison of exact and numerical solution for the test problem computed using sine transform.
Figure 19.
Comparison of exact solution and numerical solution computed using fast sine transform method for the Poisson equation with Dirichlet boundary condition.
| Listing 20. Implementation of Julia function for the Poisson solver using fast sine transform (FST) for the domain with Dirichlet boundary condition. |
![]() |
6.2. Iterative Solver
Iterative methods use successive approximations to obtain the most accurate solution to a linear system at every iteration step. These methods start with an initial guess and proceed to generate a sequence of approximations, in which the k-th approximation is derived from the previous ones. There are two main types of iterative methods. Stationary methods that are easy to understand and are simple to implement, but are not very effective. Non-stationary methods which are based on the idea of the sequence of orthogonal vectors. We would like to recommend a text by Barrett et al. [40] which provides a good discussion on the implementation of iterative methods for solving a linear system of equations.
We show only Dirichlet boundary condition implementation for iterative methods. For iterative methods, we stop iterations when the norm of the residual for Equation (83) goes below . We test the performance of all iterative methods using the method of manufactured solution. The exact field u and the corresponding source term f used for the method of manufactured solution for iterative methods are given below [12]
6.2.1. Stationary Methods: Gauss-Seidel
The iterative methods work by splitting the matrix A into
Equation (84) becomes
The solution at iteration is given by
If we take the difference between the above two equations, we get the evolution of error as . For the solution to converge to the exact solution and the error to go to zero, the largest eigenvalue of iteration matrix should be less than 1. The approximate number of iterations required for the error to go below some specified tolerance is given by
where is the number of iterations and is the maximum eigenvalue of iteration matrix . If the convergence tolerance is specified to be then the number of iterations for convergence will be and for and respectively. Therefore, the maximum eigenvalue of the iteration matrix should be less for faster convergence. When we implement iterative methods, we do not form the matrix M and P. Equation (101) is just the matrix representation of the iterative method. In matrix terms, the Gauss-Seidel method can be expressed as
where D, L and U represent the diagonal, the strictly lower-triangular, and the strictly upper-triangular parts of A, respectively. We do not explicitly construct the matrices D, L, and U, instead we use the update formula based on data vectors (i.e., we obtain a vector once a matrix operates on a vector).
The update formulas for Gauss-Seidel iterations if we iterate from left to right and from bottom to top can be written as
where
The implementation of the Gauss-Seidel method for Poisson equation in Julia is given in Listing 21. The pseudo algorithm is given by Algorithm 3, where we define an operator such that
gives same results as matrix-vector multiplication for grid point. It can be noticed that the matrix M and P are not formed, and the solution is computed using Equation (105) for every point on the grid. Since we are traversing bottom to top and left to right, the updated values on left and bottom will be used for updating the solution at grid point. This will help in accelerating the convergence compared to the Jacobi method which uses values at the previous iteration step for all its neighboring points.
Figure 20 shows the contour plot for exact and numerical solution for the Gauss-Seidel solver. As seen in Figure 24, the residual reaches values after more than 400,000 iterations for Gauss-Seidel method. The maximum eigenvalue of the iteration matrix is large for the Gauss-Seidel method and hence, the rate of convergence is slow. The rate of convergence is higher in the beginning while the high wavenumber errors are still present. Once the high wavenumber errors are resolved, the rate of convergence for low wavenumber errors drops. There are ways to accelerate the convergence such as using successive overrelaxation methods [41]. The residual is computed from Equation (83) as the difference between the source term and the left hand side term computed based on the central-difference scheme. We would like to point out that the residual is different from the discretization error. The discretization error is the difference between numerical and exact solution.
Figure 20.
Comparison of exact solution and numerical solution computed using Gauss-Seidel iterative method for the Poisson equation.
| Algorithm 3 Gauss-Seidel iterative method | |
| 1: Given b | ▹ source term |
| 2: | ▹ initialize the solution |
| 3: | |
| 4: while tolerance met do | |
| 5: for to do | |
| 6: for to do | |
| 7: | ▹ compute the residual |
| 8: | ▹ update the solution |
| 9: end for | |
| 10: end for | |
| 11: check convergence criteria, continue if necessary | |
| 12: end while | |
| Listing 21. Implementation of Gauss-Seidel iterative method for Poisson equation in Julia. |
![]() |
6.2.2. Non-Stationary Methods: Conjugate Gradient Algorithm
Non-stationary methods differ from stationary methods in that the iterative matrix changes at every iteration. These methods work by forming a basis of a sequence of matrix powers times the initial residual. The basis is called as the Krylov subspace and mathematically given by . The approximate solution to the linear system is found by minimizing the residual over the subspace formed. In this paper, we discuss the conjugate gradient method which is one of the most effective methods for symmetric positive definite systems.
The conjugate gradient method proceeds by calculating the vector sequence of successive approximate solution, residual corresponding the approximate solution, and search direction used in updating the solution and residuals. The approximate solution is updated at every iteration by a scalar multiple of the search direction vector :
Correspondingly, the residuals are updated as
The search directions are then updated using the residuals
where the choice ensures that and are orthogonal to all previous and respectively. A detailed derivation of the conjugate gradient method can be found in [42]. The complete procedure for the conjugate gradient method is given in Algorithm 4. The implementation of conjugate gradient method in Julia is given in Listing 22.
The comparison of the exact and numerical solution is shown in Figure 21. The residual history for the conjugate gradient method is displayed in Figure 24. It can be seen that the rate of convergence is slower at the start of iterations. As the conjugate gradient algorithm finds the correct direction for residual, the rate of convergence increases. There are several types of iterative methods which are not discussed in this paper and for further reading we refer to a book “Iterative Methods for Sparse Linear Systems” [43].
Figure 21.
Comparison of exact solution and numerical solution computed using conjugate gradient algorithm for the Poisson equation.
| Listing 22. Implementation of conjugate gradient method for Poisson equation in Julia. |
![]() |
| Algorithm 4 Conjugate gradient algorithm | |
| 1: Given b | ▹ Given source term in space, i.e., in Equation (82) |
| 2: Given matrix operator | ▹ Given discretization by Equation (107) |
| 3: | ▹ Initialize the solution |
| 4: | ▹ Initialize the residual |
| 5: | ▹ Initialize the conjugate |
| 6: | |
| 7: | |
| 8: while tolerance met (or ) do | |
| 9: | |
| 10: | |
| 11: | ▹ Update the solution |
| 12: | |
| 13: | |
| 14: | |
| 15: | ▹ Update the residual |
| 16: check convergence; continue if necessary | |
| 17: | |
| 18: end while | |
6.3. Multigrid Framework
We saw in Section 6.2 that the rate of convergence for iterative methods depends upon the iteration matrix . Point iterative methods like Jacobi and Gauss-Seidel methods have large eigenvalue and hence the slow convergence. As the grid becomes finer, the maximum eigenvalue of the iteration matrix becomes close to 1. Therefore, for very high-resolution simulation, these iterative methods are not feasible due to the large computational time required for residuals to go below some specified tolerance.
The multigrid framework is one of the most efficient iterative algorithm to solve the linear system of equations arising due to the discretization of the Poisson equation. The multigrid framework works on the principle that low wavenumber errors on fine grid behave like a high wavenumber error on a coarse grid. In the multigrid framework, we restrict the residuals on the fine grid to the coarser grid. The restricted residual is then relaxed to resolve the low wavenumber errors and the correction to the solution is prolongated back to the fine grid. We can use any of the iterative methods like Jacobi, Gauss-Seidel method for relaxation. The algorithm can be implemented recursively on the hierarchy of grids to get faster convergence.
Let denote the approximate solution after applying few steps of iterations of a relaxation method. The fine grid solution will be
where is the error at fine grid and the operator A is defined in Equation (84) for the Cartesian grid. We define the residual vector . This residual vector contains mostly low wavenumber errors. The correction at the fine grid is obtained by restricting the residual vector to a coarse grid ( grid spacing) and solving an equivalent system to obtain the correction. The equivalent system on a coarse grid can be written as
where is the elliptic operator on coarse grid, and is the restriction operator. The discretized form of Equation (113) can be written as
We compute the approximate error at an intermediate grid using some relaxation operation. Once the approximation to is obtained, it is prolongated back to the fine grid using
where is the prolongation operator. The approximate solution at fine grid is corrected with . The approximate solution is relaxed for a specific number of iterations and convergence criteria is checked. If the residuals are below specified tolerance we stop or we repeat the steps. The procedure can be easily extended to more number of levels. The illustration of the V-cycle multigrid framework for two levels is shown in Figure 22. In this study, we use two iterations of the Gauss-Seidel method for relaxation at every grid level. The number of iterations can be set to a different number or we can also set the residual tolerance instead of a fixed number of iterations.
Figure 22.
Illustration of multigrid V-cycle framework with three levels between fine and coarse grid. For full multigrid cycle, the coarsest mesh has grid resolution and is solved directly.
The residuals corresponding to the fine grid can be projected on to the coarse grid either using full-weighting. The full-weighting restriction operator is given by
For boundary points, the residuals are directly injected from fine grid to coarse grid. The implementation of restriction operator in Julia is given in Listing 23.
The prolongation operator transfers the error from the coarse grid to the fine grid. We use direct injection for points which are common to both coarse and fine grid and bilinear interpolation for points which are on the fine grid but not on the coarse grid. The prolongation operations are given by below equations
The implementation of prolongation operation in Julia is given in Listing 24.
| Listing 23. Implementation of restriction operation in multigrid framework. |
![]() |
| Listing 24. Implementation of prolongation operation in multigrid framework. |
![]() |
The relaxation of the solution is done using the Gauss-Seidel method for each grid level as formulated in Section 6.2.1 for a fixed number of iterations. The Julia implementation of relaxation operation is provided in Listing 25. The pseudocode for V-cycle multigrid framework for three levels if provided in Algorithm 5. The implementation of a complete multigrid framework in Julia for two levels is given in Listing 26.
| Listing 25. Implementation of relaxation operation (using Gauss-Seidel iterative method) in multigrid framework. |
![]() |
| Algorithm 5 V-cycle multigrid framework | |
| 1: Given operator | |
| 2: Given v1 | ▹ number of relaxation operation during restriction |
| 3: Given v2 | ▹ number of relaxation operation during prolongation |
| 4: Given v3 | ▹ number of relaxation operation on coarsest grid |
| 5: while tolerance met (or ) do | |
| 6: for v1 iterations do | |
| 7 | ▹ relaxation for v1 iterations |
| 8: end for | |
| 9: check convergence; continue if necessary | |
| 10: | ▹ compute residual for fine grid |
| 11: | ▹ restrict the residual from fine grid to intermediate grid |
| 12: for v1 iterations do | |
| 13: | ▹ relaxation of error for v1 iterations |
| 14: end for | |
| 15: | ▹ compute residual using error at intermediate grid |
| 16: | ▹ restrict the residual for error to coarsest grid |
| 17: for v3 iterations do | ▹ can be solved directly instead of v3 iterations |
| 18: | ▹ relaxation of error for v3 iterations |
| 19: end for | |
| 20: | ▹ prolongate error from coarsest grid to intermediate level |
| 21: | ▹ add correction to the error at intermediate grid |
| 22: for v2 iterations do | |
| 23: | ▹ relaxation of error for v2 iterations |
| 24: end for | |
| 25: | ▹ prolongate error from intermediate grid to coarse grid |
| 26: | ▹ add correction to the solution at fine level |
| 27: for v2 iterations do | |
| 28: | ▹ relaxation of solution for v2 iterations |
| 29: end for | |
| 30: end while | |
| Listing 26. Implementation of multigrid framework in Julia with two levels in V-cycle. |
![]() ![]() |
Figure 23 displays the comparison of exact solution and numerical solution for full V-cycle multigrid framework. We use 512 × 512 grid resolution and hence we use nine levels from fine mesh to coarsest mesh. When the coarsest mesh has 2 × 2 grid resolution (i.e., since boundaries are set zero, only one grid point value is unknown and that can be solved algebraically), then it is usually referred to as the full multigrid framework. Figure 24 shows that the residual drops below tolerance in just nine iterations. This does not consist of a number of iterations for relaxation operations done for each intermediate grid levels. Although we report the outer iteration counter for multigrid results, we highlight that the workload in multigrid is more than the outer iteration counts. Equation (121) gives the total iteration count for a typical V-cycle multigrid method:
where V is the outer iteration count, and are the number of iterations during prolongation and restriction, is the number of iterations for coarsest grid, and L is total number of levels in full multigrid cycle. In a typical application, the values of , , and can work effectively (i.e., a higher value of can be used if the coarsest resolution is larger than and intended to be solved by the same relaxation method). Table 2 provides the comparison of different iterative solvers for the Poisson equation.
Figure 23.
Comparison of exact solution and numerical solution computed using multigrid framework for the Poisson equation.
Figure 24.
Residual history comparison for different iterative methods for Poisson equation with Dirichlet boundary condition. The stopping criteria for all iterative methods is that the residual should reduce below . The grid resolution for all cases is and .
Table 2.
Comparison of different iterative methods in terms of iteration count, residual and CPU time for solving the Poisson equation on a resolution of and .
8. Hybrid Arakawa-Spectral Solver
In this section, instead of using a fully explicit method in time for solving 2D incompressible Navier-Stokes equation, we show how to design hybrid explicit and implicit scheme. The nonlinear Jacobian term in vorticity Equation (133) is treated explicitly using third-order Runge-Kutta scheme, and we treat the viscous term implicitly using the Crank-Nicolson scheme. This type of hybrid approach is useful when we design solvers for wall-bounded flows, where we cluster the grid in the boundary layer. For wall-bounded flows, we use dense mesh with a smaller grid size in the boundary layer region to capture the boundary layer flow correctly. First, we can re-write Equation (133) with nonlinear Jacobian term on the right hand side
The hybrid third-order Runge-Kutta/Crank-Nicolson scheme can be written as [51]
where the term is the nonlinear Jacobian term at the second-step of previous time step at . We can choose coefficients in Equations (142)–(144) in such a way that vanishes and we do not have to store the solution at second-step of the previous time step. These coefficients are
Besides, to use a hybrid scheme for time, we also design a hybrid finite difference and spectral scheme. We use Arakawa finite difference scheme for the nonlinear Jacobian term and spectral scheme for linear viscous terms. In Fourier space, Equations (142)–(144) can be written as
where . Here, m and n are wavenumber in x and y directions, respectively. Rearranging above equations, we get
We use Arakawa finite difference scheme described in Section 7 to compute the Jacobian term in physical space. The streamfunction is computed from the vorticity field using a spectral method. Once the Jacobian term is available in physical space we convert it to Fourier space using FFT. The relationship between vorticity and streamfunction is Fourier space can be written as
The time integration using hybrid third-order Runge-Kutta/ Crank-Nicolson scheme in Julia is listed in Listing 32. The Julia implementation to compute the nonlinear Jacobian term for the Arakawa-spectral solver is given in Listing 33.
| Listing 32. Julia implementation of hybrid third-order Runge-Kutta/ Crank-Nicolson scheme for 2D incompressible Navier-Stokes equation with Arakawa-spectral finite difference scheme. |
![]() ![]() |
| Listing 33. Julia implementation of computing the nonlinear Jacobian term in physical space and converting it to Fourier space. |
![]() ![]() |
We validate our hybrid Arakawa-spectral solver for vortex-merger problem. Figure 27 shows the evolution of two point vortices merging into a single vortex computed using hybridized finite difference and spectral methodology. In the next section, we will eliminate finite difference and present a pseudo-spectral methodology.
Figure 27.
Evolution of the vorticity field at three time steps during the co-rotating vortex interaction. The solution is computed using hybrid Arakawa-spectral solver.
9. Pseudo-Spectral Solver
In Section 8, we computed the Jacobian term using finite difference Arakawa scheme. We can also compute this term using a spectral method to develop the pseudo-spectral solver. The main motivation for the spectral solver is a unique advantage of both accuracy and efficiency considerations in simple geometries. It reduces a PDE problem to a set of ODEs without introducing a numerical discretization error. Furthermore, the code implementation will be much simpler due to the availability of FFT libraries. We refer to a paper by Mortensen and Langtangen [52] for further discussion of high performance pseudo-spectral solver implementation of 3D Navier-Stokes equations. We use the hybrid explicit and implicit Crank-Nicolson scheme for time integration similar to the previous section. The only difference is the computation of the Jacobian term. The Jacobian term involves the first derivative term and can be calculated easily using a spectral method. The first derivative of Equation (88) in x and y direction is calculated as
The Jacobian term in the Fourier space can be computed as
where ⊛ is the convolution operation. Using Equation (154), we get
The convolution operation is performed in physical space and then converted to physical space. The pseudo-code for calculating the Jacobian term is given as
We can observe that the Jacobian term involves the product of two functions. When we evaluate the product of two functions using Fourier transform, aliasing errors appear in the computation [12]. Aliasing errors refers to the misrepresentation of wavenumbers when the function is mapped onto a grid with not enough grid points. One way to reduce aliasing errors is to use 3/2-rule. In this method, the original grid is refined by a factor in each direction. The additional grid points are assigned zero value and this is also referred to as zero-padding. For example, if we are using spatial resolution of and in x and y direction, the Jacobian term will be evaluated on grid with and (i.e., a data set consisted of points is used in the inverse and forward FFT calls in Equation (159)). The main code for the pseudo-spectral solver is the same as the hybrid solver discussed in Section 8 except for the Jacobian function. The implementation of Jacobian calculation with 3/2 padding is given in Listing 34.
| Listing 34. Julia implementation of computing the nonlinear Jacobian term in Fourier space with 3/2 padding rule. |
![]() ![]() |
We compare the performance of pseudo-spectral solver for two-dimensional incompressible flow problems written in Julia with the vectorized version of Python code. The FFT operations in Julia are performed using the FFTW package. We use two different packages for FFT operations in Python: Numpy library and pyFFTW package. We measure the CPU time for two grid resolutions: and . We test two codes at with from time to . The CPU time for two grid resolutions for two versions of code is provided in Table 4. It can be seen that the computational time for Julia is slightly better than Python code with the Numpy library for performing FFT operations. The pyFFTW package uses FFTW which is a very fast C library. The computational time required by vectorized Python code with pyFFTW package is better than the Julia code. This might be due to the effective interface between the FFTW library and pyFFTW package. Therefore, each programming language has some benefits over others. However, the goal of this study is not to optimize the code or find the best scientific programming language but is to present how can one implement CFD codes.
Table 4.
Comparison of CPU time for Julia and Python version of pseudo-spectral solver at two grid resolutions. We highlight that the codes are not written for optimization purpose and the CPU time may not be the optimal time that can be achieved for each of the programming languages.
Another common technique to reduce aliasing error which is widely used in practice is to use 2/3-rule. In this technique we retain the data on 2/3 of the grid is retained, while the remaining data is assigned with zero value. For example, if we are using a spatial resolution of and (i.e., and ), we will retain the Fourier coefficients corresponding to (−42 to 42) and remaining Fourier coefficients are set zero. One of the advantages of using this method is that it is computationally efficient since the Fourier transform of the matrix having size as the power of 2 is more efficient than the matrix which has dimension non-powers of 2. If we use -rule it is equivalent to performing calculation on and grid. However, a data set consisted of points is used in the inverse and forward FFT calls in Equation (159). The implementation of Jacobian calculation with padding is given in Listing 35. Before we close this section, we would like to note that there is no significant aliasing error in the example presented in this section. Therefore, one might get similar results without performing any padding (1/1 rule).
| Listing 35. Julia implementation of computing the nonlinear Jacobian term in Fourier space with 2/3 padding rule. |
![]() ![]() ![]() |
10. Concluding Remarks
The easy syntax and fast performance of Julia programming language make it one of the best candidates for engineering students, especially from a non-computer science background to develop codes to solve problems they are working on. We use Julia language as a tool to solve basic fluid flow problems which can be included in graduate-level CFD coursework. We follow a similar pattern as teaching a class, starting from basics and then building upon it to solve more advanced problems. We provide small pieces of code developed for simple fundamental problems in CFD. These pieces of codes can be used as a starting point and one can go about adding more features to solve complex problems. We make all codes, plotting scripts available online and open to everyone. All the codes are made available online to everyone on Github (https://github.com/surajp92/CFD_Julia).
We explain fundamental concepts of finite difference discretization, explicit methods, implicit methods using one-dimensional heat equation in Section 2. We also outline the procedure to develop a compact finite difference scheme which gives higher-order accuracy with smaller stencil size. We present a multi-step Runge-Kutta method which has higher temporal accuracy than standard single step finite difference method. We illustrate numerical methods for hyperbolic conservation laws using the inviscid Burgers equation as the prototype example in Section 3. We demonstrate two shock-capturing methods: WENO-5 and CRWENO-5 methods for Dirichlet and periodic boundary conditions. Students can learn about WENO reconstruction and boundary points treatment which is also applicable to other problems.
We show an implementation of the finite difference method for the inviscid Burgers equation in its conservative form in Section 4. Students will get insight into a finite volume method implementation through this example. We present two approaches for computing fluxes at the interface using the flux splitting method and using Riemann solver. We use Riemann solver based on simple Rusanov scheme. Students can easily implement other methods by changing the function for Rusanov scheme. We also develop one-dimensional Euler solver and validate it for the Sod shock tube problem in Section 5. We borrow functions from heat equation codes, such as Runge-Kutta code for time integration. We use WENO-5 code developed for the inviscid Burgers equation for solving Euler equations. This will give students an understanding of how coding blocks developed for simple problems can be integrated to solve more challenging problems.
We illustrate different methods to solve elliptic equations using Poisson equation as an example in Section 6. We describe both direct methods and iterative methods to solve the Poisson equation. The direct solvers explained in this study are based on the Fourier transform. We demonstrate an implementation of FFT and FST for developing a Poisson solver for periodic and Dirichlet boundary condition respectively. We provide an overview of two iterative methods: Gauss-Seidel method and conjugate gradient method for the Poisson equation. Furthermore, we demonstrate the implementation of the V-cycle multigrid framework for the Poisson equation. A multigrid framework is a powerful tool for CFD simulations as it scales linearly, i.e., it has computational complexity.
We describe two-dimensional incompressible Navier-Stokes solver in Section 7 and validate it for two examples: lid-driven cavity problem and vortex-merger problem. We use streamfunction-vorticity formulation to develop the solver. This eliminates the pressure term in the momentum equation and we can use the collocated grid arrangement instead of the staggered grid arrangement. We use Arakawa numerical scheme for the Jacobian term. The lid-driven cavity problem has Dirichlet boundary condition and hence we use FST-based Poisson solver. We use FFT-based Poisson solver the vortex-merger problem since this problem is periodic in the domain. Most of the functions needed for developing the two-dimensional incompressible Navier-Stokes solver are taken from the heat equation and Poisson equation. Students will learn derivation of streamfunction-vorticity formulation, and its implementation through these examples and it can be easily extended to other problems like Taylor-Green vortex, decaying turbulence, etc. Furthermore, we develop hybrid incompressible Navier-Stokes solver for two-dimensional flow problems and is presented in Section 8. The solver is hybridized using explicit Runge-Kutta scheme and implicit Crank-Nicolson scheme for time integration. The solver is developed by solving the Navier-Stokes equation in Fourier space except for the nonlinear term. The nonlinear Jacobian term is computed in physical space using Arakawa finite difference scheme, converted to Fourier space and then used in Navier-Stokes equations. In addition, Section 9 provides the pseudo-spectral solver with 3/2 padding and 2/3 padding rule for two-dimensional incompressible Navier-Stokes equations. We also compare the computational performance of codes written in Julia and Python for the Navier-Stokes solvers. We should highlight that the codes are written in considering mostly pedagogical aspects (i.e., without performing any additional efforts in optimal coding and implementation practices) and cannot be viewed as a legitimate performance comparison of different languages, which is beyond the scope of this work.
Author Contributions
Data curation, S.P.; Visualization, S.P.; Supervision, O.S.; Writing—original draft, S.P.; and Writing—review and editing, S.P. and O.S.
Funding
This work received no external funding.
Conflicts of Interest
The authors declare no conflict of interest.
Appendix A. Installation Instruction
There are several ways to run Julia on your personal computer. One way is to install Julia on the computer and use it in the terminal using the built-in Julia command line. If you have a file in Julia, you can simly run the code from terminal using the command julia “filename.jl”. Another way is to run Julia code in the browser on JuliaBox.com with Jupyter notebooks. For this method, there is no installation required on the personal computer. One more way is to JuliaPro which includes Julia and the Juno IDE (https://juliacomputing.com/products/juliapro.html). This software contains a set of packages for plotting, optimization, machine learning, database, and much more.
Julia setup files can be downloaded from their website (https://julialang.org/downloads/). The website also includes instructions on how to install Julia on Windows, Linux, and mac operating systems. Some of the useful resources for learning Julia are listed below
Oftentimes, one will have to install additional packages to use some of the features in Julia. Pkg is Julia’s built in package manager, and it handles operations such as installing, updating and removing packages. We will explain with the help of FTTW library as an example on how to add and use new package. Below are the three commands needed to open Julia using terminal, add new package, and then use that package.

Appendix B. Plotting Scripts
We use PyPlot module in Julia for plotting all results. This module provides a Julia interface to the Matploltlib plotting library from Python. The Python Matplotlib has to be installed in order to use PyPlot package. Once the Matplotlib installed, you can just use Pkg.add(“PyPlot”) in Julia to install PyPlot and its dependencies. The detailed instruction for installing PyPlot package can be found on https://github.com/JuliaPy/PyPlot.jl.
We mention plotting scripts for two types of plots: XY plot and contour plot. These two plotting scripts are used in this paper to plot all the results. Also, once the student gets familiar with basic plotting scripts, one can easily extend it to more complex plots with examples available on the Internet. Some of the Internet resources that can be helpful for plotting are given below
The scripts for XY plotting and contour plotting are given in Listings A1 and A2, respectively.
| Listing A1. Plotting script for XY plot. |
![]() |
| Listing A2. Plotting script for contour plot. |
![]() |
References
- Barba, L.A.; Forsyth, G.F. CFD Python: The 12 steps to Navier-Stokes equations. J. Open Source Educ. 2018, 9, 21. [Google Scholar] [CrossRef]
- Oliphant, T.E. A Guide to NumPy; Trelgol Publishing: New York, NY, USA, 2006; Volume 1. [Google Scholar]
- Ketcheson, D.I. Teaching numerical methods with IPython notebooks and inquiry-based learning. In Proceedings of the 13th Python in Science Conference (SciPy. org), Austin, TX, USA, 6–13 July 2014. [Google Scholar]
- Ketcheson, D.I. HyperPython: An Introduction to Hyperbolic PDEs in Python. 2014. Available online: http://github.com/ketch/HyperPython/ (accessed on 25 June 2019).
- Ketcheson, D.I.; Mandli, K.T.; Ahmadia, A.J.; Alghamdi, A.; Quezada de Luna, M.; Parsani, M.; Knepley, M.G.; Emmett, M. PyClaw: Accessible, Extensible, Scalable Tools for Wave Propagation Problems. SIAM J. Sci. Comput. 2012, 34, C210–C231. [Google Scholar] [CrossRef]
- Bezanson, J.; Edelman, A.; Karpinski, S.; Shah, V.B. Julia: A fresh approach to numerical computing. SIAM Rev. 2017, 59, 65–98. [Google Scholar] [CrossRef]
- Numrich, R.W.; Reid, J. Co-Array Fortran for Parallel Programming; ACM Sigplan Fortran Forum; ACM: New York, NY, USA, 1998; Volume 17, pp. 1–31. [Google Scholar]
- Sanner, M.F. Python: A programming language for software integration and development. J. Mol. Graph. Model. 1999, 17, 57–61. [Google Scholar] [PubMed]
- Stroustrup, B. The C++ Programming Language; Pearson Education: London, UK, 2000. [Google Scholar]
- MATLAB. Version 7.10.0 (R2010a); The MathWorks Inc.: Natick, MA, USA, 2010. [Google Scholar]
- Versteeg, H.K.; Malalasekera, W. An Introduction to Computational Fluid Dynamics: The Finite Volume Method; Pearson Education: New York, NY, USA, 2007. [Google Scholar]
- Moin, P. Fundamentals of Engineering Numerical Analysis; Cambridge University Press: New York, NY, USA, 2010. [Google Scholar]
- Strang, G.; Fix, G.J. An analysis of the Finite Element Method; Prentice-Hall: Englewood Cliffs, NJ, USA, 1973; Volume 212. [Google Scholar]
- Canuto, C.; Hussaini, M.Y.; Quarteroni, A.; Thomas, A., Jr. Spectral Methods in Fluid Dynamics; Springer Science & Business Media: Berlin, Germany, 2012. [Google Scholar]
- Strikwerda, J.C. Finite Difference Schemes and Partial Differential Equations; Society for Industrial & Applied Mathmatics (SIAM): Philadelphia, PA, USA, 2004; Volume 88. [Google Scholar]
- Gottlieb, S.; Shu, C.W. Total variation diminishing Runge-Kutta schemes. Math. Comput. Am. Math. Soc. 1998, 67, 73–85. [Google Scholar] [CrossRef]
- Press, W.H.; Teukolsky, S.A.; Vetterling, W.T.; Flannery, B.P. Numerical Recipes in Fortran; Cambridge University Press: New York, NY, USA, 1992; Volume 2. [Google Scholar]
- Lele, S.K. Compact finite difference schemes with spectral-like resolution. J. Comput. Phys. 1992, 103, 16–42. [Google Scholar] [CrossRef]
- LeVeque, R.J. Finite Volume Methods for Hyperbolic Problems; Cambridge University Press: Cambridge, UK, 2002; Volume 31. [Google Scholar]
- Knight, D. Elements of Numerical Methods for Compressible Flows; Cambridge University Press: New York, NY, USA, 2006; Volume 19. [Google Scholar]
- Hirsch, C. Numerical Computation of Internal and External Flows: The Fundamentals of Computational Fluid Dynamics; Butterworth-Heinemann: Burlington, MA, USA, 2007. [Google Scholar]
- Pulliam, T.H. Artificial dissipation models for the Euler equations. AIAA J. 1986, 24, 1931–1940. [Google Scholar] [CrossRef]
- Rahman, S.; San, O. A relaxation filtering approach for two-dimensional Rayleigh–Taylor instability-induced flows. Fluids 2019, 4, 78. [Google Scholar] [CrossRef]
- Jiang, G.S.; Shu, C.W. Efficient implementation of weighted ENO schemes. J. Comput. Phys. 1996, 126, 202–228. [Google Scholar] [CrossRef]
- Ghosh, D.; Baeder, J.D. Compact reconstruction schemes with weighted ENO limiting for hyperbolic conservation laws. SIAM J. Sci. Comput. 2012, 34, A1678–A1706. [Google Scholar] [CrossRef]
- Shu, C.W. High order weighted essentially nonoscillatory schemes for convection dominated problems. SIAM Rev. 2009, 51, 82–126. [Google Scholar] [CrossRef]
- San, O.; Kara, K. Evaluation of Riemann flux solvers for WENO reconstruction schemes: Kelvin–Helmholtz instability. Comput. Fluids 2015, 117, 24–41. [Google Scholar] [CrossRef]
- Rahman, S.M.; San, O. A localised dynamic closure model for Euler turbulence. Int. J. Comput. Fluid Dyn. 2018, 32, 326–378. [Google Scholar] [CrossRef]
- Ruasnov, V. Calculation of intersection of non-steady shock waves with obstacles. USSR Comput. Math. Math. Phys. 1961, 1, 267–279. [Google Scholar]
- Toro, E.F. Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction; Springer Science & Business Media: Berlin, Germany, 2013. [Google Scholar]
- Van Der Burg, J.; Kuerten, J.G.; Zandbergen, P. Improved shock-capturing of Jameson’s scheme for the Euler equations. Int. J. Numer. Methods Fluids 1992, 15, 649–671. [Google Scholar] [CrossRef]
- Godunov, S.K. A difference method for numerical calculation of discontinuous solutions of the equations of hydrodynamics. Mat. Sb. 1959, 89, 271–306. [Google Scholar]
- Roe, P.L. Approximate Riemann solvers, parameter vectors, and difference schemes. J. Comput. Phys. 1981, 43, 357–372. [Google Scholar] [CrossRef]
- Sod, G.A. A survey of several finite difference methods for systems of nonlinear hyperbolic conservation laws. J. Comput. Phys. 1978, 27, 1–31. [Google Scholar] [CrossRef]
- Peng, J.; Zhai, C.; Ni, G.; Yong, H.; Shen, Y. An adaptive characteristic-wise reconstruction WENO-Z scheme for gas dynamic Euler equations. Comput. Fluids 2019, 179, 34–51. [Google Scholar] [CrossRef]
- Harten, A.; Lax, P.D.; van Leer, B. On upstream differencing and Godunov-type schemes for hyperbolic conservation laws. SIAM Rev. 1983, 25, 35–61. [Google Scholar] [CrossRef]
- Gupta, M.M.; Kouatchou, J.; Zhang, J. Comparison of second-and fourth-order discretizations for multigrid Poisson solvers. J. Comput. Phys. 1997, 132, 226–232. [Google Scholar] [CrossRef]
- McAdams, A.; Sifakis, E.; Teran, J. A parallel multigrid Poisson solver for fluids simulation on large grids. In Proceedings of the 2010 ACM SIGGRAPH/Eurographics Symposium on Computer Animation, Madrid, Spain, 2–4 July 2010; pp. 65–74. [Google Scholar]
- Trefethen, L.N.; Bau III, D. Numerical Linear Algebra; Society for Industrial & Applied Mathmatics (SIAM): Philadelphia, PA, USA, 1997; Volume 50. [Google Scholar]
- Barrett, R.; Berry, M.W.; Chan, T.F.; Demmel, J.; Donato, J.; Dongarra, J.; Eijkhout, V.; Pozo, R.; Romine, C.; Van der Vorst, H. Templates for the Solution of Linear Systems: Building Blocks for Iterative Methods; Society for Industrial & Applied Mathmatics (SIAM): Philadelphia, PA, USA, 1994; Volume 43. [Google Scholar]
- Hadjidimos, A. Successive overrelaxation (SOR) and related methods. J. Comput. Appl. Math. 2000, 123, 177–199. [Google Scholar] [CrossRef]
- San, O.; Vedula, P. Generalized deconvolution procedure for structural modeling of turbulence. J. Sci. Comput. 2018, 75, 1187–1206. [Google Scholar] [CrossRef]
- Saad, Y. Iterative Methods for Sparse Linear Systems; Society for Industrial & Applied Mathmatics (SIAM): Philadelphia, PA, USA, 2003; Volume 82. [Google Scholar]
- Arakawa, A. Computational design for long-term numerical integration of the equations of fluid motion: Two-dimensional incompressible flow. Part I. J. Comput. Phys. 1966, 1, 119–143. [Google Scholar] [CrossRef]
- Ghia, U.; Ghia, K.N.; Shin, C. High-Re solutions for incompressible flow using the Navier-Stokes equations and a multigrid method. J. Comput. Phys. 1982, 48, 387–411. [Google Scholar] [CrossRef]
- Hoffmann, K.A.; Chiang, S.T. Computational Fluid Dynamics; Engineering Education System: Wichita, KS, USA, 2000; Volume I. [Google Scholar]
- Meunier, P.; Le Dizes, S.; Leweke, T. Physics of vortex merging. Comptes Rendus Phys. 2005, 6, 431–450. [Google Scholar] [CrossRef]
- San, O.; Staples, A.E. High-order methods for decaying two-dimensional homogeneous isotropic turbulence. Comput. Fluids 2012, 63, 105–127. [Google Scholar] [CrossRef]
- San, O.; Staples, A.E. A coarse-grid projection method for accelerating incompressible flow computations. J. Comput. Phys. 2013, 233, 480–508. [Google Scholar] [CrossRef]
- Reinaud, J.N.; Dritschel, D.G. The critical merger distance between two co-rotating quasi-geostrophic vortices. J. Fluid Mech. 2005, 522, 357–381. [Google Scholar] [CrossRef]
- Orlandi, P. Fluid Flow Phenomena: A Numerical Toolkit; Springer Science & Business Media: Berlin, Germany, 2012; Volume 55. [Google Scholar]
- Mortensen, M.; Langtangen, H.P. High performance Python for direct numerical simulations of turbulent flows. Comput. Phys. Commun. 2016, 203, 53–65. [Google Scholar] [CrossRef]
© 2019 by the authors. Licensee MDPI, Basel, Switzerland. This article is an open access article distributed under the terms and conditions of the Creative Commons Attribution (CC BY) license (http://creativecommons.org/licenses/by/4.0/).

























































