! file src/main.f90
!
! Copyright 2009-2017 Dalton Harvie (daltonh@unimelb.edu.au)
! 
! This file is part of arb finite volume solver, referred to as `arb'.
! 
! arb is a software package designed to solve arbitrary partial
! differential equations on unstructured meshes using the finite volume
! method.  Primarily it consists of fortran source code, perl source
! code and shell scripts.  arb replies on certain third party software
! to run, most notably the computer algebra system maxima
! <http://maxima.sourceforge.net/> which is released under the GNU GPL.
! 
! The original copyright of arb is held by Dalton Harvie, however the
! project is now under collaborative development.
! 
! arb is released under the GNU GPL.  arb is free software: you can
! redistribute it and/or modify it under the terms of the GNU General
! Public License (version 3) as published by the Free Software Foundation.
! You should have received a copy of the GNU General Public Licence
! along with arb (see file licence/gpl.txt after unpacking).  If not,
! see <http://www.gnu.org/licences/>.
! 
! arb is distributed in the hope that it will be useful, but WITHOUT
! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
! FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public Licence
! for more details.
! 
! For full details of arb's licence see the licence directory.
! 
! The current homepage for the arb finite volume solver project is
! <http://people.eng.unimelb.edu.au/daltonh/downloads/arb>.
!
!-------------------------------------------------------------------------
program arb
                  
! written in f90 with object oriented approach, hopefully
! daltonh, 070608

use general_module
use setup_module
use equation_module
use solver_module
use output_module
!$ use omp_lib

implicit none
character(len=1000) :: formatline
integer :: ierror = 0
logical :: newtconverged
logical, parameter :: debug = .true.

!---------------------------------------------------

formatline = '(a,f4.2,a)'
write(*,fmt=formatline) 'program arb, version ',version,' ('//trim(versionname)//'), written by dalton harvie'

! find number of threads if openmp is in use
!$omp parallel
!$ nthreads = omp_get_num_threads() 
!$omp end parallel
if (nthreads > 1) then
  formatline = '(a,'//trim(dindexformat(nthreads))//',a)'
  write(*,fmt=formatline) 'INFO: openmp version running, with ',nthreads,' threads in use'
else if (nthreads == 1) then
  write(*,fmt=formatline) 'INFO: openmp version running, with 1 thread in use'
else
  write(*,'(a,i2,a)') 'INFO: serial version running'
end if
! now make nthreads = 1 for serial version
nthreads = max(nthreads,1)

call initialise_random_number ! initialise the random seed used to evaluate the arb variable <random>

call time_process
call setup ! sets up variable metadata, reads in values, allocates arrays, creates mesh, initialises fields etc
call time_process(description='setup')

! increment timestep if timestepaddional is specified
if (transient_simulation.and.timestepadditional > 0) timestepmin = max(timestepmin,timestep+timestepadditional)

! output initial conditions if transient and this is the first timestep
if (transient_simulation.and.timestep == 0) then
  call time_process
  call output
  if (trim(output_step_file) == "timestep") call output_step(action="write",do_update_outputs=.false.)
  call time_process(description='initial transient output')
end if

if (.not.transient_simulation) then
  backline = newtline
  newtline = timeline
end if

!---------------------------------------
time_loop: do while ( &
  (transient_simulation.and..not.check_stopfile("stoptime").and.((.not.check_condition("stop").and.timestep < timestepmax).or. &
  timestep < timestepmin)).or..not.transient_simulation) 

  newtres = huge(1.d0)

  if (transient_simulation) then
    timestep = timestep + 1
    formatline = "(a,"//trim(dindexformat(timestep))//",a)"
    write(*,fmt=formatline) repeat('+',timeline)//' timestep ',timestep,' starting '//repeat('+',totalline-timeline)
    if (convergence_details_file) then
      write(fconverge,fmt=formatline) repeat('+',timeline)//' timestep ',timestep,' starting '//repeat('+',totalline-timeline)
      call flush(fconverge)
    end if
    call time_process
    call update_and_check_transients(ierror=ierror)
    call time_process(description='start of timestep update and check transients')
    if (ierror /= 0) then
      write(*,'(a)') 'ERROR: problem completing update_and_check_transients'
      exit time_loop
    end if
    newtstep = 0  ! only reset this for transient simulations, as may be required to carry-on from old newtstep for steady-state simulations - now resetting is delayed to allow saving in a transient
    if (newtient_simulation) then
      call time_process
      call update_and_check_initial_newtients(ierror=ierror)
      call time_process(description='start of timestep update and check initial newtients')
      if (ierror /= 0) then
        write(*,'(a)') 'ERROR: problem completing update_and_check_initial_newtients'
        exit time_loop
      end if
    end if
    call time_process
    call update_and_check_derived_and_equations(ierror=ierror)
    call time_process(description='start of timestep update and check derived and equations')
    if (ierror /= 0) then
      write(*,'(a)') 'ERROR: problem completing update_and_check_derived_and_equations'
      exit time_loop
    end if
  end if

  if (trim(output_step_file) == "newtstep") call output_step(action="write")

! dump solution starting point if newtstepout is set to 1 or dumpnewt is found
  if (check_dumpfile("dumpnewt").or.newtstepout /= 0) then
    write(*,'(a)') 'INFO: user has requested output via a dump file or newtstepout specification'
    call time_process
    call output(intermediate=.true.)
    call output_step(action="write",do_update_outputs=.false.)
    call time_process(description='output')
  end if

!--------------------
! newton loop

  newtconverged = .false.
  if (newtres <= newtrestol) newtconverged = .true.
  if (.not.newtconverged) then
    if (check_condition("convergence")) newtconverged = .true.
  end if

  newt_loop: do while (((.not.newtconverged.and.newtstep < newtstepmax).or. &
      newtstep < newtstepmin).and.ierror == 0)

    newtstep = newtstep + 1

    formatline = "(a,"//trim(dindexformat(newtstep))//",a)"
    write(*,fmt=formatline) repeat('+',newtline)//' newtstep ',newtstep,' starting '//repeat('+',totalline-newtline)
    if (convergence_details_file) then
      write(fconverge,fmt=formatline) repeat('+',newtline)//' newtstep ',newtstep,' starting '//repeat('+',totalline-newtline)
      call flush(fconverge)
    end if

! calculate and check on the equation magnitudes
    call time_process
    call update_magnitudes(ierror)
    call time_process(description='start of newtstep calculating variable magnitudes')
    if (ierror /= 0) then
      write(*,'(a)') 'ERROR: problem completing update_magnitudes'
      exit newt_loop
    end if

! calculate the latest residual, based on the new variable magnitudes
    call time_process
    call residual(ierror=ierror)
    call time_process(description='start of newtstep calculating residual')
    if (ierror /= 0) then
      write(*,'(a)') 'ERROR: problem completing residual calculation'
      exit newt_loop
    end if
    write(*,'(a,g10.3,a)') "INFO: initial newton loop newtres = ",newtres," after updating variable magnitudes"
    if (convergence_details_file) write(fconverge,'(a,g16.9,a)') &
      "INFO: initial newton loop newtres = ",newtres," after updating variable magnitudes"

    if (newtconverged.and.newtstep > newtstepmin) then
      write(*,'(a,g10.3,a)') "INFO: skipping newtsolver as newtres/newtrestol = ",newtres/newtrestol," using existing unknowns"
      if (convergence_details_file) write(fconverge,'(a,g10.3,a)') "INFO: skipping newtsolver as newtres/newtrestol = ", &
        newtres/newtrestol," using existing unknowns"
    else if (ptotal == 0) then
      write(*,'(a)') 'INFO: skipping newtsolver as no equations are being solved'
      if (convergence_details_file) write(fconverge,'(a)') 'INFO: skipping netsolver as no equations are being solved'
    else
      call newtsolver(ierror) ! uses newton's method to solve equations - assumes update has been done and that there is valid magnitudes and a newtres
    end if

! if there is a problem in the newton loop (including a stop file prior to convergence), then exit newton loop here
    if (ierror /= 0) then
      write(*,'(a)') 'ERROR: problem completing newtsolver'
      exit newt_loop
    end if

! update any newtient variables if this is a newtient simulation
    if (newtient_simulation) then
      formatline = "(a,"//trim(dindexformat(newtstep))//",a,g10.3,a,g10.3)"
      write(*,fmt=formatline) 'INFO: during newton loop before newtient updates: newtstep = ',newtstep,': newtres = ',newtres, &
        ': newtres/newtrestol = ',newtres/newtrestol
      if (convergence_details_file) then
        formatline = "(a,"//trim(dindexformat(newtstep))//",a,g16.9,a,g10.3)"
        write(fconverge,fmt=formatline) &
          'INFO: during newton loop before newtient updates: newtstep = ',newtstep,': newtres = ',newtres, &
          ': newtres/newtrestol = ',newtres/newtrestol
      end if
      call time_process
      call update_and_check_newtients(ierror=ierror)
      call time_process(description='intermediate newton step update and check newtients')
      if (ierror /= 0) then
        write(*,'(a)') 'ERROR: problem completing update_and_check_newtients in newtient update section'
        exit newt_loop
      end if
      call time_process
      call update_and_check_derived_and_equations(ierror=ierror)
      call time_process(description='intermediate newton step update and check derived and equations after newtient update')
      if (ierror /= 0) then
        write(*,'(a)') 'ERROR: problem completing update_and_check_derived_and_equations in newtient update section'
        exit newt_loop
      end if
      call residual(ierror=ierror)
      if (ierror /= 0) then
        write(*,'(a)') 'ERROR: problem calculating residual in newtient update section'
        exit newt_loop
      end if
    end if

    if (trim(output_step_file) == "newtstep") call output_step(action="write")

! also start writing output files is newtstep >= newtstepdebugout

    if (check_dumpfile("dumpnewt").or.(newtstepout /= 0.and.mod(newtstep,max(newtstepout,1)) == 0).or.newtstep >= newtstepdebugout) then
      write(*,'(a)') 'INFO: user has requested output via a dump file or newtstepout specification'
      call time_process
      call output(intermediate=.true.)
      call output_step(action="write",do_update_outputs=.false.)
      call time_process(description='output')
    end if

    if (transient_simulation) then
      formatline = "(a,"//trim(dindexformat(newtstep))//",a,"//trim(dindexformat(timestep))//",a,g10.3,a,g10.3)"
      write(*,fmt=formatline) 'INFO: during newton loop: newtstep = ',newtstep,': timestep = ',timestep,': newtres = ',newtres, &
        ': newtres/newtrestol = ',newtres/newtrestol
      if (convergence_details_file) then
        formatline = "(a,"//trim(dindexformat(newtstep))//",a,"//trim(dindexformat(timestep))//",a,g16.9,a,g10.3)"
        write(fconverge,fmt=formatline) &
          'INFO: during newton loop: newtstep = ',newtstep,': timestep = ',timestep,': newtres = ',newtres, &
          ': newtres/newtrestol = ',newtres/newtrestol
      end if
    else
      formatline = "(a,"//trim(dindexformat(newtstep))//",a,g10.3,a,g10.3)"
      write(*,fmt=formatline) 'INFO: during newton loop: newtstep = ',newtstep,': newtres = ',newtres,': newtres/newtrestol = ', &
        newtres/newtrestol
      if (convergence_details_file) then
        formatline = "(a,"//trim(dindexformat(newtstep))//",a,g16.9,a,g10.3)"
        write(fconverge,fmt=formatline) &
          'INFO: during newton loop: newtstep = ',newtstep,': newtres = ',newtres,': newtres/newtrestol = ',newtres/newtrestol
      end if
    end if
    if (convergence_details_file) call flush(fconverge)

! check whether solution is converged
    if (newtres <= newtrestol) newtconverged = .true.
    if (.not.newtconverged) then
      if (check_condition("convergence")) newtconverged = .true.
    end if

! only check for stopfile if output isn't converged
    if (.not.newtconverged) then
      if (check_stopfile("stopnewt")) then
        write(*,'(a)') 'INFO: user has requested simulation stop via a stop file'
        ierror = -1 ! negative ierror indicates that user stopped arb before convergence complete
      end if
    end if

    formatline = "(a,"//trim(dindexformat(newtstep))//",a)"
    write(*,fmt=formatline) repeat('-',newtline)//' newtstep ',newtstep,' ending '//repeat('-',totalline-newtline+2)
    if (convergence_details_file) then
      write(fconverge,fmt=formatline) repeat('-',newtline)//' newtstep ',newtstep,' ending '//repeat('-',totalline-newtline+2)
      call flush(fconverge)
    end if

  end do newt_loop
!--------------------

  if (ierror > 0) then
    formatline = "(a,"//trim(dindexformat(ierror))//")"
    write(*,fmt=formatline) 'ERROR: problem in some solution routine within newton loop: error number = ',ierror
    exit time_loop
  else if (ierror < 0) then
    write(*,'(a)') 'ERROR: newton solver did not converge due to user created stop file'
    exit time_loop
  else if (newtconverged) then
    if (newtres <= newtrestol) then
      write(*,'(a)') 'INFO: newton iterations have converged due to newtres condition'
    else
      write(*,'(a)') &
        'INFO: user-specified newton loop convergence condition satisfied'
    end if
  else
    write(*,'(a)') 'ERROR: newton solver did not converge'
    ierror = 5
    exit time_loop
  end if

! if user has requested to halt then write message
  if (transient_simulation.and.check_stopfile("stoptime")) write(*,'(a)') &
    'INFO: user has requested simulation stop via a stop file'

! silly bell functionality!
  if (check_condition("bell")) call ring_bell

! write output if output is due, or we are finishing
  if ((transient_simulation.and.(check_condition("output").or.(timestepout /= 0.and.mod(timestep,max(timestepout,1)) == 0).or. &
    check_condition("stop").or.timestep >= timestepmax.or.check_stopfile("stoptime").or.check_dumpfile("dumptime"))).or. &
    .not.transient_simulation) then
    if (check_dumpfile("dumptime")) write(*,'(a)') 'INFO: user has requested output via a dump file'
    call time_process
    if (output_timings.and.output_timings_on_mesh_write.and.(timestepout /= 0.and.mod(timestep,max(timestepout,1)) == 0)) &
      write(*,'(2(a,g10.3))') 'TIMING: total wall time = ',total_wall_time,': total cpu time = ',total_cpu_time  
    call output
    if (trim(output_step_file) == "timestep") call output_step(action="write",do_update_outputs=.false.)
    call time_process(description='output')
  else
    if (trim(output_step_file) == "timestep") call output_step(action="write")
  end if

  if (transient_simulation) then
    formatline = "(a,"//trim(dindexformat(timestep))//",a)"
    write(*,fmt=formatline) repeat('-',timeline)//' timestep ',timestep,' ending '//repeat('-',totalline-timeline+2)
     
    if (convergence_details_file) then
      write(fconverge,fmt=formatline) repeat('-',timeline)//' timestep ',timestep,' ending '//repeat('-',totalline-timeline+2)
      call flush(fconverge)
    end if
  end if

! if not a transient simulation then exit loop
  if (.not.transient_simulation) exit time_loop

end do time_loop
!---------------------------------------

if (trim(output_step_file) == "final") call output_step(action="write")

if (output_timings) write(*,'(2(a,g10.3))') 'TIMING: total wall time = ',total_wall_time,': total cpu time = ',total_cpu_time

! if there was an error or earlier stop requested then exit without closing timestep
if (ierror /= 0) then
  write(*,'(a)') "WARNING: the last output is not converged"
  write(*,'(a)') 'INFO: a debug output file (debug.output.msh) is being written that contains the current values of '// &
    'all variable components'
  call output(debug_dump=.true.)
  if (trim(output_step_file) == "timestep") call output_step(action="write",do_update_outputs=.false.)
  write(*,'(a)') "ERROR: the simulation was not successful"
else
  write(*,'(a)') "SUCCESS: the simulation finished gracefully"
end if

if (convergence_details_file) close(fconverge)
call output_step(action="close")

if (ierror /= 0) call exit(ierror) ! exit while setting ierror as exit status

end program arb

!-----------------------------------------------------------------