example_web.f90 Source File


This file depends on

sourcefile~~example_web.f90~~EfferentGraph sourcefile~example_web.f90 example_web.f90 sourcefile~daskr_kinds.f90 daskr_kinds.F90 sourcefile~example_web.f90->sourcefile~daskr_kinds.f90

Source Code

!----------------------------------------------------------------------------------------------
! Adapted from original Fortran code in `original/examples/dweb.f`
!----------------------------------------------------------------------------------------------

module web_par
   !! Common set of parameters for [[example_web]].
   use daskr_kinds, only: rk, one
   implicit none

   integer, parameter :: np = 1, ns = 2*np, mx = 40, my = mx, mxns = mx*ns
   real(rk), parameter :: pi = 4*atan(one)

   real(rk) :: aa, ee, gg, bb, dprey, dpred
   real(rk) :: acoef(ns, ns), alpha, ax, ay, bcoef(ns), beta, cox(ns), coy(ns), diff(ns), dx, dy

contains

   impure subroutine setpar()
   !! This routine sets the basic problem parameters which are passed to the various routines
   !! via the module [[web_par]].
      integer :: i, j

      ax = one
      ay = one

      aa = one
      ee = 1e4_rk
      gg = 0.5e-6_rk
      bb = one
      dprey = one
      dpred = 0.05_rk
      alpha = 5e1_rk
      beta = 3e2_rk

      dx = ax/(mx - 1)
      dy = ay/(my - 1)

      do j = 1, np
         do i = 1, np
            acoef(np + i, j) = ee
            acoef(i, np + j) = -gg
         end do
         acoef(j, j) = -aa
         acoef(np + j, np + j) = -aa
         bcoef(j) = bb
         bcoef(np + j) = -bb
         diff(j) = dprey
         diff(np + j) = dpred
      end do

      do i = 1, ns
         cox(i) = diff(i)/dx**2
         coy(i) = diff(i)/dy**2
      end do

   end subroutine setpar

end module web_par

module web_m
   !! Procedures for [[example_web]].
   use daskr_kinds, only: rk, zero, one
   implicit none

