! $Id$
!
!** AUTOMATIC CPARAM.INC GENERATION ****************************
! Declare (for generation of cparam.inc) the number of f array
! variables and auxiliary variables added by this module
!
!***************************************************************
!
module Pencil_check
!
  use Cdata
  use General, only: keep_compiler_quiet
  use Messages
!
  implicit none
!
  private
!
  public :: pencil_consistency_check
!
  contains
!***********************************************************************
    subroutine pencil_consistency_check(f,df,p)
!
!  This subroutine checks the run for missing and for superfluous pencils.
!  First a reference df is calculated with all the requested pencils. Then
!  the pencil request is flipped one by one (a pencil that is requested
!  is not calculated, a pencil that is not requested is calculated). A
!  complete set of pencils should fulfil
!    - Calculating a not requested pencil should not change df
!    - Not calculating a requested pencil should change df
!  The run has a problem when
!    - Calculating a not requested pencil changes df
!      (program dies with error message)
!    - Not calculating a requested pencil does not change df
!      (program gives a warning)
!  If there are missing pencils, the programmer should go into the
!  pencil_criteria_XXX subs and request the proper pencils (based cleverly
!  on run parameters).
!
!  18-apr-05/tony: coded
!
      use Equ, only: initialize_pencils, pde
      use General, only: random_number_wrapper, random_seed_wrapper, notanumber
      use Mpicomm, only: mpireduce_and, mpireduce_or, mpibcast_logical, stop_it_if_any, MPI_COMM_WORLD
!
      real, dimension(mx,my,mz,mfarray) :: f
      real, dimension(mx,my,mz,mvar) :: df
      type (pencil_case) :: p
      real, allocatable, dimension(:,:,:,:) :: df_ref, f_other
      real, allocatable, dimension(:) :: fname_ref
      real, dimension (nx) :: dt1_max_ref
      integer :: i,j,k,penc,iv,nite
      integer, dimension (mseed) :: iseed_org
      logical, dimension (mfarray) :: lfound_nan=.false.
      logical, dimension (mfarray) :: lfound_nan_loc=.false.
      logical :: lconsistent=.true., lconsistent_allproc=.false.
      logical :: ldie=.false.
      integer :: mem_stat1, mem_stat2
      real :: save_dt
!
      if (lroot) print*, 'pencil_consistency_check: checking pencil case'
      lpencil_check_at_work=.true.
      !TP: save dt to be able to load it after the pencil check has been done
      save_dt = dt
!
!  Prevent code from dying due to any errors.
!
      call life_support_on('needed for pencil consistency check')
!
!  Allocate memory for alternative df, fname.
!
      allocate(f_other(mx,my,mz,mfarray)  ,stat=mem_stat1)
      allocate(df_ref(mx,my,mz,mvar)      ,stat=mem_stat2)
      if ((mem_stat1>0).or.(mem_stat2>0)) then
        if (lroot) then
          print*, '  Large buffers are needed to perform these tests'
          print*, '  rigourously. For that reason it may only be  '
          print*, '  possible to perform the check on smaller test runs.'
          call stop_it_if_any(.true.,'pencil_consistency_check: '// &
              'failed to allocate required memory')
        endif
      endif
      call stop_it_if_any(.false.,'')
      allocate(fname_ref(nname))
!
!  Check requested pencils.
!
      lfirst=.true.
      headt=.false.
      itsub=1  ! some modules like dustvelocity.f90 reference dt_beta_ts(itsub)
      if (lpencil_check_no_zeros) then
        call random_seed_wrapper(GET=iseed_org)
        call random_seed_wrapper(PUT=iseed_org)
        do i=1,mfarray
          if (maxval(abs(f(l1:l2,m1:m2,n1:n2,i)))==0.0) then
            call random_number_wrapper(f_other(:,:,:,i))
          else
            f_other(:,:,:,i)=f(:,:,:,i)
          endif
        enddo
      else
        f_other=f
      endif
      df_ref=0.0
      call initialize_pencils(p,penc0)
