! $Id$
!
!  This module applies a sixth order hyperviscosity to the equation
!  of motion (following Haugen & Brandenburg 2004). This hyperviscosity
!  ensures that the energy dissipation rate is positive define everywhere.
!
!  The rate of strain tensor
!
!    S^(3) = (-nab^2)^2*S
!
!  is a high order generalisation of the first order rate of strain tensor
!
!    2*S_ij = u_i,j + u_j,i - 2/3*delta_ij*div(u)
!
!  Spatial derivatives are accurate to second order.
!
!** AUTOMATIC CPARAM.INC GENERATION ****************************
! Declare (for generation of cparam.inc) the number of f array
! variables and auxiliary variables added by this module
!
! CPARAM logical, parameter :: lhyperviscosity_strict=.true.
!
! MVAR CONTRIBUTION 0
! MAUX CONTRIBUTION 3
!
!***************************************************************
module Hypervisc_strict
!
  use Cparam
  use Cdata
  use Messages
!
  implicit none
!
  include 'hypervisc_strict.h'
!
  contains
!***********************************************************************
    subroutine register_hypervisc_strict()
!
!  Set up indices for hyperviscosity auxiliary slots.
!
      use FArrayManager
!
      if (lroot) call svn_id( &
           "$Id$")
!
!  Set indices for auxiliary variables
!
      call farray_register_auxiliary('hypvis',ihypvis,vector=3)
!
    endsubroutine register_hypervisc_strict
!***********************************************************************
    subroutine hyperviscosity_strict(f)
!
!  Apply momentum-conserving, symmetric, sixth order hyperviscosity with
!  positive definite heating rate (see Haugen & Brandenburg 2004).
!
!  The rate of strain tensor of order 3 is defined as:
!
!               /-1/3*div(u)+dux/dx  1/2*(dux/dy+duy/dx) 1/2*(dux/dz+duz/dx) \
!               |                                                            |
!  S^(3)=(d2)^2 |1/2*(dux/dy+duy/dx) -1/3*div(u)+duy/dy  1/2*(duy/dz+duz/dy) |
!               |                                                            |
!               \1/2*(dux/dz+duz/dx) 1/2*(duy/dz+duz/dy) -1/3*div(u)+2*duz/dz/
!
!  where d2 is the Laplacian operator d2=(d^2/dx^2+d2/dy^2+d2/dz^2).
!
!  To avoid communicating ghost zones after each operator, we use
!  derivatives that are second order in space.
!
!  24-nov-03/nils: coded
!
      use SharedVariables, only: get_shared_variable
      use Sub
!
      real, dimension (mx,my,mz,mfarray) :: f
!
      real, dimension (mx,my,mz,3) :: tmp, tmp2
      real, dimension (mx,my,mz) :: tmp3, tmp4
      integer :: i, j, ierr
      logical, save :: lfirstcall=.true.
      logical, pointer, save :: lvisc_hyper3_nu_const_strict
!
!  Calculate del2(del2(del2(u))), accurate to second order.
!
      call del2v_2nd(f,tmp,iux)
      f(:,:,:,ihypvis:ihypvis+2)=tmp
      call del2v_2nd(f,tmp,ihypvis)
      f(:,:,:,ihypvis:ihypvis+2)=tmp
      call del2v_2nd(f,tmp,ihypvis)
!
!  Calculate del2(del2(grad(div(u)))), accurate to second order.
!  Probably gives zero derivative at the Nyquist scale, but the del2^3
!  term above gives dissipation at this scale.
!
      call graddivu_2nd(f,tmp2,iux)
      f(:,:,:,ihypvis:ihypvis+2)=tmp2
      call del2v_2nd(f,tmp2,ihypvis)
      f(:,:,:,ihypvis:ihypvis+2)=tmp2
      call del2v_2nd(f,tmp2,ihypvis)
!
!  Add the two terms.
!
      f(:,:,:,ihypvis:ihypvis+2)=tmp+tmp2/3.
!
!  For constant nu we also need the term [2*nu*S^3].grad(lnrho).
!
      if (lfirstcall) then
        call get_shared_variable('lvisc_hyper3_nu_const_strict', &
            lvisc_hyper3_nu_const_strict,caller='hyperviscosity_strict')
        if (ip<10) &
            print*, 'hyperviscosity_strict: lvisc_hyper3_nu_const_strict=', &
            lvisc_hyper3_nu_const_strict
        lfirstcall=.false.
      endif