contains

   pure subroutine setid(mx, my, ns, nsd, lid, iwork)
   !! This routine sets the ID array in `iwork`, indicating which components are differential
   !! and which are algebraic.
      integer, intent(in) :: mx
      integer, intent(in) :: my
      integer, intent(in) :: ns
      integer, intent(in) :: nsd
      integer, intent(in) :: lid
      integer, intent(inout) :: iwork(*)

      integer :: i, i0, i00, jx, jy, nsdp1

      nsdp1 = nsd + 1
      do jy = 1, my
         i00 = mx*ns*(jy - 1) + lid
         do jx = 1, mx
            i0 = i00 + ns*(jx - 1)
            do i = 1, nsd
               iwork(i0 + i) = 1
            end do
            do i = nsdp1, ns
               iwork(i0 + i) = -1
            end do
         end do
      end do

   end subroutine setid

   pure subroutine cinit(c, cprime, pred_ic, rpar)
   !! This routine computes and loads the vectors of initial values.
      use web_par, only: ax, ay, alpha, beta, dx, dy, ns, np, mx, my, mxns, pi
      real(rk), intent(out) :: c(:)
      real(rk), intent(out) :: cprime(:)
      real(rk), intent(in)  :: pred_ic
      real(rk), intent(inout) :: rpar(:)

      integer :: i, ioff, iyoff, jx, jy, npp1
      real(rk) :: argx, argy, fac, t, x, y

      ! Load C
      npp1 = np + 1
      do jy = 1, my
         y = (jy - 1)*dy
         argy = 16*y*y*(ay - y)*(ay - y)
         iyoff = mxns*(jy - 1)
         do jx = 1, mx
            x = (jx - 1)*dx
            argx = 16*x*x*(ax - x)*(ax - x)
            ioff = iyoff + ns*(jx - 1)
            fac = one + alpha*x*y + beta*sin(4*pi*x)*sin(4*pi*y)
            do i = 1, np
               c(ioff + i) = 10.0_rk + i*argx*argy
            end do
            do i = npp1, ns
               c(ioff + i) = pred_ic
            end do
         end do
      end do

      ! Load CPRIME
      t = zero
      call f(t, c, cprime, rpar)
      do jy = 1, my
         iyoff = mxns*(jy - 1)
         do jx = 1, mx
            ioff = iyoff + ns*(jx - 1)
            do i = npp1, ns
               cprime(ioff + i) = zero
            end do
         end do
      end do

   end subroutine cinit

   impure subroutine out(t, c, ns, mx, my, lout)
   !! This routine prints the values of the individual species densities at the current time
   !! `t`, to logical unit `lout`.
      real(rk), intent(in) :: t
      real(rk), intent(in) :: c(ns, mx, my)
      integer, intent(in) :: ns
      integer, intent(in) :: mx
      integer, intent(in) :: my
      integer, intent(in) :: lout

      logical, save :: first = .true.
      integer :: i, jx, jy

      if (first) then
         write (lout, '(a16)', advance='no') 't'
         do i = 1, ns
            do jy = 1, my
               do jx = 1, mx
                  write (lout, '(1x, a16)', advance='no') &
                     "c("//to_string(i)//","//to_string(jx)//","//to_string(jy)//")"
               end do
            end do
         end do
         write (lout, *) ''
         first = .false.
      end if

      write (lout, '(es16.5e3)', advance="no") t
      do i = 1, ns
         do jy = 1, my
            do jx = 1, mx
               write (lout, '(1x, es16.5e3)', advance="no") c(i, jx, jy)
            end do
         end do
      end do
      write (lout, *) ''

   end subroutine out

   pure subroutine res(t, c, cprime, cj, delta, ires, rpar, ipar)
   !! This routine computes the residuals vector, using subroutine [[f]] for the right-hand
   !! sides.
      use web_par, only: ns, np, mx, my, mxns
      real(rk), intent(in) :: t
      real(rk), intent(in) :: c(*)
      real(rk), intent(in) :: cprime(*)
      real(rk), intent(in) :: cj
      real(rk), intent(out) :: delta(*)
      integer, intent(in) :: ires
      real(rk), intent(inout) :: rpar(*)
      integer, intent(in) :: ipar(*)

      integer :: i, ic0, ici, iyoff, jx, jy

      call f(t, c, delta, rpar)

      do jy = 1, my
         iyoff = mxns*(jy - 1)
         do jx = 1, mx
            ic0 = iyoff + ns*(jx - 1)
            do i = 1, ns
               ici = ic0 + i
               if (i > np) then
                  delta(ici) = -delta(ici)
               else
                  delta(ici) = cprime(ici) - delta(ici)
               end if
            end do
         end do
      end do

   end subroutine res

   pure subroutine f(t, c, cprime, rpar)
   !! This routine computes the right-hand sides of all the equations and returns them in the
   !! array `cprime`. The interaction rates are computed by calls to [[rates]], and these are
   !! saved in `rpar` for later use in preconditioning.
      use web_par, only: cox, coy, ns, mx, my, mxns
      real(rk), intent(in) :: t
      real(rk), intent(in) :: c(*)
      real(rk), intent(out) :: cprime(*)
      real(rk), intent(inout) :: rpar(*)

      integer :: i, ic, ici, idxl, idxu, idyl, idyu, iyoff, jx, jy
      real(rk) :: dcxli, dcxui, dcyli, dcyui

      do jy = 1, my
         iyoff = mxns*(jy - 1)
         idyu = mxns
         if (jy == my) idyu = -mxns
         idyl = mxns
         if (jy == 1) idyl = -mxns
         do jx = 1, mx
            ic = iyoff + ns*(jx - 1) + 1
            ! Get interaction rates at one point (X,Y)
            call rates(t, jx, jy, c(ic), rpar(ic))
            idxu = ns
            if (jx == mx) idxu = -ns
            idxl = ns
            if (jx == 1) idxl = -ns
            do i = 1, ns
               ici = ic + i - 1
               ! Do differencing in Y
               dcyli = c(ici) - c(ici - idyl)
               dcyui = c(ici + idyu) - c(ici)
               ! Do differencing in X
               dcxli = c(ici) - c(ici - idxl)
               dcxui = c(ici + idxu) - c(ici)
               ! Collect terms and load CPRIME elements
               cprime(ici) = coy(i)*(dcyui - dcyli) + cox(i)*(dcxui - dcxli) + rpar(ici)
            end do
         end do
      end do

   end subroutine f

   pure subroutine rates(t, jx, jy, c, rate)
   !! This routine computes one block of the rate term of the system \(v\), namely block
   !! `(jx, jy)`, for use in preconditioning.
      use web_par, only: alpha, beta, acoef, bcoef, dx, dy, pi, ns
      real(rk), intent(in) :: t
      integer, intent(in) :: jx
      integer, intent(in) :: jy
      real(rk), intent(in) :: c(*)
      real(rk), intent(inout) :: rate(*)

      integer :: i
      real(rk) :: fac, x, y

      y = (jy - 1)*dy
      x = (jx - 1)*dx
      fac = one + alpha*x*y + beta*sin(4*pi*x)*sin(4*pi*y)

      rate(1:ns) = zero
      do i = 1, ns
         rate(1:ns) = rate(1:ns) + c(i)*acoef(1:ns, i)
      end do

      do i = 1, ns
         rate(i) = c(i)*(bcoef(i)*fac + rate(i))
      end do

   end subroutine rates

   subroutine jacrs(res_, ires, neq, t, c, cprime, rewt, savr, wk, h, cj, wp, iwp, ier, &
                    rpar, ipar)
   !! This routine interfaces to subroutines [[DRBDJA]] or [[DRBGJA]], depending on the flag
   !! `jbg=ipar(2)`, to generate and preprocess the block-diagonal Jacobian corresponding to
   !! the reaction term \(v\).
   !!
   !! * If `jbg==0`, we call [[DRBDJA]], with no block-grouping.
   !! * If `jbg==1`, we call [[DRBGJA]], and use block-grouping.
   !!
   !! Array `rpar`, containing the current \(v\) vector, is passed to [[DRBDJA]] and [[DRBGJA]]
   !! as argument `R0`, consistent with the loading of `rpar` in [[f]]. The procedure        name
   !! [[rates]] is passed as the name of the routine which computes the individual blocks of
   !! \(v\).
      external :: res_
      integer, intent(in) :: ires
      integer, intent(in) :: neq
      real(rk), intent(in) :: t
      real(rk), intent(in) :: c(*)
      real(rk), intent(in) :: cprime(*)
      real(rk), intent(in) :: rewt(*)
      real(rk), intent(in) :: savr(*)
      real(rk), intent(in) :: wk(*)
      real(rk), intent(in) :: h
      real(rk), intent(in) :: cj
      real(rk), intent(in) :: wp(*)
      integer, intent(in) :: iwp(*)
      integer, intent(in) :: ier
      real(rk), intent(in) :: rpar(*)
      integer, intent(in) :: ipar(*)

      integer :: jbg
      external :: drbdja, drbgja

      jbg = ipar(2)
      if (jbg == 0) then
         call drbdja(t, c, rpar, rates, wk, rewt, cj, wp, iwp, ier)
      else
         call drbgja(t, c, rpar, rates, wk, rewt, cj, wp, iwp, ier)
      end if

   end subroutine jacrs

   subroutine psolrs(neq, t, cc, ccprime, savr, wk, cj, wt, wp, iwp, b, eplin, ier, rpar, ipar)
   !! This routine applies the inverse of a product preconditioner matrix to the vector in the
   !! array `b`. Depending on the flag `jpre`, this involves a call to [[gs]], for the inverse
   !! of the spatial factor, and/or a call to [[DRBDPS]] or [[DRBGPS]] for the inverse of the
   !! reaction-based factor (`cj*I_d - dR/dy`). The latter factor uses block-grouping (with a
   !! call to [[DRBGPS]]) if `jbg == 1`, and does not (with a call to [[DRBDPS]]) if `jbg == 0`.
   !! the flag `jbg` is passed as `ipar(2)`. The array `b` is overwritten with the solution.
      integer, intent(in) :: neq
      real(rk), intent(in) :: t
      real(rk), intent(in) :: cc(*)
      real(rk), intent(in) :: ccprime(*)
      real(rk), intent(in) :: savr(*)
      real(rk), intent(inout) :: wk(*)
      real(rk), intent(in) :: cj
      real(rk), intent(in) :: wt(*)
      real(rk), intent(in) :: wp(*)
      integer, intent(in) :: iwp(*)
      real(rk), intent(inout) :: b(*)
      real(rk), intent(in) :: eplin
      integer, intent(inout) :: ier
      real(rk), intent(inout) :: rpar(*)
      integer, intent(in) :: ipar(*)

      integer :: jbg, jpre
      real(rk) :: hl0
      external :: drbdps, drbgps

      jpre = ipar(1)
      ier = 0
      hl0 = one/cj

      jbg = ipar(2)

      if (jpre == 2 .or. jpre == 3) call gauss_seidel(neq, hl0, b, wk)

      if (jpre /= 2) then
         if (jbg == 0) call drbdps(b, wp, iwp)
         if (jbg == 1) call drbgps(b, wp, iwp)
      end if

      if (jpre == 4) call gauss_seidel(neq, hl0, b, wk)

   end subroutine psolrs

   pure subroutine gauss_seidel(n, hl0, z, x)
   !! This routine provides the inverse of the spatial factor for a product preconditoner in an
   !! ns-species reaction-diffusion problem. It performs `itmax` Gauss-Seidel iterations to
   !! compute an approximation to \(A_S^{-1} z\), where \(A_S = I - h_{l0} J_ d \), and \(J_d\)
   !! represents the diffusion contributions to the Jacobian. The solution vector is returned
   !! in `z`.
      use web_par, only: cox, coy, ns, mx, my, mxns
      integer, intent(in) :: n
      real(rk), intent(in) :: hl0
      real(rk), intent(inout) :: z(*)
      real(rk), intent(inout) :: x(*)

      integer, parameter :: itmax = 5
      integer :: i, ic, ici, iter, iyoff, jx, jy
      real(rk) :: elamda, beta1(ns), gamma1(ns), beta2(ns), gamma2(ns), dinv(ns)

      ! Write matrix as A = D - L - U.
      ! Load local arrays BETA, BETA2, GAMMA1, GAMMA2, and DINV.
      do i = 1, ns
         elamda = one/(one + 2*hl0*(cox(i) + coy(i)))
         beta1(i) = hl0*cox(i)*elamda
         beta2(i) = 2*beta1(i)
         gamma1(i) = hl0*coy(i)*elamda
         gamma2(i) = 2*gamma1(i)
         dinv(i) = elamda
      end do

      ! Zero X in all its components, since X is added to Z at the end.
      x(1:n) = zero

      ! Load array X with (D-inverse)*Z for first iteration.
      do jy = 1, my
         iyoff = mxns*(jy - 1)
         do jx = 1, mx
            ic = iyoff + ns*(jx - 1)
            do i = 1, ns
               ici = ic + i
               x(ici) = dinv(i)*z(ici)
               z(ici) = zero
            end do
         end do
      end do

      do iter = 1, itmax

         ! Calculate (D-inverse)*U*X
         if (iter > 1) then

            jy = 1
            jx = 1
            ic = ns*(jx - 1)
            do i = 1, ns
               ici = ic + i
               x(ici) = beta2(i)*x(ici + ns) + gamma2(i)*x(ici + mxns)
            end do
            do jx = 2, mx - 1
               ic = ns*(jx - 1)
               do i = 1, ns
                  ici = ic + i
                  x(ici) = beta1(i)*x(ici + ns) + gamma2(i)*x(ici + mxns)
               end do
            end do
            jx = mx
            ic = ns*(jx - 1)
            do i = 1, ns
               ici = ic + i
               x(ici) = gamma2(i)*x(ici + mxns)
            end do
            do jy = 2, my - 1
               iyoff = mxns*(jy - 1)
               jx = 1
               ic = iyoff
               do i = 1, ns
                  ici = ic + i
                  x(ici) = beta2(i)*x(ici + ns) + gamma1(i)*x(ici + mxns)
               end do
               do jx = 2, mx - 1
                  ic = iyoff + ns*(jx - 1)
                  do i = 1, ns
                     ici = ic + i
                     x(ici) = beta1(i)*x(ici + ns) + gamma1(i)*x(ici + mxns)
                  end do
               end do
               jx = mx
               ic = iyoff + ns*(jx - 1)
               do i = 1, ns
                  ici = ic + i
                  x(ici) = gamma1(i)*x(ici + mxns)
               end do
            end do
            jy = my
            iyoff = mxns*(jy - 1)
            jx = 1
            ic = iyoff
            do i = 1, ns
               ici = ic + i
               x(ici) = beta2(i)*x(ici + ns)
            end do
            do jx = 2, mx - 1
               ic = iyoff + ns*(jx - 1)
               do i = 1, ns
                  ici = ic + i
                  x(ici) = beta1(i)*x(ici + ns)
               end do
            end do
            jx = mx
            ic = iyoff + ns*(jx - 1)
            do i = 1, ns
               ici = ic + i
               x(ici) = zero
            end do

         end if

         ! Calculate [(I - (D-inverse)*L)]-inverse * X
         jy = 1
         do jx = 2, mx - 1
            ic = ns*(jx - 1)
            do i = 1, ns
               ici = ic + i
               x(ici) = x(ici) + beta1(i)*x(ici - ns)
            end do
         end do
         jx = mx
         ic = ns*(jx - 1)
         do i = 1, ns
            ici = ic + i
            x(ici) = x(ici) + beta2(i)*x(ici - ns)
         end do
         do jy = 2, my - 1
            iyoff = mxns*(jy - 1)
            jx = 1
            ic = iyoff
            do i = 1, ns
               ici = ic + i
               x(ici) = x(ici) + gamma1(i)*x(ici - mxns)
            end do
            do jx = 2, mx - 1
               ic = iyoff + ns*(jx - 1)
               do i = 1, ns
                  ici = ic + i
                  x(ici) = (x(ici) + beta1(i)*x(ici - ns)) + gamma1(i)*x(ici - mxns)
               end do
            end do
            jx = mx
            ic = iyoff + ns*(jx - 1)
            do i = 1, ns
               ici = ic + i
               x(ici) = (x(ici) + beta2(i)*x(ici - ns)) + gamma1(i)*x(ici - mxns)
            end do
         end do
         jy = my
         iyoff = mxns*(jy - 1)
         jx = 1
         ic = iyoff
         do i = 1, ns
            ici = ic + i
            x(ici) = x(ici) + gamma2(i)*x(ici - mxns)
         end do
         do jx = 2, mx - 1
            ic = iyoff + ns*(jx - 1)
            do i = 1, ns
               ici = ic + i
               x(ici) = (x(ici) + beta1(i)*x(ici - ns)) + gamma2(i)*x(ici - mxns)
            end do
         end do
         jx = mx
         ic = iyoff + ns*(jx - 1)
         do i = 1, ns
            ici = ic + i
            x(ici) = (x(ici) + beta2(i)*x(ici - ns)) + gamma2(i)*x(ici - mxns)
         end do

         ! Add increment X to Z
         do i = 1, n
            z(i) = z(i) + x(i)
         end do

      end do

   end subroutine gauss_seidel

   pure subroutine c1_average(c, c1ave)
   !! This routine computes the spatial average value of \(c_1\).
      use web_par, only: mx, my, ns, mxns
      real(rk), intent(in) :: c(*)
      real(rk), intent(out) :: c1ave

      integer :: ioff, iyoff, jx, jy
      real(rk) :: total

      total = zero
      do jy = 1, my
         iyoff = mxns*(jy - 1)
         do jx = 1, mx
            ioff = iyoff + ns*(jx - 1)
            total = total + c(ioff + 1)
         end do
      end do

      c1ave = total/(mx*my)

   end subroutine c1_average

   pure subroutine rt(neq, t, c, cprime, nrt, rval, rpar, ipar)
   !! Roots routine.
      integer, intent(in) :: neq
      real(rk), intent(in) :: t
      real(rk), intent(in) :: c(neq)
      real(rk), intent(in) :: cprime(neq)
      integer, intent(in) :: nrt
      real(rk), intent(out) :: rval(nrt)
      real(rk), intent(in) :: rpar(*)
      integer, intent(in) :: ipar(*)

      real(rk) :: c1ave

      call c1_average(c, c1ave)
      rval(1) = c1ave - 20.0_rk

   end subroutine rt

   function to_string(value) result(string)
      integer, intent(in) :: value
      character(len=:), allocatable :: string
      character(len=128) :: buffer

      write (buffer, *) value
      string = trim(adjustl(buffer))

   end function