!
!  Calculate reference results with all requested pencils on.
!
      lpencil=lpenc_requested
      call pde(f_other,df_ref,p)
      dt1_max_ref=dt1_max
!
      lfound_nan=.false.
      do iv=1,mvar; do n=n1,n2; do m=m1,m2
        if (notanumber(df_ref(:,m,n,iv))) lfound_nan_loc(iv)=.true.
      enddo; enddo; enddo
      call mpireduce_or(lfound_nan_loc,lfound_nan,mfarray,MPI_COMM_WORLD)
      call mpibcast_logical(lfound_nan,mfarray,comm=MPI_COMM_WORLD)
      if (lroot) then
        do iv=1,mvar
          if (lfound_nan(iv)) then
            print*, 'pencil_consistency_check: NaNs in reference "df_ref" at variable', iv
            print*, 'pencil_consistency_check: numbers of variables are given in "data/index.pro"'
          endif
        enddo
      endif
      if (any(lfound_nan)) then
        if (lroot) then
          print*, 'pencil_consistency_check: the presence of NaNs '// &
               'in "df_ref" makes this test impossible'
          call stop_it_if_any(.true.,'pencil_consistency_check: '// &
              'generation of reference failed, quitting pencil check')
        endif
      endif
      call stop_it_if_any(.false.,'')
!
      if (ldt) then
        if (notanumber(dt1_max_ref)) &
          print*, 'pencil_consistency_check: NaNs in "dt1_max_ref"'
      endif
!
      nite=npencils
      if ((.not.lpencil_check).and.lpencil_check_small) nite=0
!
      if (lroot) print*, 'pencil_consistency_check: checking requested pencils'
!
      do penc=0,nite
        if (lroot.and.penc==0) &
            print*, 'pencil_consistency_check: performing small pencil check'
        if (lroot.and.penc==1) &
            print*, 'pencil_consistency_check: performing full pencil check'// &
            ' (takes a while)'
        df=0.0
        if (lpencil_check_no_zeros) then
          call random_seed_wrapper(PUT=iseed_org)
          do i=1,mvar+maux
            if (maxval(abs(f(l1:l2,m1:m2,n1:n2,i)))==0.0) then
              call random_number_wrapper(f_other(:,:,:,i))
            else
              f_other(:,:,:,i)=f(:,:,:,i)
            endif
          enddo
        else
          f_other=f
        endif
        call initialize_pencils(p,penc0)
!
!  Calculate results with one pencil swapped.
!
        lpencil=lpenc_requested
        if (penc>0) then
          lpencil(penc)=(.not. lpencil(penc))
        else
          lpencil=.true.
        endif

        call pde(f_other,df,p)
        lfound_nan=.false.
        do iv=1,mvar; do n=n1,n2; do m=m1,m2
          if (notanumber(df(:,m,n,iv))) lfound_nan(iv)=.true.
        enddo; enddo; enddo
        do iv=1,mvar
          if (lfound_nan(iv)) then
            print*, 'pencil_consistency_check: NaNs in "df" at variable', &
                iv, ' for pencil ', trim(pencil_names(penc))
            print*, 'pencil_consistency_check: numbers of variables are in "data/index.pro".'
          endif
        enddo
!
!  Compare results.
!
        lconsistent=.true.
        lconsistent_allproc=.false.
        if (ldt) then
          do i=1,nx
            if (dt1_max(i)/=dt1_max_ref(i)) then
              lconsistent=.false.
              exit
            endif
          enddo
        endif
        if (lconsistent) then
f_loop:   do iv=1,mvar
            do k=n1,n2; do j=m1,m2; do i=l1,l2
              lconsistent=(df(i,j,k,iv)==df_ref(i,j,k,iv))
              if (.not. lconsistent) exit f_loop
            enddo; enddo; enddo
          enddo f_loop
        endif
!
        call mpireduce_and(lconsistent,lconsistent_allproc,MPI_COMM_WORLD)