!
!  First we calculate the gradient of the logarithmic density (second order
!  accuracy).
!
      if (lvisc_hyper3_nu_const_strict) then
        call grad_2nd(f,ilnrho,tmp)
        if (ldensity_nolog) then
          tmp3=exp(f(:,:,:,irho))
          do i=1,3; tmp(:,:,:,i)=tmp(:,:,:,i)/tmp3; enddo
        endif
!
!  Add [(del2)^2(u_i,j+u_j,i)].grad(lnrho) to hyperviscosity.
!
        do i=1,3; do j=1,3
          call der_2nd(f,iux-1+i,tmp3,j)
          call del2_2nd_nof(tmp3,tmp4)
          call del2_2nd_nof(tmp4,tmp3)
          f(:,:,:,ihypvis-1+i)=f(:,:,:,ihypvis-1+i)+tmp3*tmp(:,:,:,j)
          f(:,:,:,ihypvis-1+j)=f(:,:,:,ihypvis-1+j)+tmp3*tmp(:,:,:,i)
        enddo; enddo
!
!  Add -2/3*[(del2)^2delta_ij*div(u)].grad(lnrho) term.
!
        call div_2nd(f,iux,tmp3)
        call del2_2nd_nof(tmp3,tmp4)
        call del2_2nd_nof(tmp4,tmp3)
        f(:,:,:,ihypvis  )=f(:,:,:,ihypvis  )-2/3.*tmp3*tmp(:,:,:,1)
        f(:,:,:,ihypvis+1)=f(:,:,:,ihypvis+1)-2/3.*tmp3*tmp(:,:,:,2)
        f(:,:,:,ihypvis+2)=f(:,:,:,ihypvis+2)-2/3.*tmp3*tmp(:,:,:,3)
      endif
!
! find heating term (yet it only works for ivisc='hyper3')
! the heating term is d/dt(0.5*rho*u^2) = -2*mu3*( S^(2) )^2
!
!      if (ldiagnos) then
!        if (ivisc == 'hyper3') then
!          sij2=0
!          do i=1,2
!            do j=i+1,3
!
! find uij and uji (stored in tmp in order to save space)
!
!              call der_2nd_nof(f(:,:,:,iux+i-1),tmp(:,:,:,1),j) ! uij
!              call der_2nd_nof(f(:,:,:,iux+j-1),tmp(:,:,:,2),i) ! uji
!
! find (nabla2 uij) and (nable2 uji)
!
!              call del2_2nd_nof(tmp(:,:,:,1),tmp(:,:,:,3))
!              tmp(:,:,:,1)=tmp(:,:,:,3) ! nabla2 uij
!              call del2_2nd_nof(tmp(:,:,:,2),tmp(:,:,:,3))
!              tmp(:,:,:,2)=tmp(:,:,:,3) ! nabla2 uji
!
! find sij2 for full array
!
!              sij2=sij2+tmp(l1:l2,m1:m2,n1:n2,1)*tmp(l1:l2,m1:m2,n1:n2,2) &
!                   +0.5*tmp(l1:l2,m1:m2,n1:n2,1)**2 &
!                   +0.5*tmp(l1:l2,m1:m2,n1:n2,2)**2
!            enddo
!          enddo
!
!  add diagonal terms
!
!          tmp(:,:,:,1)=0
!          call divu_2nd(f,tmp(:,:,:,1))                     ! divu
!          call del2_2nd_nof(tmp(:,:,:,1),tmp(:,:,:,3))        ! del2(divu)
!          do i=1,3
!            call der_2nd_nof(f(:,:,:,iux+i-1),tmp(:,:,:,1),i) ! uii
!            call del2_2nd_nof(tmp(:,:,:,1),tmp(:,:,:,2))      ! nabla2 uii
!            sij2=sij2+(tmp(l1:l2,m1:m2,n1:n2,2)-tmp(l1:l2,m1:m2,n1:n2,3)/3.)**2
!          enddo
!
!          epsK_hyper=2*nu_hyper3*sum(sij2)
!        endif
!      endif
!
    endsubroutine hyperviscosity_strict
!***********************************************************************
    subroutine der_2nd(f,k,df,j)