end module web_m

program example_web
!! Example program for [[daskr]]:
!! DAE system derived from \(s\)-species interaction PDE in 2 dimensions.
!!
!! This program solves a DAE system that arises from a system of partial differential equations.
!! The PDE system is a food web population model, with predator-prey interaction and diffusion
!! on the unit square in two dimensions. The dependent variable vector is
!!
!! $$ c = [c_1, c_2 , ..., c_s] $$
!!
!! and the PDEs are as follows:
!!
!! $$\begin{aligned}
!! \frac{\partial c_i}{\partial t} &= d_i \left( \frac{\partial^2 c_i}{\partial x^2}
!!           + \frac{\partial^2 c_i}{\partial y^2} \right) + v_i \quad i=1,...,s/2 \\
!!                               0 &= d_i \left( \frac{\partial^2 c_i}{\partial x^2}
!!           + \frac{\partial^2 c_i}{\partial y^2} \right) + v_i \quad i=s/2+1,...,s
!! \end{aligned}$$
!!
!! where the rate of formation of species \(i\) is given by:
!!
!! $$ v_i(x,y,c) = c_i \left( b_i + \sum_{j=1}^s a_{ij} c_j \right) $$
!!
!! The number of species is \(s = 2 p\), with the first \(p\) being prey and the last \(p\)
!! being predators. The coefficients \(a_{ij}\), \(b_i\), \(d_i\) are:
!!
!! $$\begin{aligned}
!!   a_{ii} &= -a  \quad (\mathrm{all}\; i) \\
!!   a_{ij} &= -g  \quad (i \le p,\; j > p) \\
!!   a_{ij} &=  e  \quad (i > p,\; j \le p)
!! \end{aligned}$$
!!
!! $$\begin{aligned}
!!   b_i    &=  b (1 + \alpha x y + \beta \sin(4 \pi x) \sin(4 \pi y)) \quad (i \le p) \\
!!   b_i    &= -b (1 + \alpha x y + \beta \sin(4 \pi x) \sin(4 \pi y)) \quad (i > p)
!! \end{aligned}$$
!!
!! $$\begin{aligned}
!!   d_i    &= d_{prey} \quad (i \le p) \\
!!   d_i    &= d_{pred} \quad (i > p)
!! \end{aligned}$$
!!
!! The various scalar parameters are set in subroutine [[setpar]].
!!
!! The boundary conditions are of Neumann type (zero normal derivative) everywhere. A polynomial
!! in \(x\) and \(y\) is used to set the initial conditions. The PDEs are discretized by central
!! differencing on a \( M_x \times M_y\) mesh.
!!
!! The root function is:
!!
!! $$ r(t,c,c') = \iint c_1 dx dy - 20 $$
!!
!! The DAE system is solved by [[daskr]] with three different method options:
!!
!! 1. Direct band method for the linear systems (internal Jacobian),
!! 2. Preconditioned Krylov method for the linear systems, without block-grouping in the
!!    reaction-based factor, and
!! 3. Preconditioned Krylov method for the linear systems, with block-grouping in the
!!    reaction-based factor.
!!
!! In the Krylov cases, the preconditioner is the product of two factors:
!!
!! * The spatial factor uses a fixed number of Gauss-Seidel iterations based on the diffusion
!!   terms only.
!! * The reaction-based factor is a block-diagonal matrix based on the partial derivatives of
!!   the interaction terms `R` only.
!!
!! With block-grouping, only a subset of the \((s \times s)\) blocks are computed. An integer
!! flag, `jpre`, is set in the main program to specify whether the preconditioner is to use only
!! one of the two factors or both, and in which order.
!!
!! The reaction-based preconditioner factor is set up and solved in seven subroutines:
!!
!! * [[DMSET2]], [[DRBDJA]], [[DRBDPS]] in the case of no block-grouping, and
!! * [[DGSET2]], [[GSET1]], [[DRBGJA]], [[DRBGPS]]  in the case of block-grouping.
!!
!! These routines are provided separately for general use on problems arising from
!! reaction-transport systems.
!!
!! Two output files are written: one with the problem description and performance statistics on
!! and one with solution profiles at selected output times. The solution file is written only
!! in the case of the direct method.
!!
!! References:
!!
!! * Peter N. Brown and Alan C. Hindmarsh, "Reduced Storage Matrix Methods in Stiff ODE
!!   Systems", J. Appl. Math. & Comp., 31 (1989), pp. 40-91.
!! * Peter N. Brown, Alan C. Hindmarsh, and Linda R. Petzold, "Using Krylov Methods in the
!!   Solution of Large-Scale Differential-Algebraic Systems", SIAM J. Sci. Comput., 15 (1994),
!!   pp. 1467-1488.
!! * Peter N. Brown, Alan C. Hindmarsh, and Linda R. Petzold, "Consistent Initial Condition
!!   Calculation for Differential-Algebraic Systems", LLNL Report UCRL-JC-122175, August 1995;
!!   SIAM J. Sci. Comput., 19 (1998), pp. 1495 - 1512.

   use daskr_kinds, only: rk, zero, one
   use web_par, only: aa, alpha, bb, beta, dpred, dprey, ee, gg, mx, my, mxns, np, ns, setpar
   use web_m
   implicit none

   integer, parameter :: neq = ns*mx*my, maxm = max(mx, my)
   integer, parameter :: lrw = 63 + (3*ns*maxm + 11)*neq + maxm, liw = 40 + 2*neq
   integer :: idid, info(20), iout, ipar(2), iwork(liw), jbg, jpre, jroot, leniw, &
              lenrw, ldout, lcout, meth, ncfl, ncfn, ng, nli, nlidif, nni, nnidif, nout, &
              npe, nps, nqu, nre, nrt, nrte, nst, nxg, nyg
   real(rk) :: atol, avlin, c1ave, cc(neq), ccprime(neq), hu, pred_ic, rpar(neq), rtol, &
               rwork(lrw), t, tout

   ! Dimension solution arrays and work arrays.
   !
   ! When INFO(12) = 0, with INFO(5) = 0, INFO(6) = 1:
   !       The length required for RWORK is
   !       60 + 3*NRT + (2*ML+MU+11)*NEQ + 2*(NEQ/(ML+MU+1) + 1) .
   !       For MX = MY = (even number) and ML = MU = NS*MX, this length is
   !       60 + 3*NRT + (3*NS*MX + 11)*NEQ + MY .
   !       The length required for IWORK is  40 + 2*NEQ .
   !
   ! When INFO(12) = 1:
   !       The length required for RWORK is
   !       101 + 3*NRT + 19*NEQ + LENWP = 104 + 19*NEQ + NS*NS*NGRP .
   !       The length required for IWORK is
   !       40 + NEQ + LENIWP = 40 + NEQ + NS*NGRP .
   !
   ! The dimensions for the various arrays are set using parameters
   !       MAXN    which must be >= NEQ = NS*MX*MY,
   !       MAXM    which must be >= MAX(MX,MY).

   ! Open output files.
   open (newunit=ldout, file='./example/example_web_d.out', action="write", position="rewind")
   open (newunit=lcout, file='./example/example_web_c.out', action="write", position="rewind")

   ! Call SETPAR to set basic problem parameters.
   call setpar

   ! Set NRT = number of root functions.
   nrt = 1

   write (ldout, '(a, /)') 'DWEB: Example program for DASKR package'
   write (ldout, '(a, i4)') 'Food web problem with NS species, NS =', ns
   write (ldout, '(a, /)') 'Predator-prey interaction and diffusion on a 2-D square'
   write (ldout, '(a, e12.4, a, e12.4, a, e12.4)') &
      'Matrix parameters..  a =', aa, '  e =', ee, '  g =', gg
   write (ldout, '(21x, a, e12.4)') &
      'b =', bb
   write (ldout, '(a, e12.4, a, e12.4)') &
      'Diffusion coefficients: dprey =', dprey, '  dpred =', dpred
   write (ldout, '(a, e12.4, a, e12.4)') &
      'Rate parameters alphaa =', alpha, ' and beta =', beta
   write (ldout, '(a, 2i4, 5x, a, i7)') &
      'Mesh dimensions (MX,MY) =', mx, my, ' Total system size is NEQ =', neq
   write (ldout, '(a)') 'Root function is R(Y) = average(c1) - 20'

   ! Set the flat initial guess for the predators.
   pred_ic = 1e5_rk

   ! Set remaining method parameters for DDASKR.
   ! These include the INFO array and tolerances.
   info = 0

   ! Set INFO(11) = 1, indicating I.C. calculation requested.
   info(11) = 1

   ! Set INFO(14) = 1 to get the computed initial values.
   info(14) = 1

   ! Set INFO(15) = 1 to signal that a preconditioner setup routine is to be called in the
   ! Krylov case.
   info(15) = 1

   ! Set INFO(16) = 1 to get alternative error test (on the differential variables only).
   info(16) = 1

   ! Set the tolerances.
   rtol = 1e-5_rk
   atol = rtol

   write (ldout, '(/, a, e10.2, a, e10.2)') &
      'Tolerance parameters: RTOL =', rtol, '  ATOL =', atol
   write (ldout, '(a, i2, a)') &
      'Internal I.C. calculation flag INFO(11) =', info(11), '  (0 = off, 1 = on)'
   write (ldout, '(a, e10.2)') &
      'Predator I.C. guess =', pred_ic
   write (ldout, '(a, i2, a)') &
      'Alternate error test flag INFO(16) =', info(16), '  (0 = off, 1 = on)'

   ! Set NOUT = number of output times.
   nout = 18

   ! Loop over method options:
   ! METH = 0 means use INFO(12) = 0 (direct)
   ! METH = 1 means use INFO(12) = 1 (Krylov) without block-grouping in the reaction-based
   !          factor in the preconditioner.
   ! METH = 2 means use INFO(12) = 1 (Krylov) with block-grouping in the reaction-based factor
   !          in the preconditioner.
   ! A block-grouping flag JBG, communicated through IPAR, is set to 0 (no block-grouping) or
   ! 1 (use block-grouping) with METH = 1 or 2.
   ! Reset INFO(1) = 0 and INFO(11) = 1.

   do meth = 0, 2

      info(12) = min(meth, 1)
      info(1) = 0
      info(11) = 1
      jbg = meth - 1
      ipar(2) = jbg

      write (ldout, '(/, 80("."), //, a, i2, a)') &
         'Linear solver method flag INFO(12) =', info(12), '  (0 = direct, 1 = Krylov)'

      ! In the case of the direct method, set INFO(6) = 1 to signal a banded Jacobian, set
      ! IWORK(1) = IWORK(2) = MX*NS, the half-bandwidth, and call SETID to set the IWORK
      ! segment containing the block ID indicating the differential and algebraic components.
      if (info(12) == 0) then
         info(6) = 1
         iwork(1) = mxns
         iwork(2) = mxns
         call setid(mx, my, ns, np, 40, iwork)
         write (ldout, '(a, i4)') 'Difference-quotient banded Jacobian, half-bandwidths =', mxns
      end if

      ! In the case of the Krylov method, set and print various preconditioner parameters.
      if (info(12) == 1) then

         ! First set the preconditioner choice JPRE.
         !  JPRE = 1 means reaction-only (block-diagonal) factor A_R
         !  JPRE = 2 means spatial factor (Gauss-Seidel) A_S
         !  JPRE = 3 means A_S * A_R
         !  JPRE = 4 means A_R * A_S
         ! Use IPAR to communicate JPRE to the preconditioner solve routine.
         jpre = 3
         ipar(1) = jpre
         write (ldout, '(a, i3)') 'Preconditioner flag is JPRE =', jpre
         write (ldout, '(a)') &
            '(1 = reaction factor A_R, 2 = spatial factor A_S, 3 = A_S*A_R, 4 = A_R*A_S)'

         ! Call DMSET2 if JBG = 0, or DGSET2 if JBG = 1, to set the 2D mesh parameters and 
         ! block-grouping data, and the IWORK segment ID indicating the differential and 
         ! algebraic components.
         if (jbg == 0) then
            call dmset2(mx, my, ns, np, 40, iwork)
            write (ldout, '(a)') 'No block-grouping in reaction factor'
         end if
         if (jbg == 1) then
            nxg = 5
            nyg = 5
            ng = nxg*nyg
            call dgset2(mx, my, ns, np, nxg, nyg, 40, iwork)
            write (ldout, '(a)') 'Block-grouping in reaction factor'
            write (ldout, '(a, i5, a, i3, a, i3, a)') &
               'Number of groups =', ng, ' (NGX by NGY, NGX =', nxg, ',  NGY =', nyg, ')'
         end if
      end if

      ! Set the initial T and TOUT, and call CINIT to set initial values.
      t = zero
      tout = 1.0e-8_rk
      call cinit(cc, ccprime, pred_ic, rpar)

      nli = 0
      nni = 0

      ! Header for "ldout"
      write (ldout, '(/10x, a)') &
         't      <c1>  NSTEP   NRE   NNI   NLI   NPE    NQ          H    AVLIN'

      ! Loop over output times and call DASKR. At each output time, print average c1 value and
      ! performance data.
      ! The first call, with IOUT = 0, is to calculate initial values only.
      ! After the first call, reset INFO(11) = 0 and the initial TOUT.
      ! If a root was found, we flag this, and return to the DASKR call.
      do iout = 0, nout

         do
            call daskr(res, neq, t, cc, ccprime, tout, info, rtol, atol, &
                       idid, rwork, lrw, iwork, liw, rpar, ipar, jacrs, psolrs, &
                       rt, nrt, jroot)

            nst = iwork(11)
            nre = iwork(12)
            npe = iwork(13)
            nnidif = iwork(19) - nni
            nni = iwork(19)
            nlidif = iwork(20) - nli
            nli = iwork(20)
            nqu = iwork(8)
            hu = rwork(7)
            avlin = zero
            if (nnidif > 0) avlin = one*nlidif/nnidif

            if (meth == 0) then
               call out(t, cc, ns, mx, my, lcout)
            end if

            call c1_average(cc, c1ave)
            write (ldout, '(e11.5, f10.5, i7, i6, i6, i6, i6, i6, e11.2, f9.4)') &
               t, c1ave, nst, nre, nni, nli, npe, nqu, hu, avlin

            if (idid == 5) then
               write (ldout, '(15x, a, i3)') '*****   Root found, JROOT =', jroot
            else
               exit
            end if

         end do

         if (idid < 0) then
            write (ldout, '(//a, e12.4//)') 'Final time reached =', t
            exit
         end if

         if (tout > 0.9_rk) then
            tout = tout + one
         else
            tout = 10*tout
         end if

         if (iout == 0) then
            info(11) = 0
            tout = 1.0e-8_rk
            nli = 0
            nni = 0
         end if

      end do

      lenrw = iwork(18)
      leniw = iwork(17)
      nst = iwork(11)
      nre = iwork(12)
      npe = iwork(13)
      nni = iwork(19)
      nli = iwork(20)
      nps = iwork(21)
      if (nni > 0) avlin = real(nli)/real(nni)
      ncfn = iwork(15)
      ncfl = iwork(16)
      nrte = iwork(36)

      write (ldout, '(//a)') 'Final statistics for this run..'
      write (ldout, '(a, i8, a, i6)') 'RWORK size =', lenrw, '  IWORK size =', leniw
      write (ldout, '(a, i5)') 'Number of time steps              =', nst
      write (ldout, '(a, i5)') 'Number of residual evaluations    =', nre
      write (ldout, '(a, i5)') 'Number of root fn. evaluations    =', nrte
      write (ldout, '(a, i5)') 'Number of Jac. or prec. evals.    =', npe
      write (ldout, '(a, i5)') 'Number of preconditioner solves   =', nps
      write (ldout, '(a, i5)') 'Number of nonlinear iterations    =', nni
      write (ldout, '(a, i5)') 'Number of linear iterations       =', nli
      write (ldout, '(a, f8.4)') 'Average Krylov subspace dimension =', avlin
      write (ldout, '(i3, a, i5, a)') ncfn, ' nonlinear conv. failures,', ncfl, ' linear conv. failures'

   end do

   close (unit=ldout)
   close (unit=lcout)

end program example_web