!
        if (lroot) then
          if (penc>0) then
            if (lconsistent_allproc .and. lpenc_requested(penc)) then
              print '(a,i4,a)', ' pencil_consistency_check: '// &
                  'possible overcalculation... pencil '//&
                  trim(pencil_names(penc))//' (',penc,')'// &
                  ' is requested, but does not appear to be required!'
            elseif ( (.not. lconsistent_allproc) .and. &
                     (.not. lpenc_requested(penc)) ) then
              print '(a,i4,a)',' pencil_consistency_check: '// &
                  'MISSING PENCIL... pencil '// &
                  trim(pencil_names(penc))//' (',penc,')'// &
                  ' is not requested, but calculating it changes the results!'
              ldie=.true.
            endif
          else
            if (.not.lconsistent_allproc) then
              print*, 'pencil_consistency_check: '// &
                  'the small pencil check has found one or more'
              print*, '                          MISSING pencils. '// &
                  'This could be a serious problem.'
              if (lpencil_check) then
                print*, 'pencil_consistency_check: '// &
                    'the full pencil check beginning will now find'
                print*, '                          out which pencils are missing'
              else
                print*, 'pencil_consistency_check: '// &
                    'you need to run the full pencil check to find'
                print*, '                          out which ones ('// &
                    'set lpencil_check=T in &run_pars'
                print*, '                          and run again)'
                ldie=.true.
              endif
            else
              print*, 'pencil_consistency_check: '// &
                  'the small pencil check reported no problems'
            endif
          endif
        endif
      enddo
!
!  Check pencil initialization.
!
      if (lroot) print*, 'pencil_consistency_check: '// &
          'checking dependence on pencil initialization'
      df=0.0
      if (lpencil_check_no_zeros) then
        call random_seed_wrapper(PUT=iseed_org)
        do i=1,mvar+maux
          if (maxval(abs(f(l1:l2,m1:m2,n1:n2,i)))==0.0) then
            call random_number_wrapper(f_other(:,:,:,i))
          else
            f_other(:,:,:,i)=f(:,:,:,i)
          endif
        enddo
      else
        f_other=f
      endif
      call initialize_pencils(p,0.5*penc0)
!
      lpencil=lpenc_requested
      call pde(f_other,df,p)
      lfound_nan=.false.
      do iv=1,mvar; do n=n1,n2; do m=m1,m2
        if (notanumber(df(:,m,n,iv))) lfound_nan(iv)=.true.
      enddo; enddo; enddo
      do iv=1,mvar
        if (lfound_nan(iv)) then
          print*, 'pencil_consistency_check: NaNs in df at variable', iv
          print*, 'pencil_consistency_check: numbers of variables are in "data/index.pro".'
        endif
      enddo
!
!  Compare results.
!
      lconsistent=.true.
      lconsistent_allproc=.false.
      if (ldt) then
        do i=1,nx
          if (dt1_max(i)/=dt1_max_ref(i)) then
            lconsistent=.false.
            exit
          endif
        enddo
      endif
      if (lconsistent) then
f_lop:  do iv=1,mvar
          do k=n1,n2; do j=m1,m2; do i=l1,l2
            lconsistent=(df(i,j,k,iv)==df_ref(i,j,k,iv))
            if (.not. lconsistent) exit f_lop
          enddo; enddo; enddo
        enddo f_lop
      endif
!
      call mpireduce_and(lconsistent,lconsistent_allproc,MPI_COMM_WORLD)
      if (lroot) then
        if (.not. lconsistent_allproc) then
          print*, 'pencil_consistency_check: '// &
              'results depend on pencil initialization'
          print*, '                          This is a serious problem '// &
              'that may show the use of'
          print*, '                          uninitialized pencils. Check '// &
              'carefully that all'
          print*, '                          pencils are calculated and '// &
              'that conjugate pencils are'
          print*, '                          calculated *after* the '// &
              'individual components'
          ! [PAB] add code here in order to find the problematic component
          ldie=.true.
        else
          print*, 'pencil_consistency_check: '// &
              'results are independent of pencil initialization'
        endif
      endif
