Gallery / Simple example
Two-dimensional Linear Advection Problem
This page shows an example
to numerically solve a two-dimensional linear advection equation
with the discontinuous Galerkin method (DGM) using FElib.
The equation solved is as
\(\begin{align}
\dfrac{\partial u}{\partial t} + \dfrac{\partial (c_x u)}{\partial x} + \dfrac{\partial (c_y u)}{\partial x} = 0 \;\;\; {\rm in} \;\; \Omega \times T
\end{align}\)
where
u=u(x,y,t) is the scalar quantity,
$t \in (0,T]$ for $T < \infty$, $\Omega$ is the computational domain with $0 \le x \le L_x$ and $0 \le y \le L_y$, and the components of advective velocity in x-direction and y-direction are denoted by $c_x$ and $c_y$, respectively.
The initial condition is
\[ u(x,y,0) = u_0(x,y) \;\; x \in \Omega \]
and periodic boundary condition is imposed at $x=0, L_x$ for the x-direction and $y=0, L_y$ for the y-direction.
In this page, we assume that $L_x=L_y=1$. an exponential profile $u_0(x,y) = \exp{[-(x^2+y^2)/R^2}]$ where $R=1/10$ is used as a initial condition. As for the advective velocity, we consider the following two cases: i) a uniform flow with constant velocity with $c_x=c_y=1/\sqrt{2}$ ii) the swirling flow with $c_x=\sin^2{(\pi x)} \sin{(2\pi y)} \cos{(2\pi t)}$ and $c_y=-\sin^2{(\pi y)} \sin{(2\pi x)} \cos{(2\pi t)}$. Note that, for the both cases, the profile at $t=n$ (where $n=1,2,\dots$) should be the same as the initial one if the equation would be exactly solved.
1. Discretization
The computational domain is divided by non-overlapping quadrilateral element $\Omega_e$
whose sides have a width of $h_e$.
In each element, we introduce a local coordinate as $\xi=2(x-x_e)/h_e$ and $\zeta=2(y-y_e)/h_e$.
where $x_e$ and $y_e$ are the center position in the x- and y- directions, respectively.
When the quadrilateral elements are used,
we can represent the numerical solution within $\Omega_e$ with tensor-product manner as
\(\begin{align}
u(\xi,\zeta,t)|\_{\Omega_e} \sim u^e(\xi,t)=\sum_{j1=0}^p \sum_{j2=0}^p U^e_{j1,j2}(\xi,\zeta,t) l_{j1}(\xi) l_{j2}(\zeta)
\end{align}\)
where $l_{j1}(\xi)$ and $l_{j2}(\zeta)$ are the Lagrange polynomials associated with the $j$’s and Lagrange-Gauss-Lobatto (LGL) node.
In the tensor-product formulation,
the discretization strategy for the one-dimensional linear advection problem can be straightforwardly extended to the multi-dimensional one.
The corresponding strong form of the semi-discretized equation with nodal DGM is written as
\(\begin{align}
\dfrac{h_e^2}{4} \int_{-1}^1 \int_{-1}^1 \dfrac{\partial u^e}{\partial t} l_{j1} l_{j2} d\xi d\zeta &+ \int_{-1}^1 \int_{-1}^1 \left[ \dfrac{\partial c_x u^e}{\partial \xi} + \dfrac{\partial c_y u^e}{\partial \xi} \right] l_{j1} l_{j2} d\xi d\eta \nonumber \\
&+ \left[ \int_{-1}^1 ( \widehat{c_x u} - c_x u^e ) d\eta \right]^{\xi=1}_{\xi=-1} + \left[ \int_{-1}^1 ( \widehat{c_y u} - c_y u^e ) d\xi \right]^{\zeta=1}_{\zeta=-1}
= 0
\;\;\;\; (j=0,\cdots,p)
\end{align}\)
where
the numerical flux at element boundary in $x-$ or $y-$ direction is defined as
\(\begin{align}
\widehat{cu}(u^+,u^-) n = \dfrac{c}{2}(u^+ + u^-)n - \beta \dfrac{|c|}{2}(u^+ - u^-)
\end{align}\)
where
$u^+$ and $u^-$ are the value of $u$ at the boundary of neighbor and own elements, respectively.
For the x- and y- directions, $n=n_x, c=c_x$ and $n=n_y, c=c_y$ for the y-direction
(where $n_x$ and $n_y$ are the component of normal vector at element boundary), respectively.
Substituting Eq. (2) to Eq. (3) gives the semi-discretized equation based on the nodal DGM in the matrix-vector form as \(\begin{align} \dfrac{h_e^2}{4} M \dfrac{d \vec{U^e}}{dt} =& -\left[ S_x^T (c_x \vec{U^e}) + S_y^T (c_y \vec{U^e}) \right] \nonumber \\ &- B_x (\widehat{c_x u} - c_x \vec{U^e}) - B_y (\widehat{c_y u} - c_y \vec{U^e}) \end{align}\) where $\vec{U^e}=(U^e_{0,0},\cdots,U^e_{p,p})$, $M$ is the mass matrix, $S_x$ and $S_y$ are the stiffness matrix in $x-$ and $y-$ direction, and $S_{x,1D}$ and $S_{y,1D}$ are the matrix associated with boundary integration in $x-$ and $y-$ direction. If we multiply the both sides of the above equation by $4/h_e^2 M^{-1}$, the differential form is obtained as follows \(\begin{align} \dfrac{d \vec{U^e}}{dt} = - e [ D_x (c_x \vec{U^e}) + D_y (c_y \vec{U^e}) ] - f L (\widehat{cu} - c\vec{U^e}) \end{align}\) where $D=M^{-1} S^T$ is the differential matrix, $L=M^{-1} B$ is the lifting matrix, and $e=f=2/h_e$.
As in the one-dimensional linear advection problem, the temporal discretization for the spatially semi-discretized equation in Eq. (8) is based on the method of lines and we adopt a strong Stability Preserving Runge-Kutta (SSP RK) method with three stages and third order accuracy.
2. Implementation
An implementation of the discretized equation described in the above section is shown below. This source code is essentially the same as that in rootdir/sample/advect2d/test_advect2d.f90.
Source code: test_advect2d.f90
#include "scalelib.h"
program linear_adv_eq
use scale_precision
use scale_io
use scale_prc
use scale_sparsemat
use scale_element_base
use scale_element_quadrilateral
use scale_localmesh_2d
use scale_mesh_rectdom2d
use scale_meshfield_base
use scale_meshfieldcomm_base
use scale_meshfieldcomm_rectdom2d
use scale_file_history_meshfield
use scale_file_history
use scale_time_manager
use scale_timeint_rk
implicit none
!-----------------------------------------------------------------------------
real(RP), parameter :: dom_xmin = 0.0_RP, dom_xmax = 1.0_RP
real(RP), parameter :: dom_ymin = 0.0_RP, dom_ymax = 1.0_RP
real(RP), parameter :: c_x = 1.0_RP, c_y = 1.0_RP
real(RP) :: beta = 1.0_RP
type(QuadrilateralElement) :: refElem
type(SparseMat) :: Dx, Dy, Lift
type(MeshRectDom2D), target :: mesh
type(LocalMesh2D), pointer :: lcmesh
type(MeshField2D), target :: u
type(MeshFieldCommRectDom2D) :: fields_comm
type(MeshFieldContainer) :: field_list(1)
type(timeint_rk), allocatable :: tinteg_lc(:)
integer :: nowstep
integer :: rkstage
integer :: tintbuf_ind
integer, parameter :: RKVAR_U = 1
integer :: n
integer :: ke
integer :: HST_ID
!-----------------------------------------------------------------------------
call init()
call set_initcond()
do nowstep=1, TIME_NSTEP
do rkstage=1, tinteg_lc(1)%nstage
!* Exchange halo data
call fields_comm%Put(field_list, 1)
call fields_comm%Exchange()
call fields_comm%Get(field_list, 1)
!* Update prognostic variables
do n=1, mesh%LOCAL_MESH_NUM
lcmesh => mesh%lcmesh_list(n)
tintbuf_ind = tinteg_lc(n)%tend_buf_indmap(rkstage)
call cal_tend( tinteg_lc(n)%tend_buf2D_ex(:,:,RKVAR_U,tintbuf_ind), &
u%local(n)%val, lcmesh, lcmesh%refElem2D )
call tinteg_lc(n)%Advance( rkstage, u%local(n)%val, RKVAR_U, &
1, lcmesh%refElem2D%Np, lcmesh%NeS, lcmesh%NeE )
end do
end do
!* Advance time
call TIME_manager_advance()
call FILE_HISTORY_set_nowdate( TIME_NOWDATE, TIME_NOWSUBSEC, TIME_NOWSTEP )
!* Output
call FILE_HISTORY_meshfield_put(HST_ID, u)
call FILE_HISTORY_meshfield_write()
end do
call final()
write(*,*) "Final"
contains
subroutine cal_tend( dudt, u_, lmesh, elem )
implicit none
class(LocalMesh2D), intent(in) :: lmesh
class(ElementBase2D), intent(in) :: elem
real(RP), intent(out) :: dudt(elem%Np,lmesh%NeA)
real(RP), intent(in) :: u_(elem%Np,lmesh%NeA)
real(RP) :: Fx(elem%Np), Fy(elem%Np), LiftDelFlx(elem%Np)
real(RP) :: del_flux(elem%NfpTot,lmesh%Ne)
!------------------------------------------------------------------------
call cal_elembnd_flux( del_flux, & ! (out)
u_, lmesh%normal_fn(:,:,1), lmesh%normal_fn(:,:,2), & ! (in)
lmesh%vmapM, lmesh%vmapP, lmesh, elem ) ! (in)
do ke = lmesh%NeS, lmesh%NeE
call sparsemat_matmul(Dx, c_x * u_(:,ke), Fx)
call sparsemat_matmul(Dy, c_y * u_(:,ke), Fy)
call sparsemat_matmul(Lift, lmesh%Fscale(:,ke) * del_flux(:,ke), LiftDelFlx)
dudt(:,ke) = - ( lmesh%Escale(:,ke,1,1) * Fx(:) &
+ lmesh%Escale(:,ke,2,2) * Fy(:) &
+ LiftDelFlx(:) )
end do
return
end subroutine cal_tend
subroutine cal_elembnd_flux( del_flux, u_, nx, ny, vmapM, vmapP, lmesh, elem )
implicit none
class(LocalMesh2D), intent(in) :: lmesh
class(ElementBase2D), intent(in) :: elem
real(RP), intent(out) :: del_flux(elem%NfpTot*lmesh%Ne)
real(RP), intent(in) :: u_(elem%Np*lmesh%NeA)
real(RP), intent(in) :: nx(elem%NfpTot*lmesh%Ne)
real(RP), intent(in) :: ny(elem%NfpTot*lmesh%Ne)
integer, intent(in) :: vmapM(elem%NfpTot*lmesh%Ne)
integer, intent(in) :: vmapP(elem%NfpTot*lmesh%Ne)
integer :: i, iP, iM
real(RP) :: vel
!------------------------------------------------------------------------
do i=1, elem%NfpTot*lmesh%Ne
iM = vmapM(i); iP = vmapP(i)
vel = c_x * nx(i) + c_y * ny(i)
del_flux(i) = 0.5_RP * ( vel * ( u_(iP) - u_(iM) ) &
- beta * abs(vel) * ( u_(iP) - u_(iM) ) )
end do
return
end subroutine cal_elembnd_flux
subroutine set_initcond()
use scale_const, only: PI => CONST_PI
implicit none
!-------------------------------------------------
do n=1, mesh%LOCAL_MESH_NUM
lcmesh => mesh%lcmesh_list(n)
do ke=lcmesh%NeS, lcmesh%NeE
u%local(n)%val(:,ke) = sin( 4.0_RP * PI * lcmesh%pos_en(:,ke,1) / ( dom_xmax - dom_xmin ) ) &
* sin( 4.0_RP * PI * lcmesh%pos_en(:,ke,2) / ( dom_ymax - dom_ymin ) )
end do
end do
call FILE_HISTORY_meshfield_put(HST_ID, u)
call FILE_HISTORY_meshfield_write()
return
end subroutine set_initcond
subroutine init()
use scale_calendar
implicit none
integer :: NeGX = 8, NeGY = 8
integer :: NprcX = 1, NprcY = 1
integer :: PolyOrder = 4
character(len=H_MID) :: TINTEG_SCHEME_TYPE = 'ERK_SSP_3s3o'
namelist /PARAM_ADVECT2D/ &
NprcX, NeGX, NprcY, NeGY, &
PolyOrder, &
TINTEG_SCHEME_TYPE, &
beta
integer :: comm, myrank, nprocs
logical :: ismaster
integer :: ierr
!---------------------------------
call PRC_MPIstart( comm )
call PRC_SINGLECOM_setup( comm, & ! [IN]
nprocs, myrank, ismaster ) ! [OUT]
call PRC_ERRHANDLER_setup( .false., ismaster ) ! [IN]
call IO_setup( "advect2d.conf", allow_noconf = .true. )
call IO_LOG_setup( myrank, ismaster )
rewind(IO_FID_CONF)
read(IO_FID_CONF,nml=PARAM_ADVECT2D,iostat=ierr)
if( ierr > 0 ) then !--- fatal error
LOG_ERROR("init",*) 'Not appropriate names in namelist PARAM_TEST. Check!'
call PRC_abort
endif
LOG_NML(PARAM_ADVECT2D)
call CALENDAR_setup
call TIME_manager_Init
!--
call refElem%Init( PolyOrder, .false. )
call Dx%Init( refElem%Dx1 )
call Dy%Init( refElem%Dx2 )
call Lift%Init( refElem%Lift )
call mesh%Init( NeGX, NeGY, dom_xmin, dom_xmax, dom_ymin, dom_ymax, &
.true., .true., refElem, 1, NprcX, NprcY )
call mesh%Generate()
call u%Init( "u", "1", mesh )
call fields_comm%Init( 1, 0, mesh )
field_list(1)%field2d => u
call FILE_HISTORY_meshfield_setup( mesh2D_=mesh )
call FILE_HISTORY_reg( u%varname, "u", u%unit, HST_ID, dim_type='XY')
allocate( tinteg_lc(mesh%LOCAL_MESH_NUM) )
do n=1, mesh%LOCAL_MESH_NUM
lcmesh => mesh%lcmesh_list(n)
call tinteg_lc(n)%Init( TINTEG_SCHEME_TYPE, TIME_DTSEC, 1, &
2, (/ lcmesh%refElem2D%Np, lcmesh%NeA /) )
end do
return
end subroutine init
subroutine final()
implicit none
!---------------------------------
call FILE_HISTORY_meshfield_finalize()
call u%Final()
call fields_comm%Final()
call mesh%Final()
call Dx%Final(); call Dy%Final(); call Lift%Final()
call refElem%Final()
call TIME_manager_Final()
call PRC_MPIfinish()
return
end subroutine final
end program linear_adv_eq
3. Build and Execution
-
Build FElib following install document. Note that the environment variables set here are also used in subsequent steps.
-
Make a new directory in rootdir/sample/, and copy the following four files:
-
Execute
% make
to compile test_advect2d.f90. After this step, a binary file (test_advect1d) will be obtained. -
Execute
% make run
to run the program with advect2d.conf. If this step succeeds, history.pe000000.nc will be output. -
Execute
% make vis
to visualize the simulation result. By this command, the python script will generate advect2D.mp4.
Simulation paramters in advect2d.conf are configured as folllows:
-
As for the spatial discretization, the number of element is $16\times 16$ and the polynominal order ($p$) is 3.
-
The integration period is $T=5$ and the timestep is $\Delta t=0.005$.
4. Result
Although the details of numerical behavior associated with high-order DGM are not described here (see excercise described below), we can verify the $L^2$ error norm for $p=3$ and the linear advection equation with $C^\infty$ initial profile decreases with $O(h_e^4)$ (i.e., the fourth-order accuracy) for significantly small $\Delta t$ when the element size decreases. This convergence rate is mainly related to the interpolation.
5. Exercise
- Change initial condition
- Investigate convergence rate of numerical solution
- Confirm effect of disspation with full upwinding numerical flux
- Change temporal scheme
6. Reference
- Hesthaven, J. S., and T. Warburton, 2007: Nodal discontinuous Galerkin methods: algorithms, analysis, and applications, Springer Science & Business Media