!
!  Calculate derivative of scalar, accurate to 2nd order.
!
!  13-sep-07/anders: adapted from div_2nd
!
      real, dimension (mx,my,mz,mfarray) :: f
      real, dimension (mx,my,mz) :: df
      integer :: j,k
!
      real :: fac
!
      df=0.0
!
      if (j==1 .and. nxgrid/=1) then
        fac=1./(2.*dx)
        df(2:mx-1,:,:) = df(2:mx-1,:,:) &
                         + ( f(3:mx,:,:,k)-f(1:mx-2,:,:,k) ) *fac
      endif
!
      if (j==2 .and. nygrid/=1) then
        fac=1./(2.*dy)
        df(:,2:my-1,:) = df(:,2:my-1,:) &
                         + ( f(:,3:my,:,k)-f(:,1:my-2,:,k) )*fac
      endif
!
      if (j==3 .and. nzgrid/=1) then
        fac=1./(2.*dz)
        df(:,:,2:mz-1) = df(:,:,2:mz-1) &
                         + ( f(:,:,3:mz,k)-f(:,:,1:mz-2,k) )*fac
      endif
!
    endsubroutine der_2nd
!***********************************************************************
    subroutine div_2nd(f,k,df)
!
!  Calculate divergence of a vector, accurate to 2nd order.
!
!  23-nov-02/tony: coded
!
      real, dimension (mx,my,mz,mfarray) :: f
      real, dimension (mx,my,mz) :: df
      integer :: k
!
      real :: fac
!
      df=0.0
!
      if (nxgrid/=1) then
        fac=1./(2.*dx)
        df(2:mx-1,:,:) = df(2:mx-1,:,:) &
                         + ( f(3:mx,:,:,k  )-f(1:mx-2,:,:,k  ) ) *fac
      endif
!
      if (nygrid/=1) then
        fac=1./(2.*dy)
        df(:,2:my-1,:) = df(:,2:my-1,:) &
                         + ( f(:,3:my,:,k+1)-f(:,1:my-2,:,k+1) )*fac
      endif
!
      if (nzgrid/=1) then
        fac=1./(2.*dz)
        df(:,:,2:mz-1) = df(:,:,2:mz-1) &
                         + ( f(:,:,3:mz,k+2)-f(:,:,1:mz-2,k+2) )*fac
      endif
!
    endsubroutine div_2nd
!***********************************************************************
    subroutine grad_2nd(f,k,df)
!
!  Calculate gradient of scalar, accurate to second order.
!
!  13-sep-07/anders: adapted from div_2nd
!
      real, dimension (mx,my,mz,mfarray) :: f
      real, dimension (mx,my,mz,3) :: df
      integer :: k
!
      real :: fac
!
      df=0.0
!
      if (nxgrid/=1) then
        fac=1./(2.*dx)
        df(2:mx-1,:,:,1) = (f(3:mx,:,:,k)-f(1:mx-2,:,:,k))*fac
      endif
!
      if (nygrid/=1) then
        fac=1./(2.*dy)
        df(:,2:my-1,:,2) = (f(:,3:my,:,k)-f(:,1:my-2,:,k))*fac
      endif
!
      if (nzgrid/=1) then
        fac=1./(2.*dz)
        df(:,:,2:mz-1,3) = (f(:,:,3:mz,k)-f(:,:,1:mz-2,k))*fac
      endif
!
    endsubroutine grad_2nd
!***********************************************************************
    subroutine der_2nd_nof(var,tmp,j)
!
!  24-nov-03/nils: coded
!
      real, dimension (mx,my,mz) :: var
      real, dimension (mx,my,mz) :: tmp
      integer :: j
!
      intent (in) :: var,j
      intent (out) :: tmp
!
      tmp=0.
!
      if (j==1 .and. nxgrid/=1) then
          tmp(     1,:,:) = (-3.*var(1,:,:) &
                             +4.*var(2,:,:) &
                             -1.*var(3,:,:))/(2.*dx)
          tmp(2:mx-1,:,:) = (-1.*var(1:mx-2,:,:) &
                             +1.*var(3:mx  ,:,:))/(2.*dx)
          tmp(    mx,:,:) = (+1.*var(mx-2,:,:) &
                             -4.*var(mx-1,:,:) &
                             +3.*var(mx  ,:,:))/(2.*dx)
      endif