!
!  Check diagnostic pencils.
!
      lout=.true.
      lfirst=.true.
      df=0.0
      if (lpencil_check_no_zeros) then
        call random_seed_wrapper(put=iseed_org)
        do i=1,mfarray
          if (maxval(abs(f(l1:l2,m1:m2,n1:n2,i)))==0.0) then
            call random_number_wrapper(f_other(:,:,:,i))
          else
            f_other(:,:,:,i)=f(:,:,:,i)
          endif
        enddo
      else
        f_other=f
      endif
      fname=0.0; fweight=0.0
      call initialize_pencils(p,penc0)
!
!  Calculate reference diagnostics with all diagnostic pencils on.
!
      lpencil=(lpenc_diagnos.or.lpenc_requested)
      call pde(f_other,df,p)
      fname_ref=fname(1:nname)
!
      if (lroot) &
          print*, 'pencil_consistency_check: checking diagnostics pencils'
!
      do penc=0,nite
        if (lroot.and.penc==0) &
            print*, 'pencil_consistency_check: performing small pencil check'
        if (lroot.and.penc==1) &
            print*, 'pencil_consistency_check: performing full pencil check'// &
            ' (takes a while)'
        df=0.0
        if (lpencil_check_no_zeros) then
          call random_seed_wrapper(put=iseed_org)
          do i=1,mfarray
            if (maxval(abs(f(l1:l2,m1:m2,n1:n2,i)))==0.0) then
              call random_number_wrapper(f_other(:,:,:,i))
            else
              f_other(:,:,:,i)=f(:,:,:,i)
            endif
          enddo
        else
          f_other=f
        endif
        fname=0.0; fweight=0.0
        call initialize_pencils(p,penc0)
!
!  Calculate diagnostics with one pencil swapped.
!
        lpencil=(lpenc_diagnos.or.lpenc_requested)
        if (penc>0) then
          lpencil(penc)=(.not. lpencil(penc))
        else
          lpencil=.true.
        endif
        call pde(f_other,df,p)
!
!  Compare results.
!
        lconsistent=.true.
        lconsistent_allproc=.false.
        do k=1,nname
          lconsistent=(fname(k)==fname_ref(k))
          if (.not.lconsistent) exit
        enddo
!
        call mpireduce_and(lconsistent,lconsistent_allproc,MPI_COMM_WORLD)
!
!  ref = result same as "correct" reference result
!    d = swapped pencil set as diagnostic
!    r = swapped pencil set as requested (can take over for diagnostic pencil)
!
!   ref +  d +  r = d not needed but set, r not needed but set; optimize d
!   ref +  d + !r = d not needed but set, r not needed and not set; optimize d
!  !ref +  d +  r = d needed and set, r needed and set; d superfluous, but OK
!  !ref +  d + !r = d needed and set; all OK
!   ref + !d +  r = d not needed and not set; all OK
!  !ref + !d +  r = d needed and not set, r needed and set; all OK
!  !ref + !d + !r = d needed and not set, r needed and not set; missing d
!
        if (lroot) then
          if (penc>0) then
            if (lpencil_check_diagnos_opti .and. &
                lconsistent_allproc .and. lpenc_diagnos(penc) ) then
              print '(a,i4,a)','pencil_consistency_check: '// &
                  'OPTIMISATION POTENTIAL... pencil '// &
                  trim(pencil_names(penc))//' (',penc,')'// &
                  ' is requested for diagnostics, '// &
                  'but does not appear to be required!'
            elseif ( (.not. lconsistent_allproc) .and. &
                (.not. lpenc_diagnos(penc)) .and. &
                (.not. lpenc_requested(penc)) ) then
              print '(a,i4,a)','pencil_consistency_check: '// &
                  'MISSING PENCIL... pencil '// &
                  trim(pencil_names(penc))//' (',penc,')'// &
                  ' is not requested for diagnostics, '// &
                  'but calculating it changes the diagnostics!'
              ldie=.true.
            endif
          else
            if (.not.lconsistent_allproc) then
              print*, 'pencil_consistency_check: '// &
                  'the small pencil check has found one or more'
              print*, '                          MISSING diagnostics pencils. '// &
                  'This could be a serious problem.'
              if (lpencil_check) then
                print*, 'pencil_consistency_check: '// &
                    'the full pencil check beginning now will find'
                print*, '                          out which pencils are missing'
              else
                print*, 'pencil_consistency_check: '// &
                    'you need to run the full pencil check to find'
                print*, '                          out which ones ('// &
                    'set lpencil_check=T in &run_pars'
                print*, '                          and run again)'
                ldie=.true.
              endif
            else
              print*, 'pencil_consistency_check: '// &
                  'the small pencil check reported no problems'
            endif
          endif
        endif
      enddo
