pf_imk.f90 Source File

Implicit Munthe-Kass Runge-Kutta sweeper


This file depends on

sourcefile~~pf_imk.f90~~EfferentGraph sourcefile~pf_imk.f90 pf_imk.f90 sourcefile~pf_timer.f90 pf_timer.f90 sourcefile~pf_imk.f90->sourcefile~pf_timer.f90 sourcefile~pf_dtype.f90 pf_dtype.f90 sourcefile~pf_imk.f90->sourcefile~pf_dtype.f90 sourcefile~pf_hooks.f90 pf_hooks.f90 sourcefile~pf_imk.f90->sourcefile~pf_hooks.f90 sourcefile~pf_utils.f90 pf_utils.f90 sourcefile~pf_imk.f90->sourcefile~pf_utils.f90 sourcefile~pf_timer.f90->sourcefile~pf_dtype.f90 sourcefile~pf_hooks.f90->sourcefile~pf_timer.f90 sourcefile~pf_hooks.f90->sourcefile~pf_dtype.f90 sourcefile~pf_utils.f90->sourcefile~pf_timer.f90 sourcefile~pf_utils.f90->sourcefile~pf_dtype.f90

Contents

Source Code


Source Code

!!  Implicit Munthe-Kass Runge-Kutta sweeper
!
! This file is part of LIBPFASST.
!
!>  This module implements fully implicit Munthe-Kaas Runge Kutta methods using explicit SDC sweeping
!!
!!  The equation to be solved is 
!! 
!! $$ y'=A(y,t)y  $$
!!
!! where \(A\) is a matrix and \(y)\ is  a vector or matrix or if Lax_pair = true
!!
!! $$Y'=[A(Y,t),Y]$$ where both \(A\) and \(Y\) are matrices
!!
!!  We solve this by finding the solution to
!!
!!  $$Q' = dexpinv_Q(A)$$
!!
!!  Using PFASST
module pf_mod_imk
  use pf_mod_dtype
  use pf_mod_utils
  implicit none

  !>  Implicit Munthe-Kaas Runge-Kutta sweeper type, extends abstract sweeper
  type, extends(pf_sweeper_t), abstract :: pf_imk_t
     class(pf_encap_t), allocatable :: A(:)
     real(pfdp), allocatable :: QtilE(:,:)
     real(pfdp), allocatable :: dtsdc(:)
     real(pfdp), allocatable :: tsdc(:)
     real(pfdp), allocatable :: QdiffE(:,:)  !!  qmat-QtilE
     real(pfdp) :: bernoullis(20), t0, dt
     integer ::  qtype, nterms
     logical ::  Lax_pair, use_SDC, debug, mkrk, rk
  contains
    procedure :: sweep        => imk_sweep
    procedure :: initialize   => imk_initialize
    procedure :: evaluate     => imk_evaluate
    procedure :: integrate    => imk_integrate
    procedure :: residual     => imk_residual
    procedure :: spreadq0     => imk_spreadq0
    procedure :: evaluate_all => imk_evaluate_all
    procedure :: imk_destroy
    procedure(pf_f_eval_p), deferred :: f_eval
    procedure(pf_dexpinv_p), deferred :: dexpinv
    procedure(pf_propagate_p), deferred :: propagate
    procedure(pf_commutator_p), deferred :: commutator_p
 end type pf_imk_t

 interface
    !>  Subroutine f_eval computes A(y,t)
     subroutine pf_f_eval_p(this, y, t, level, f)
       import pf_imk_t, pf_encap_t, c_int, pfdp
       class(pf_imk_t),   intent(inout) :: this
       class(pf_encap_t), intent(inout) :: y, f
       real(pfdp),        intent(in   ) :: t
       integer(c_int),    intent(in   ) :: level
     end subroutine pf_f_eval_p
    !>  Subroutine dexpinv computes Om'=F=dexpinv_Om(A)
     subroutine pf_dexpinv_p(this, a, omega, f)
       import pf_imk_t, pf_encap_t, c_int, pfdp
       class(pf_imk_t), intent(inout) :: this
       class(pf_encap_t), intent(inout) :: a
       class(pf_encap_t), intent(inout) :: omega
       class(pf_encap_t), intent(inout) :: f       !!  The resultign-level
     end subroutine pf_dexpinv_p
    !>  Subroutine propagate   computes y_m=expm(Om_m)y_0(expm(Om_m))-1 or (expm(Om_m))y_0 or
     subroutine pf_propagate_p(this, q0, q)
       import pf_imk_t, pf_encap_t, c_int, pfdp
       class(pf_imk_t), intent(inout) :: this
       class(pf_encap_t), intent(inout) :: q, q0
     end subroutine pf_propagate_p
     subroutine pf_commutator_p(this, a, b, out, flags)
       import pf_imk_t, pf_encap_t, c_int, pfdp
       class(pf_imk_t), intent(inout) :: this
       class(pf_encap_t), intent(inout) :: a, b, out
       integer, intent(in), optional :: flags
     end subroutine pf_commutator_p
  end interface