!
      if (j==2 .and. nygrid/=1) then
          tmp(:,     1,:) = (-3.*var(:,1,:) &
                             +4.*var(:,2,:) &
                             -1.*var(:,3,:))/(2.*dy)
          tmp(:,2:my-1,:) = (-1.*var(:,1:my-2,:) &
                             +1.*var(:,3:my  ,:))/(2.*dy)
          tmp(:,    my,:) = (+1.*var(:,my-2,:) &
                             -4.*var(:,my-1,:) &
                             +3.*var(:,my  ,:))/(2.*dy)
      endif
!
      if (j==3 .and. nzgrid/=1) then
          tmp(:,:,     1) = (-3.*var(:,:,1) &
                             +4.*var(:,:,2) &
                             -1.*var(:,:,3))/(2.*dz)
          tmp(:,:,2:mz-1) = (-1.*var(:,:,1:mz-2) &
                             +1.*var(:,:,3:mz  ))/(2.*dz)
          tmp(:,:,    mz) = (+1.*var(:,:,mz-2) &
                             -4.*var(:,:,mz-1) &
                             +3.*var(:,:,mz  ))/(2.*dz)
      endif
!
    endsubroutine der_2nd_nof
!***********************************************************************
    subroutine del2v_2nd(f,del2f,k)
!
!  Calculate Laplacian of a vector, accurate to second order.
!
!  24-nov-03/nils: adapted from del2v
!
      real, dimension (mx,my,mz,mfarray) :: f
      real, dimension (mx,my,mz,3) :: del2f
      real, dimension (mx,my,mz) :: tmp
      integer :: i,k,k1
!
      intent (in) :: f, k
      intent (out) :: del2f
!
      del2f=0.
!
!  Apply Laplacian to each vector component individually.
!
      k1=k-1
      do i=1,3
        call del2_2nd(f,tmp,k1+i)
        del2f(:,:,:,i)=tmp
      enddo
!
    endsubroutine del2v_2nd
!***********************************************************************
    subroutine del2_2nd(f,del2f,k)
!
!  Calculate del2 of a scalar, get scalar.
!  Accurate to second order.
!
!  24-nov-03/nils: adapted from del2
!
      real, dimension (mx,my,mz,mfarray) :: f
      real, dimension (mx,my,mz) :: del2f,d2fd
      integer :: k,k1
!
      intent (in) :: f, k
      intent (out) :: del2f
!
      k1=k-1
      call der2_2nd(f,d2fd,k,1)
      del2f=d2fd
      call der2_2nd(f,d2fd,k,2)
      del2f=del2f+d2fd
      call der2_2nd(f,d2fd,k,3)
      del2f=del2f+d2fd
!
    endsubroutine del2_2nd
!***********************************************************************
    subroutine del2_2nd_nof(f,del2f)
!
!  Calculate Laplacian of a scalar, get scalar.
!
!  24-nov-03/nils: adapted from del2_2nd
!
      real, dimension (mx,my,mz) :: f
      real, dimension (mx,my,mz) :: del2f,d2fd
!
      intent (in) :: f
      intent (out) :: del2f
!
      call der2_2nd_nof(f,d2fd,1)
      del2f=d2fd
      call der2_2nd_nof(f,d2fd,2)
      del2f=del2f+d2fd
      call der2_2nd_nof(f,d2fd,3)
      del2f=del2f+d2fd
!
    endsubroutine del2_2nd_nof
!***********************************************************************
    subroutine der2_2nd(f,der2f,i,j)
!
!  Calculate the second derivative of f.
!  Accurate to second order.
!
!  24-nov-03/nils: coded
!
      real, dimension (mx,my,mz,mfarray) :: f
      real, dimension (mx,my,mz) :: der2f
      integer :: i,j
!
      intent (in) :: f,i,j
      intent (out) :: der2f
!
      der2f=0.
!
      if (j==1 .and. nxgrid/=1) then
        der2f(2:mx-1,:,:) = (+1.*f(1:mx-2,:,:,i) &
                             -2.*f(2:mx-1,:,:,i) &
                             +1.*f(3:mx  ,:,:,i) ) / (dx**2)
      endif
