In this project we will explore how to simulate 2-dimensional forced turbulence
by means of the pseudo-spectral method.
I will give a coarse introduction to the physical models of 2-dimensional turbulence followed by a discussion on the numerical methods suitable to efficiently simulate this natural phenomenon.
The numerical methods discussed here are implemented in the python module pyTurb, which is available at my github page. To produce simulations with a practically relevant resolution, pyTurb uses the module cupy to accelerate the code on a single GPU.
Above we see a simulation produced with pyTurb with a resolution of 1024 points in each spatial direction. Shown is the vorticity field forced from rest at a wavenumber of . For the shown 80 turn over times it took about 75 seconds on a A100 Nvidia GPU.
In this section we describe how to run a simulation with pyTurb.
The pyTurb project can be downloaded in the following ways:
ssh:
git clone https://github.com/MikeWilbert/pyTurb.git
https:
git clone git@github.com:MikeWilbert/pyTurb.git
To start a first test run go to the code directory
cd pyturb_2d/code
There you will find the python module pyTurb as well as an example code that usesthe module to run a simulation. Additionally you will find the files plot_stats.py and plot_spectra.py which display makroscopic fluid quantities and the energy spectra produced printed during the simulations, respectively.
Before you run the execution script with
python NS_GPU.py
you might want to change the output directory as well as the resolution. You can also change other parameters. The patrameters for the simulations are described in detail in the next section.
In the following the functions included in pyTurb are briefly described.
init(N_, k_a_, k_f_, dk_f_ c_res_, out_dir_)Reads the parameters necessary to specify the simulation. This function needs to be called before any other pyTurb function.
| parameter | description |
|---|---|
N_ |
# of grid points per direction |
k_a_ |
linear friction wavenumber |
k_f_ |
middle of forcing wavenumber band |
dk_f_ |
width of forcing wavenumber band |
c_res_ |
ratio of maximum resolved wavenumber to diffusion wavenumber . Good value: 3, acceptable value: 1.5 |
out_dir_ |
output directory |
step()Performs a single time step.
print_vtk()Prints the vorticity field in the vtk format to the output directory specified in the init function. That format constits of an XML header which describes the following binary data stored in the file. VTK files can be read e.g. by Paraview for visualization purposes.
print_spectrum()Prints the energy spectrum and the corresponding simulation time to the file 'spectra.csv' that can be found in the output directory specified in the init function.
print_stats()Prints the mean energy and dissipation rate and the corresponding simulation time to the file 'stats.csv' that can be found in the output directory specified in the init function.
For an example file to run a simulation with pyTurb we refer to the file 'code/NS_GPU.py'
In this section, the basic ideas and formulas behind the theory of 2-dimensional turbulence will be discussed.
We start by considering the incompressible Navier-Stokes equations with an additional linear friction and a random force:
| term | name | function |
|---|---|---|
| convection / non-linearity | veclocity transports itself | |
| pressure gradient | enforces incompressibility | |
| viscous diffusion | dissipates energy at small scales | |
| linear friction | removes energy at large scales | |
| random force | inserts energy at a prescribed scale |
If we consider only 2-dimensional systems, it is more practical to formulate the incompressible Navier-Stokes equations in terms of the vorticity .
Before taking the curl of the momentum equation (1), it is usefull to reformulate the non-linearity by applying the identity
With that we find an evolution equation for the vorticity
Note that the vorticity is divergence free by definition . Thus, no pressure term is needed in this formulation.
If we consider only 2 spatial dimensions, equations (3) can be simplified even further.
Assume the fluid velocity is restricted to the x-y plane, then the vorticity vector only has a non-zero component in the z-direction .
Using the vector identity
$$ \begin{aligned} \nabla \times ( \mathbf{A} \times \mathbf{B} ) &= \mathbf{B} \cdot \nabla \mathbf{A} - \mathbf{A} \cdot \nabla \mathbf{B} \\ \quad &+ \mathbf{A} ( \nabla \cdot \mathbf{B} ) - \mathbf{B} ( \nabla \cdot \mathbf{A} ) \end{aligned} $$
and the solenoidality of the velocity and the vorticity , we can write the non-linear term as
In the 2-dimensional case, the first term on the right-hand-side vanishes and we find the 2-dimensional version of the vorticity equation
Note that this is only a scalar equation for the z-component of the vorticity.
Since the velocity field is divergence-free, it can also be written as the curl of a vector potential. In the 2-dimensional case this vector potential only has z-component that we wil call the stream function . As for the vorticity in 2D, we write .
By expressing the veclocity by the stream function we find the stream fruntion formulation of the 2D vorticity equation
Finally, it would be more consistent to express the stream function by the vorticity instead of the velocity vector .
This can be achieved by applying the vector identity to the definitions of and .
Since the only has one component in the z-direction and is restricted to the x-y plane, it has zero divergence and we end up with
Equations (5) and (6) are the equations we are interested to solve.
In 3-dimensional turbulence, the theory by Kolmogorov from 1941 is still most prominent.
In a nutshell, it states that if energy is put into the system by some forcing mechanism, the non-linearity breaks up the eddies of this inertial scale into smaller eddys, which by themselfes break up into even smaller structures and so on. This is known as the Richardson cascade.
The Reynolds number , which denotes the ratio of the non-linear term to the diffusion, is typically a very high number, so that diffusion can be neglected in the cascade. But since scales with the cosidered length scale, after a sufficient number of break ups, the eddies will be so small, that they feel the viscous friction and their energy dissipates into heat.
Kolmogorov predicted a kinetic energy spectrum of the form
and also gave the spacial and temporal scales where the diffusion sets in.
For a more detailed and quantitative discussion about 3D turbulence, please consider the book by Lautrup or any other basic fluid dynamics book.
In the 2-dimensional case, the turbulent cascade changes its character in two ways. The basic theory for 2D turbulence was created by Kraichnan in his 1967 paper.
First of all, the energy is transported to larger scales instead of smaller scales. This is called the inverse energy cascade. The inverse cascade obeys the same power law as in 3D Kolmogorov theory with a coefficient of . Since diffusion will only remove energy from the system at small scales, there is a need for an energy sink at large scales. In numerical simulations it is therefor common practice to introduce a linear friction term, which performs this task.
The other special feature in 2D turbulence is the enstrophy cascade. Enstrophy is the analog to energy for the voriticvity, that is some measure of swirliness. This is a downward cascade and scales approximately as
although this slope should not be considered too strict, as there are also other theoretical predictions, e.g. slopes with or . Kraichnan himself wrote in his original paper that the slope "must be modified by factors with logarithmic dependence". For a discussion on the slope of the enstrophy cascade we refer to sections 9 & 10 of the review by Tabling.
Since numerical simulations are restricted to a finite range of scales, it is very important to know the relevant spatial scales that have to be resolved. In the case of 2D turbulence, there are 3 important scales.
There is the foring scale associated with a wavenumber at which the energy is injected into the system. This scale depends on the choice of the forcing term.
Then there is the dissipation scale, at which diffusion sets in and removes enstrophy from the system. This scale is associated with the diffusion term and especially with the viscosity .
Finally, we have the friction scale at which energy is removed from the system at large wavenumbers at the end of the inverse cascade. This is due to the linear friction force and will be a function of the friction coefficient .
Consider a differenctial equation, where the velocity is only changed by the linear friction force
Then we find a solution of the form
Here we see, that the coefficient acts as the inverse of the time scale the linear friction acts on.
By conservation of energy (see e.g. the original paper by Kraichnan) the maximum spatial scale reached by the inverse cascade as a function of time is given by
denotes the energy dissipation rate.
Setting as the inverse time in this formula leads an expression for the friction scale
By means of that relation we can specify the friction scale by setting the friction coefficient as
The energy dissipation must be equal to the energy production rate induced by the forcing, which we can specify explcitely by construction of the forcing term, as we will see later in this document.
In the original paper by Kraichnan we find an expression for the dissipation scale
By assuming the relation between the dissipation rate of energy and the entsrophy dissipation rate
which makes sense when we look at the definition of those two quantities by their relation to the enrgy spectrum, we find an expression for the viscosity as a function of the forcing wavenumber and the dissipation scale .
In summary, we can provide the three relevant scales , and and use the above relations to scale the forces in such a way that the evolving turbulence is restricted to this finite range of scales.
In this section we will consider the numerical methods used in pyTurb to solve equations (5) & (6).
pyTurb is based the Fourier pseudo-spectral approach, which is very suitable for problems on periodic domains with infititely smooths solutions.
The main trick of the Fourier pseudo-spectral method is to exploit the fact that derivatives in physical space become multiplications with the wavevector in Fourier space.
This can be easily shown for the one-dimensional case.
The basic idea of the Fourier transform is that every complex and integrable function can be described by a superposition of plane waves, i.e.
is known as the inverse Fourier transform.
Here, the complex amplitude , which includes the amplitude and phase of the corresponding plane wave, is called the Fourier coefficient of .
For all real \( k \), the complex exponentials \( \exp(i k x) \) are orthogonal, and the Fourier coefficients can be expressed as
$$ \hat{f}(k) = \frac{1}{2 \pi} \int f(x) \, \exp(-i k x) \, dx =: \mathcal{F}(f)(k) $$
\( \mathcal{F} \) is then called the Fourier transform.
Now, consider the derivative of a function. Then we find
$$ \begin{align*} f'(x) &= \frac{d}{dx} \int \hat{f}(k) \, e^{i k x} \, dk \\ &= \int i k \, \hat{f}(k) \, e^{i k x} \, dk \\ &= \mathcal{F}^{-1}\big(i k \hat{f}(k)\big) = \mathcal{F}^{-1}\big(i k \mathcal{F}(f)(k)\big) \end{align*} $$
Thus, to take the derivative of f, we can transform to Fourtier space, multiply with and then transform back to physical space. This procedure can easily be extended to vector valued functions:
$$ \begin{align*} \mathcal{F}(\nabla \phi(\mathbf{x})) &= i \mathbf{k} \hat{\phi}(k) \\ \mathcal{F}(\nabla \cdot \mathbf{u}(\mathbf{x})) &= i \mathbf{k} \cdot \hat{\mathbf{u}}(k) \\ \mathcal{F}(\nabla \times \mathbf{u}(\mathbf{x})) &= i \mathbf{k} \times \hat{\mathbf{u}}(k) \\ \mathcal{F}(\Delta \mathbf{u}(\mathbf{x})) &= - |\mathbf{k}|^2 \hat{\mathbf{u}}(k) \end{align*} $$
Note also, that in Fourier space the Poisson equation can be solved quiet easily as the Laplace operator can be simply inverted by dividing by .
Since we want to solve equations (5) & (6) on a computer, we are restricted to a finite number spacial samples of and thus we will deal with a finite number of Fourier modes. This leads from the Fourier integral to the discrete Fourier transform (DFT).
Let's consider the finite space interval and discretize it by points in space that are an equal distance apart. By now evalutating at these discrete points , with , we define the discrete Fourier transform and its inverse as
The DFT assumes that the function can be described by a finite number of plane waves, which are infinetly smooths, periodic functions. Therefore, also needs to be periodic and infintely smooth. If that assumption is not fullfilled, it will show in the form of unphysical oscillations or a refelction of unresolvable modes into the spectrum.
The naive computation of the DFT gives a number of operations of the order (every with every ). This will become very expensive in terms of computation time if we want to compute high resolutions in multiple dimensions. This unconvencience is overcome by an algorithms called the Fast Fourier Transform (FFT), which achieves a number of operations of , i.e. nearly optimal, by utilizing a divide-and-conquer approach based on the periodicty of the Fourier base.
So far, the pseudo-spectral method can be summarized as follows: Compute the right-hand-side of equation (6) at discrete equidistant points by transforming the initial data to Fourier space using the FFT and compute the derivatives by multiplications with the wavevector. If the r.h.s. is evaluated this leaves us with an ordinary differential equation in time, for that a great variety of numerical methods exists. Also the stream function can easiy be computed in Fourier space by inverting the Laplace operator.
The only thing we have not considered yet is the non-linear term. In our case it consists of the multiplication of two gradients. Unfortunately, multiplications in Fourier space become convultions in physical space. In discrete space, this is again an operation of order . This can be avoided by first calculating the derivatives in Fourier space, then transforming to physical space and perform the multiplications there. Since we use the FFT for the transformations, we are back at .
The main idea of the pseudo-spectral method can be summarized as follows:
Compute derivatives in Fourier space, calculate multiplications in real space and transform between those two views by the efficient FFT.
Another issue associated with the multiplication in real space is that we need to double the number of Fourier modes to properly represent the result.
$$ \begin{align*} f_j \, g_j &= \left( \sum_{k=-N/2}^{N/2-1} \hat{f}_k \, e^{i k x_j} \right) \left( \sum_{m=-N/2}^{N/2-1} \hat{g}_m \, e^{i m x_j} \right)\\ &= \sum_{k,m=-N/2}^{N/2-1} \hat{f}_k \hat{g}_m \, e^{i (k+m) x_j}. \end{align*} $$
Thus, the resulting interval of discrete wavenumbers will be . But we will surely not double the spatial resultion after every multiplication. Keeping the resultion fixed to the wavenumbers then results in the unresolved wavenumbers being refected back into the other side of the resolvable spectrum, producing erroneous results. This can be seen by the following consideration.
$$ \begin{align*} \exp(i \, x_j \, k) &= \exp\Big(i \, 2\pi \frac{j k}{N}\Big) \cdot 1 \\ &= \exp\Big(i \, 2\pi \frac{j k}{N}\Big) \cdot \exp(\pm i \, 2\pi j) \\ &= \exp\Big(i \, x_j \, (k \pm N)\Big) \end{align*} $$
E.g. if we have , the resolvable wavenumbers are in the discrete interval . The wavenumber interval produced by the multiplication is . The amplitude will then be added to the amplitude .
To circumvent this effect, we need to apply methods known as dealiasing methods. One of the most polular of these methods is the 2/3-rule proposed by Orszag in 1971.
The method constists of deleting the highest 3rd of the spectrum and pad it with zeros instead. Then the multiplication is performed and afterwards the upper third of the spectrum of the result is padded again. Thereby the non-zero modes are only reflected into the upper third of the spectrum, and do not get mixed up with the correctly resolved modes. In the last step the faulty modes can simply be deleted.
This method completely removes the aliaing error by the cost of reducing the resolution by a factor of 2/3. In higher dimensions the loss is even greater. Although this sounds pretty dramatic, the high accuracy and efficiency of the pseudo-spectral method is still superior compared to other numerical methods.
More information about dealiasing methods and the pseudo-spectral method in general can be found e.g. in the book by Canuto.
Now that we have an efficient method at hand to evaluate the r.h.s. of the vorticity equation (6), we should think about how to advance the solution in time. This comes down to the numerical solution of ordinary differential equations (ODEs).
Let's look at a one-dimensional ODE of the general form
Usually we know at some start time and want to know it at some final later time . Applying the Taylor expansion to gives
Truncating to second order will only give an acceptable error if the time interval is sufficiently small, which is generally not given.
To overcome this difficulty, the time interval can be equally divided into parts. This givies the discretization . Each discrete time is then seperated by the next time by a time interval and we have . Now we can use the truncated Taylor expansion above to move from one discrete time to the next until we have covered the whole interval of interest. If is high enough, then might be small enough that the error in each time step is small enough to get a reasonablly good approximation to .
Each time step is then given by
with being an approximation to . This method is called the Euler forward method. Each time step introduces an error of the order of . Thus, it is said that the Euler forward method has a local truncation error of order 2.
But actually we are interested to arrive at . To do so we must perform Euler steps, each introducing an error of order . Since the number of steps depends also on the time interval as , the error to the final result is of order . Therefore, the forward Euler method is said to have a global truncation error of order 1.
This low order of accuracy implies that the we need a quiet high number of time steps to get an acceptable result, which in turn might be very expensive in terms of computation time. It is therefore desirable to create methods with higher order. The most common class of methods achieving arbitrary order of convergence
is the class of the so-called Runge-Kutta methods.
The basic idea of these methods is no use the Euler method as a starting point to get an approximation of at some point in the time interval . This gives two values in that interval that can then be combined to give a better approximation to a third one and so on. By combining the approximations in the time step interval in a clever way, methods of arbitarry order and possibly great stability can be created.
As an example, let us consider Heun's method. It is a second order method given by
In Heun's method the function is approximated at the end of the time step interval by the Euler forward method. Thus we have expresison for at the left and the right border of the interval and can use their mean value to approximate in the middle of the time step interval.
The method used in the module pyTurb is the SSPRK3 method, short for strong stability preserving Runge-Kutta method of order 3. It consists of the following steps:
$$ \begin{align*} y_1 &= y^n + \Delta t \, f(y^n, t^n) \\ y_2 &= y^n + \frac{\Delta t}{2} \, \frac{f(y^n, t^n) + f(y_1, t^{n+1})}{2} \\ y^{n+1} &= y^n + \Delta t \, \\ &\frac{f(y^n, t^n) + 4\,f(y_2, t^{n+1/2}) + f(y_1, t^{n+1})}{6} \end{align*} $$
The SSPRK3 method thus uses Heun's method to get approximations to at the center and the right edge of the time step interval and combines them to a method of order 3.
So far, we have only considered the order of accuracy of the time integration schemes. Another important porperty of a numerical integration method is its stability. Stability refers to the fact that the errors made in each time step can amplify each other so that an unphysical exponential rise in the numerical approximation will occur if the time step is chosen too large.
For the SSPRK3 method in 2 dimensions, which is used in pyTurb, the stability condition reads
with
gives refers to the advection and to the diffusion.
This stability criterion can be obtained by applying a Fourier transform to the advection-diffusion equation,which serves as a model problem. For more information we refer to the project by Marin Lauber.
From the above stability condition, we observe that the time step depends on the grid spacing . More specific, for the advection part, we have and for the diffusion we find . We see that the stability condition arising from the diffusion is a way stronger restrcition on the spatial resolution.
To overcome this difficulty with the diffusion term, we use the fact that the pure diffusion equation can be solved analytically in Fourier space. This allows us to formulate an ansatz by solving the diffusion term explicitely and thereby ommitting the stability restriction due to diffusion.
The diffusion equation in Fourier space is given by
where for sake of simplicity we ommit the hat symbol over the vorticity symbol .
The solution of this equation is simply given by
Now we write the vorticity equation (5) in the form
with including the non-linear term and additional forces.
The solution of the diffusion equation motivates the ansatz
Comparing the partial time derivative of the above equation with the general form of the vorticity equation, we find
Thus, we can treat the diffusion anaylitically and solve for the other terms with a numerical method by advancing in time and afterwards computing . By that precedure, we get rid of the stability condition imposed by the diffusion term.
Note that the time derivative of depends on .
For the implementation of this ansatz for the SSPRRK3 solver please take a look at the file pyTurb.py.
Finally, to simulate 2D turbulence in an equilibrium state we have to define a forcing term that drives the turbulence. The approach used in pyTurb is to generate Gaussian white noise in a certain band of wavenumbers. The strength of the foring can be explcitely set by calculating the energy in the band-passed white noise and rescale the forcing to inject a predefined amount of energy. The timescale of the forcing is set to . This means that the forcing is the same in every Runge-Kutta substep.
Since we can choose the energy injection rate freely, we will set it in such a way that the time scale of the simulations will be equal to the eddy turnover time associated with the energy injection. In analogy with 3-dimensional turbulence, we will call it the large eddy turnover time , altough it does not refer to the largest eddies in 2-dimensional turbulence.
can be defined by
with the characteristic length and velocity scales and respectively.
Additionally, we can approximate the energy injection rate by
Combining the two above relations, we find an expresseion for the large eddy turnover time as a function of the forcing only.
Thus, by setting
we will have in our simulations.