contains
  !> Perform nsweep  sweeps on level  and set qend appropriately.
  ! with the two-array encap, things are a little tricky
  ! copy default behavior : copy the solution only
  ! copy flagged behavior : copy the name of the encap
  ! setval default behavior : set the value of the name of the encap
  ! setval flagged behavior : set the value of the solution
  subroutine imk_sweep(this, pf, level_index, t0, dt, nsweeps, flags)
    use pf_mod_timer
    use pf_mod_hooks

    !>  Inputs
    class(pf_imk_t), intent(inout) :: this
    type(pf_pfasst_t), intent(inout),target :: pf      !!  PFASST structure
    integer,             intent(in)    :: level_index  !!  which level to sweep on
    real(pfdp),        intent(in   ) :: t0             !!  Time at beginning of time step
    real(pfdp),        intent(in   ) :: dt             !!  time step size
    integer,             intent(in)    :: nsweeps      !!  number of sweeps to do
    integer, optional,   intent(in)    :: flags

    !>  Local variables
    class(pf_level_t), pointer :: lev    !!  points to current level

    this%t0 = t0
    this%dt = dt
    if (this%rk) then
       call rk_step(this, pf, t0, dt)
    else if (this%mkrk) then
       call mkrk_step(this, pf, t0, dt)
    else
       call imk_actually_sweep(this, pf, level_index, t0, dt, nsweeps)
    end if
  end subroutine imk_sweep

  subroutine rk_step(this, pf, t0, dt)
    use pf_mod_timer
    use pf_mod_hooks

    !>  Inputs
    class(pf_imk_t), intent(inout) :: this
    type(pf_pfasst_t), intent(inout),target :: pf      !!  PFASST structure
    real(pfdp),        intent(in   ) :: t0             !!  Time at beginning of time step
    real(pfdp),        intent(in   ) :: dt             !!  time step size

    !>  Local variables
    class(pf_level_t), pointer :: lev    !!  points to current level

    integer     :: m        !!  Loop variables
    real(pfdp)  :: t        !!  Time at nodes

    t = t0 + dt

    lev => pf%levels(1)
    do m = 1, 5
       call lev%Q(m)%setval(0.0_pfdp)
    end do

    call call_hooks(pf, 1, PF_PRE_SWEEP)

    call lev%Q(1)%copy(lev%q0, flags=1)
    call this%f_eval(lev%Q(1), t, lev%index, this%A(1))
    ! commutator_p flags=1 hack copies Q(1)%y -> Q(1)%array
    ! all subsequent RK stages are done on Q(m)%array
    call this%commutator_p(this%A(1), lev%Q(1), lev%F(1,1), flags=1)

    call lev%Q(2)%axpy(1.0_pfdp, lev%Q(1))
    call lev%Q(2)%axpy(0.5_pfdp*dt, lev%F(1,1))
    call this%f_eval(lev%Q(2), t, lev%index, this%A(2))
    call this%commutator_p(this%A(2), lev%Q(2), lev%F(2,1))

    call lev%Q(3)%axpy(1.0_pfdp, lev%Q(1))
    call lev%Q(3)%axpy(0.5_pfdp*dt, lev%F(2,1))
    call this%f_eval(lev%Q(3), t, lev%index, this%A(3))
    call this%commutator_p(this%A(3), lev%Q(3), lev%F(3,1))

    call lev%Q(4)%axpy(1.0_pfdp, lev%Q(1))
    call lev%Q(4)%axpy(dt, lev%F(3,1))
    call this%f_eval(lev%Q(4), t, lev%index, this%A(4))
    call this%commutator_p(this%A(4), lev%Q(4), lev%F(4,1))

    call lev%Q(5)%axpy(1.0_pfdp, lev%Q(1))
    call lev%Q(5)%axpy(dt/6.0_pfdp, lev%F(1,1))
    call lev%Q(5)%axpy(dt/3.0_pfdp, lev%F(2,1))
    call lev%Q(5)%axpy(dt/3.0_pfdp, lev%F(3,1))
    call lev%Q(5)%axpy(dt/6.0_pfdp, lev%F(4,1))

    ! commutator_p flags=2 hack copies Q(m)%array -> Q(m)%y
    ! only the Q argument in this case matters
    ! in order to get solution back to qend
    call this%commutator_p(this%A(1), lev%Q(1), lev%F(1,1), flags=2)
    call this%commutator_p(this%A(2), lev%Q(2), lev%F(1,1), flags=2)
    call this%commutator_p(this%A(3), lev%Q(3), lev%F(1,1), flags=2)
    call this%commutator_p(this%A(4), lev%Q(4), lev%F(1,1), flags=2)
    call this%commutator_p(this%A(5), lev%Q(5), lev%F(1,1), flags=2)

    call pf_residual(pf, lev, dt)
    call lev%qend%copy(lev%Q(lev%nnodes), flags=1)

    call call_hooks(pf, 1, PF_POST_SWEEP)
  end subroutine rk_step

  subroutine mkrk_step(this, pf, t0, dt)
    use pf_mod_timer
    use pf_mod_hooks

    !>  Inputs
    class(pf_imk_t), intent(inout) :: this
    type(pf_pfasst_t), intent(inout),target :: pf      !!  PFASST structure
    real(pfdp),        intent(in   ) :: t0             !!  Time at beginning of time step
    real(pfdp),        intent(in   ) :: dt             !!  time step size

    !>  Local variables
    class(pf_level_t), pointer :: lev    !!  points to current level

    integer     :: m        !!  Loop variables
    real(pfdp)  :: t        !!  Time at nodes

    lev => pf%levels(1)

    t = t0 + dt
    do m = 1, 5
       call lev%Q(m)%setval(0.0_pfdp)
    end do

    call call_hooks(pf, 1, PF_PRE_SWEEP)

    call lev%Q(1)%copy(lev%q0, flags=1)
    call this%f_eval(lev%Q(1), t, lev%index, this%A(1))
    call this%dexpinv(this%A(1), lev%Q(1), lev%F(1,1))

    call lev%Q(2)%axpy(0.5_pfdp*dt, lev%F(1,1))
    call this%propagate(lev%q0, lev%Q(2))
    call this%f_eval(lev%Q(2), t, lev%index, this%A(2))
    call this%dexpinv(this%A(2), lev%Q(2), lev%F(2,1))

    call lev%Q(3)%axpy(0.5_pfdp*dt, lev%F(2,1))
    call this%propagate(lev%q0, lev%Q(3))
    call this%f_eval(lev%Q(3), t, lev%index, this%A(3))
    call this%dexpinv(this%A(3), lev%Q(3), lev%F(3,1))

    call lev%Q(4)%axpy(dt, lev%F(3,1))
    call this%propagate(lev%q0, lev%Q(4))
    call this%f_eval(lev%Q(4), t, lev%index, this%A(4))
    call this%dexpinv(this%A(4), lev%Q(4), lev%F(4,1))

    call lev%Q(5)%axpy(dt/6.0_pfdp, lev%F(1,1))
    call lev%Q(5)%axpy(dt/6.0_pfdp, lev%F(4,1))
    call lev%Q(5)%axpy(dt/3.0_pfdp, lev%F(2,1))
    call lev%Q(5)%axpy(dt/3.0_pfdp, lev%F(3,1))

    call this%propagate(lev%q0, lev%Q(5))

    call pf_residual(pf, lev, dt)
    call lev%qend%copy(lev%Q(lev%nnodes), 1)

    call call_hooks(pf, 1, PF_POST_SWEEP)
  end subroutine mkrk_step

  subroutine imk_actually_sweep(this, pf, level_index, t0, dt, nsweeps)
    use pf_mod_timer
    use pf_mod_hooks

    !>  Inputs
    class(pf_imk_t), intent(inout) :: this
    type(pf_pfasst_t), intent(inout),target :: pf      !!  PFASST structure
    integer,             intent(in)    :: level_index  !!  which level to sweep on
    real(pfdp),        intent(in   ) :: t0             !!  Time at beginning of time step
    real(pfdp),        intent(in   ) :: dt             !!  time step size
    integer,             intent(in)    :: nsweeps      !!  number of sweeps to do

    !>  Local variables
    class(pf_level_t), pointer :: lev    !!  points to current level

    integer     :: m, n,k   !!  Loop variables
    real(pfdp)  :: t        !!  Time at nodes
    lev => pf%levels(level_index)   !!  Assign level pointer

    call start_timer(pf, TLEVEL+lev%index-1)

    do k = 1,nsweeps   !!  Loop over sweeps

       ! compute integrals and add fas correction
       do m = 1, lev%nnodes-1
          call lev%I(m)%setval(0.0_pfdp)
          do n = 1, lev%nnodes
             call lev%I(m)%axpy(dt*this%QdiffE(m,n), lev%F(n,1))
          end do
          if (level_index < pf%nlevels) then
             call lev%I(m)%axpy(1.0_pfdp, lev%tauQ(m))
          end if
       end do

       !  Recompute the first function value if this is first sweep
       if (k .eq. 1) then
          call lev%Q(1)%setval(0.0_pfdp) ! likely an unnecessary setting of Omega=0
          call this%evaluate(lev, t0, 1)
       end if

       t = t0
       ! do the sub-stepping in sweep
       do m = 1, lev%nnodes-1  !!  Loop over substeps
          t = t + dt*this%dtsdc(m)

          !>  Accumulate rhs
          call lev%Q(m+1)%setval(0.0_pfdp)
          do n = 1, m
             call lev%Q(m+1)%axpy(dt*this%QtilE(m,n), lev%F(n,1))
          end do
          !>  Add the tau term
          call lev%Q(m+1)%axpy(1.0_pfdp, lev%I(m))

          !>  Compute explicit function on new value
          call this%evaluate(lev, t, m+1)
       end do  !!  End substep loop

       call pf_residual(pf, lev, dt)
       call lev%qend%copy(lev%Q(lev%nnodes), 1)

    end do  !  End loop on sweeps

    call end_timer(pf, TLEVEL+lev%index-1)
  end subroutine imk_actually_sweep

  subroutine imk_initialize(this, lev)
    class(pf_imk_t),   intent(inout) :: this
    class(pf_level_t), intent(inout) :: lev

    integer :: m, nnodes

    this%npieces = 1
    nnodes = lev%nnodes

    allocate(this%QdiffE(nnodes-1,nnodes), this%QtilE(nnodes-1,nnodes))
    allocate(this%dtsdc(nnodes-1))
    allocate(this%tsdc(nnodes))


    this%dtsdc = lev%nodes(2:nnodes) - lev%nodes(1:nnodes-1)
    this%bernoullis = 0.0_pfdp
    this%bernoullis(1 ) =       -1.0_pfdp / 2.0_pfdp
    this%bernoullis(2 ) =        1.0_pfdp / 6.0_pfdp
    this%bernoullis(4 ) =       -1.0_pfdp / 3.0_pfdp
    this%bernoullis(6 ) =        1.0_pfdp / 4.2e1_pfdp
    this%bernoullis(8 ) =       -1.0_pfdp / 3.0e1_pfdp
    this%bernoullis(10) =        5.0_pfdp / 6.6e1_pfdp
    this%bernoullis(12) =     -691.0_pfdp / 2.73e3_pfdp
    this%bernoullis(14) =        7.0_pfdp / 6.0_pfdp
    this%bernoullis(16) =    -3617.0_pfdp / 5.10e2_pfdp
    this%bernoullis(18) =    43867.0_pfdp / 7.98e2_pfdp
    this%bernoullis(20) =    -174611.0_pfdp/330.0_pfdp
    !>  Assign explicit approximate quadrature rule
    this%QtilE =  lev%sdcmats%qmatFE
    this%QdiffE = lev%sdcmats%qmat-this%QtilE

    !>  Make space for temporary variables
    call lev%ulevel%factory%create_array(this%A, nnodes, &
         lev%index,   lev%shape)

    do m = 1, nnodes
       call this%A(m)%setval(0.0_pfdp)
    end do

  end subroutine imk_initialize

  subroutine imk_integrate(this, lev, qSDC, fSDC, dt, fintSDC, flags)
    class(pf_imk_t),   intent(inout) :: this
    class(pf_level_t), intent(in   ) :: lev
    class(pf_encap_t), intent(in   ) :: qSDC(:), fSDC(:, :)
    real(pfdp),        intent(in   ) :: dt
    class(pf_encap_t), intent(inout) :: fintSDC(:)
    integer, optional,   intent(in)    :: flags

    integer :: j, m

    do m = 1, lev%nnodes-1
       call fintSDC(m)%setval(0.0_pfdp)
       do j = 1, lev%nnodes
          call fintSDC(m)%axpy(dt*lev%sdcmats%qmat(m,j), fSDC(j,1))
       end do
    end do

  end subroutine imk_integrate

  subroutine imk_evaluate(this, lev, t, m, flags, step)
    use pf_mod_dtype
    class(pf_imk_t),   intent(inout) :: this
    real(pfdp),        intent(in   ) :: t
    integer,           intent(in   ) :: m
    class(pf_level_t), intent(inout) :: lev
    integer, optional,   intent(in)    :: flags, step

    integer :: i
    real(pfdp) :: dt

    !  Propagate to get y=exp(Om)
    !prop needs e^{Q (omega)} and apply to Y
    if (this%debug) &
         print*, 'level', lev%index, 'in evaluate ', m, '------------------'

    if (this%rk) then
       ! 't' in f_evals are meaningless since i have a time-independent matrix, A
       dt = this%dt
       do i = 1, 5
         call lev%Q(i)%setval(0.0_pfdp)
       end do

       call lev%Q(1)%copy(lev%q0, flags=1)
       call this%f_eval(lev%Q(1), t, lev%index, this%A(1))
       ! commutator_p flags=1 hack copies Q(1)%y -> Q(1)%array
       ! all subsequent RK stages are done on Q(m)%array
       call this%commutator_p(this%A(1), lev%Q(1), lev%F(1,1), flags=1)

       call lev%Q(2)%axpy(1.0_pfdp, lev%Q(1))
       call lev%Q(2)%axpy(0.5_pfdp*dt, lev%F(1,1))
       call this%f_eval(lev%Q(2), t, lev%index, this%A(2))
       call this%commutator_p(this%A(2), lev%Q(2), lev%F(2,1))

       call lev%Q(3)%axpy(1.0_pfdp, lev%Q(1))
       call lev%Q(3)%axpy(0.5_pfdp*dt, lev%F(2,1))
       call this%f_eval(lev%Q(3), t, lev%index, this%A(3))
       call this%commutator_p(this%A(3), lev%Q(3), lev%F(3,1))

       call lev%Q(4)%axpy(1.0_pfdp, lev%Q(1))
       call lev%Q(4)%axpy(dt, lev%F(3,1))
       call this%f_eval(lev%Q(4), t, lev%index, this%A(4))
       call this%commutator_p(this%A(4), lev%Q(4), lev%F(4,1))

       call lev%Q(5)%axpy(1.0_pfdp, lev%Q(1))
       call lev%Q(5)%axpy(dt/6.0_pfdp, lev%F(1,1))
       call lev%Q(5)%axpy(dt/3.0_pfdp, lev%F(2,1))
       call lev%Q(5)%axpy(dt/3.0_pfdp, lev%F(3,1))
       call lev%Q(5)%axpy(dt/6.0_pfdp, lev%F(4,1))

       ! commutator_p flags=2 hack copies Q(m)%array -> Q(m)%y
       ! only the Q argument in this case matters
       ! in order to get solution back to qend
       call this%commutator_p(this%A(1), lev%Q(1), lev%F(1,1), flags=2)
       call this%commutator_p(this%A(2), lev%Q(2), lev%F(1,1), flags=2)
       call this%commutator_p(this%A(3), lev%Q(3), lev%F(1,1), flags=2)
       call this%commutator_p(this%A(4), lev%Q(4), lev%F(1,1), flags=2)
       call this%commutator_p(this%A(5), lev%Q(5), lev%F(1,1), flags=2)

    else
       if (this%debug) then
       endif
       if (m > 1) then
         if (this%debug) print*, 'propagating'
         call this%propagate(lev%q0, lev%Q(m))
       else
         if (this%debug) print*, 'copying'
         call lev%Q(1)%copy(lev%q0, flags=1)
       end if
       if (this%debug) print*, 'Q'
       if (this%debug) call lev%Q(m)%eprint()

       !  Compute A(y,t)
       call this%f_eval(lev%Q(m), t, lev%index, this%A(m))
       if (this%debug) print*, 'A'
       if (this%debug) call this%A(m)%eprint()

       !  Compute the series expansion for dexpinv
       if (m > 1)  then
         call this%dexpinv(this%A(m), lev%Q(m), lev%F(m,1))
       else
         call lev%F(1,1)%copy(this%A(1))
       endif
       if (this%debug) print*, 'depxinv'
       if (this%debug) call lev%F(m,1)%eprint()
    endif
    if (this%debug) print*, 'end evaluate ------------'
  end subroutine imk_evaluate

  subroutine imk_evaluate_all(this, lev, t, flags, step)
    class(pf_imk_t),   intent(inout) :: this
    class(pf_level_t), intent(inout) :: lev
    real(pfdp),        intent(in   ) :: t(:)
    integer, optional,   intent(in)    :: flags, step

    integer :: m
    if (this%rk) then
       call lev%ulevel%sweeper%evaluate(lev, t(1), 1)
    else
       do m = 1, lev%nnodes
          call lev%ulevel%sweeper%evaluate(lev, t(m), m)
       end do
    end if
  end subroutine imk_evaluate_all

  subroutine imk_residual(this, lev, dt, flags)
    class(pf_imk_t),   intent(inout) :: this
    class(pf_level_t), intent(inout) :: lev
    real(pfdp),        intent(in   ) :: dt
    integer, optional,   intent(in)    :: flags
    integer :: m

    call lev%ulevel%sweeper%integrate(lev, lev%Q, lev%F, dt, lev%I)

    ! add tau (which is 'node to node')
    if (allocated(lev%tauQ)) then
       do m = 1, lev%nnodes-1
          call lev%I(m)%axpy(1.0_pfdp, lev%tauQ(m))
       end do
    end if

    ! subtract out Q  (not initial condition is zero
    do m = 1, lev%nnodes-1
       call lev%R(m)%copy(lev%I(m))
       call lev%R(m)%axpy(-1.0_pfdp, lev%Q(m+1))
    end do

  end subroutine imk_residual

  subroutine imk_spreadq0(this, lev, t0, flags, step)
    class(pf_imk_t),  intent(inout) :: this
    class(pf_level_t), intent(inout) :: lev
    real(pfdp),        intent(in   ) :: t0
    integer, optional,   intent(in)    :: flags, step

    integer m,p
    !  Stick initial condition into first node slot
    call lev%Q(1)%copy(lev%q0, 1)

    ! set initial omega to 0
    call lev%Q(1)%setval(0.0_pfdp)

    !  Evaluate F at first spot
    call lev%ulevel%sweeper%evaluate(lev, t0, 1)

    ! Spread F and solution to all nodes
    do m = 2, lev%nnodes
       call lev%Q(m)%copy(lev%Q(1), 1)
       call lev%Q(m)%copy(lev%Q(1))
       do p = 1, lev%ulevel%sweeper%npieces
         call lev%F(m,p)%copy(lev%F(1,p))
       end do
    end do

  end subroutine imk_spreadq0

  !>  Save function values so that difference can be computed
  subroutine imk_save(lev)
    class(pf_level_t), intent(inout) :: lev  !!  Level to save on

    integer :: m

    do m = 1, lev%nnodes
       call lev%pF(m,1)%copy(lev%F(m,1))
    end do
  end subroutine imk_save

  subroutine imk_destroy(this, lev)
      class(pf_imk_t),   intent(inout) :: this
      class(pf_level_t), intent(inout) :: lev

      deallocate(this%QtilE, this%QdiffE)
      deallocate(this%dtsdc)
      deallocate(this%tsdc)

      call lev%ulevel%factory%destroy_array(this%A, lev%nnodes, &
           lev%index,   lev%shape)
  end subroutine imk_destroy

end module pf_mod_imk