!
     if (j==2 .and. nygrid/=1) then
        der2f(:,2:my-1,:) = (+1.*f(:,1:my-2,:,i) &
                             -2.*f(:,2:my-1,:,i) &
                             +1.*f(:,3:my  ,:,i) ) / (dy**2)
      endif
!
     if (j==3 .and. nzgrid/=1) then
        der2f(:,:,2:mz-1) = (+1.*f(:,:,1:mz-2,i) &
                             -2.*f(:,:,2:mz-1,i) &
                             +1.*f(:,:,3:mz  ,i) ) / (dz**2)
      endif
!
    endsubroutine der2_2nd
!***********************************************************************
    subroutine der2_2nd_nof(f,der2f,j)
!
!  Calculate the second derivative of f.
!  Accurate to second order.
!
!  07-jan-04/nils: adapted from der2_2nd
!
      real, dimension (mx,my,mz) :: f
      real, dimension (mx,my,mz) :: der2f
      integer :: j
!
      intent (in) :: f,j
      intent (out) :: der2f
!
      der2f=0.
!
      if (j==1 .and. nxgrid/=1) then
        der2f(2:mx-1,:,:) = (+1.*f(1:mx-2,:,:) &
                             -2.*f(2:mx-1,:,:) &
                             +1.*f(3:mx  ,:,:) ) / (dx**2)
      endif
!
     if (j==2 .and. nygrid/=1) then
        der2f(:,2:my-1,:) = (+1.*f(:,1:my-2,:) &
                             -2.*f(:,2:my-1,:) &
                             +1.*f(:,3:my  ,:) ) / (dy**2)
      endif
!
     if (j==3 .and. nzgrid/=1) then
        der2f(:,:,2:mz-1) = (+1.*f(:,:,1:mz-2) &
                             -2.*f(:,:,2:mz-1) &
                             +1.*f(:,:,3:mz  ) ) / (dz**2)
      endif
!
    endsubroutine der2_2nd_nof
!***********************************************************************
    subroutine graddivu_2nd(f,graddivuf,k)
!
!  Calculate the gradient of the divergence of a vector.
!  Accurate to second order.
!
!  24-nov-03/nils: coded
!
      real, dimension (mx,my,mz,mfarray) :: f
      real, dimension (mx,my,mz,3) :: graddivuf
      real, dimension (mx,my,mz) :: tmp,tmp2
      integer :: k,k1
!
      intent (in) :: f, k
      intent (out) :: graddivuf
!
      graddivuf=0.
!
      k1=k-1
!
!  d/dx(div(u))
!
      call der2_2nd(f,tmp,k1+1,1)
      graddivuf(:,:,:,1)=tmp
      call der_2nd_nof(f(:,:,:,k1+2),tmp,1)
      call der_2nd_nof(tmp,tmp2,2)
      graddivuf(:,:,:,1)=graddivuf(:,:,:,1)+tmp2
      call der_2nd_nof(f(:,:,:,k1+3),tmp,1)
      call der_2nd_nof(tmp,tmp2,3)
      graddivuf(:,:,:,1)=graddivuf(:,:,:,1)+tmp2
!
!  d/dy(div(u))
!
      call der2_2nd(f,tmp,k1+2,2)
      graddivuf(:,:,:,2)=tmp
      call der_2nd_nof(f(:,:,:,k1+1),tmp,1)
      call der_2nd_nof(tmp,tmp2,2)
      graddivuf(:,:,:,2)=graddivuf(:,:,:,2)+tmp2
      call der_2nd_nof(f(:,:,:,k1+3),tmp,2)
      call der_2nd_nof(tmp,tmp2,3)
      graddivuf(:,:,:,2)=graddivuf(:,:,:,2)+tmp2
!
!  d/dz(div(u))
!
      call der2_2nd(f,tmp,k1+3,3)
      graddivuf(:,:,:,3)=tmp
      call der_2nd_nof(f(:,:,:,k1+1),tmp,1)
      call der_2nd_nof(tmp,tmp2,3)
      graddivuf(:,:,:,3)=graddivuf(:,:,:,3)+tmp2
      call der_2nd_nof(f(:,:,:,k1+2),tmp,2)
      call der_2nd_nof(tmp,tmp2,3)
      graddivuf(:,:,:,3)=graddivuf(:,:,:,3)+tmp2
!
    endsubroutine graddivu_2nd
!***********************************************************************
endmodule Hypervisc_strict