!
!  By default the pencil check does not report optimisation of diagnostics
!  pencils.
!
      if (lroot .and. .not. lpencil_check_diagnos_opti) then
        print*, 'pencil_consistency_check: '// &
            'set lpencil_check_diagnos_opti=T in run.in to report'
        print*, 'pencil_consistency_check: '// &
            'optimisation possibilities for diagnostics pencils'
      endif
!
!  Check pencil initialization for diagnostics.
!
      if (lroot) print*, 'pencil_consistency_check: '// &
          'checking dependence on pencil initialization'
      df=0.0
      if (lpencil_check_no_zeros) then
        call random_seed_wrapper(PUT=iseed_org)
        do i=1,mvar+maux
          if (maxval(abs(f(l1:l2,m1:m2,n1:n2,i)))==0.0) then
            call random_number_wrapper(f_other(:,:,:,i))
          else
            f_other(:,:,:,i)=f(:,:,:,i)
          endif
        enddo
      else
        f_other=f
      endif
      call initialize_pencils(p,0.5*penc0)
!
      lpencil=(lpenc_diagnos.or.lpenc_requested)
      call pde(f_other,df,p)
!
!  Compare results.
!
      lconsistent=.true.
      lconsistent_allproc=.false.
      do k=1,nname
        if (fname(k)/=fname_ref(k)) then
          lconsistent=.false.
          exit
        endif
      enddo
!
      call mpireduce_and(lconsistent,lconsistent_allproc,MPI_COMM_WORLD)
      if (lroot) then
        if (.not. lconsistent_allproc) then
          print*, 'pencil_consistency_check: '// &
              'diagnostics depend on pencil initialization'
          print*, '                          This is a serious problem '// &
              'that may show the use of'
          print*, '                          uninitialized pencils. Check '// &
              'carefully that all'
          print*, '                          pencils are calculated and '// &
              'that conjugate pencils are'
          print*, '                          calculated *after* the '// &
              'individual components'
        else
          print*, 'pencil_consistency_check: '// &
              'diagnostics are independent of pencil initialization'
        endif
      endif
!
!  Clean up.
!
      if (lpencil_check_no_zeros) call random_seed_wrapper(put=iseed_org)
      headt=.true.
      lout=.false.
      lfirst=.false.
      df=0.0
      fweight=0.0
      fname=0.0
      deallocate(df_ref)
      deallocate(fname_ref)
      deallocate(f_other)
!
!  Return the code to its former mortal state.
!
      call life_support_off('end of pencil consistency check')
!
      call stop_it_if_any(ldie,'pencil_consistency_check: one or more tests failed')
!
      lpencil_check_at_work=.false.
!
      if (lroot) print*, 'pencil_consistency_check: all tests passed'
!
    !TP: load dt saved when the pencil consistency check was called
    !This way we are side-effect free
    dt = save_dt
    endsubroutine pencil_consistency_check
!***********************************************************************
endmodule Pencil_check