!-*- mode: F90 -*-! !------------------------------------------------------------! ! Copyright (C) 2026 Wannier Developer Group ! ! ! ! This library is free software; you can redistribute it ! ! and/or modify it under the terms of the GNU Lesser General ! ! Public License as published by the Free Software ! ! Foundation; either version 2.1 of the License, or (at your ! ! option) any later version. ! ! ! ! This library 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 Lesser General Public License for ! ! more details. ! ! ! ! You should have received a copy of the GNU Lesser General ! ! Public License along with this library; if not, see ! ! <https://www.gnu.org/licenses/>. ! ! ! ! The webpage of the Wannier90 code is ! ! <https://www.wannier.org>. ! ! ! ! The Wannier90 code is hosted on GitHub ! ! <https://github.com/wannier-developers/wannier90> ! !------------------------------------------------------------! ! ! ! w90_io: file io and timing functions ! ! ! !------------------------------------------------------------! module w90_io !! Module to handle operations related to file input and output. use w90_constants, only: dp implicit none private character(len=10), parameter, public :: w90_version = '4.0.3 ' !! Label for this version of wannier90 public :: io_stopwatch_start public :: io_stopwatch_stop public :: io_commandline public :: io_date public :: io_print_timings public :: io_time public :: io_wallclocktime public :: prterr public :: print_error_halt contains ! was io_stopwatch(tag, 1, error), acts on stopwatch 1 !===================================== subroutine io_stopwatch_start(tag, timers) !===================================== !! Stopwatch to time parts of the code !===================================== use w90_types, only: timer_list_type, nmax implicit none ! arguments type(timer_list_type), intent(inout) :: timers character(len=*), intent(in) :: tag !! Which stopwatch to act upon !integer, intent(in) :: mode !! Action 1=start 2=stop ! local variables integer :: i real(kind=dp) :: t call cpu_time(t) do i = 1, timers%nnames if (timers%clocks(i)%label .eq. tag) then timers%clocks(i)%ptime = t timers%clocks(i)%ncalls = timers%clocks(i)%ncalls + 1 return end if end do if (.not. timers%overflow) then if (timers%nnames == nmax) then timers%overflow = .true. else timers%nnames = timers%nnames + 1 timers%clocks(timers%nnames)%label = tag timers%clocks(timers%nnames)%ctime = 0.0_dp timers%clocks(timers%nnames)%ptime = t timers%clocks(timers%nnames)%ncalls = 1 end if end if return end subroutine io_stopwatch_start ! was io_stopwatch(tag, 2, error), acts on stopwatch 2 !===================================== subroutine io_stopwatch_stop(tag, timers) !===================================== !! Stopwatch to time parts of the code !===================================== use w90_types, only: timer_list_type implicit none ! arguments character(len=*), intent(in) :: tag type(timer_list_type), intent(inout) :: timers !! Which stopwatch to act upon !integer, intent(in) :: mode !! Action 1=start 2=stop ! local variables integer :: i real(kind=dp) :: t call cpu_time(t) do i = 1, timers%nnames if (timers%clocks(i)%label .eq. tag) then timers%clocks(i)%ctime = timers%clocks(i)%ctime + t - timers%clocks(i)%ptime return end if end do return end subroutine io_stopwatch_stop !================================================ subroutine io_print_timings(timers, stdout) !================================================ ! !! Output timing information to stdout ! !================================================ use w90_types, only: timer_list_type implicit none type(timer_list_type), intent(in) :: timers integer, intent(in) :: stdout integer :: i if (timers%overflow) then write (stdout, '(1x,a)') 'Warning: Timer array overflowed, some timing data has been lost' end if write (stdout, '(/1x,a)') '*===========================================================================*' write (stdout, '(1x,a)') '| TIMING INFORMATION |' write (stdout, '(1x,a)') '*===========================================================================*' write (stdout, '(1x,a)') '| Tag Ncalls Time (s)|' write (stdout, '(1x,a)') '|---------------------------------------------------------------------------|' do i = 1, timers%nnames write (stdout, '(1x,"|",a50,":",i10,4x,f10.3,"|")') & timers%clocks(i)%label, timers%clocks(i)%ncalls, timers%clocks(i)%ctime end do write (stdout, '(1x,a)') '*---------------------------------------------------------------------------*' return end subroutine io_print_timings !================================================ subroutine io_commandline(prog, dryrun, post_proc_flag, seedname) !================================================ ! !! Parse the commandline ! !================================================ implicit none character(len=:), allocatable, intent(in) :: prog !! Name of the calling program logical, intent(out) :: dryrun, post_proc_flag !! Have we been asked for a dryrun character(len=:), allocatable, intent(inout) :: seedname integer :: num_arg, loop character(len=50), allocatable :: ctemp(:) logical :: print_help, print_version character(len=10) :: help_flag(3), version_flag(3), dryrun_flag(3) help_flag(1) = '-h ' help_flag(2) = '-help ' help_flag(3) = '--help ' version_flag(1) = '-v ' version_flag(2) = '-version ' version_flag(3) = '--version ' dryrun_flag(1) = '-d ' dryrun_flag(2) = '-dryrun ' dryrun_flag(3) = '--dryrun ' post_proc_flag = .false. print_help = .false. print_version = .false. dryrun = .false. num_arg = command_argument_count() allocate (ctemp(num_arg)) do loop = 1, num_arg call get_command_argument(loop, ctemp(loop)) end do if (num_arg == 0) then ! program called without any argument print_help = .true. elseif (num_arg == 1) then ! program called with one argument if (any(index(ctemp(1), help_flag(:)) > 0)) then print_help = .true. elseif (any(index(ctemp(1), version_flag(:)) > 0)) then print_version = .true. elseif ((ctemp(1) (1:1) == '-')) then !catch any other flag. Note seedname can't start with '-' print_help = .true. else ! must be the seedname seedname = trim(ctemp(1)) end if else ! not 2 - as mpi call might add commands to argument list if (any(index(ctemp(1), help_flag(:)) > 0)) then print_help = .true. elseif (any(index(ctemp(1), version_flag(:)) > 0)) then print_version = .true. elseif (any(index(ctemp(1), dryrun_flag(:)) > 0)) then dryrun = .true. seedname = trim(ctemp(2)) if (seedname(1:1) == '-') print_help = .true. elseif (index(ctemp(1), '-pp') > 0) then post_proc_flag = .true. seedname = trim(ctemp(2)) if (seedname(1:1) == '-') print_help = .true. else ! must be the seedname seedname = trim(ctemp(1)) if (seedname(1:1) == '-') print_help = .true. end if end if if (print_help) then if (prog == 'wannier90') then write (6, '(a)') 'Wannier90: The Maximally Localised Wannier Function Code' write (6, '(a)') 'http://www.wannier.org' write (6, '(a)') ' Usage:' write (6, '(a)') ' wannier90.x <seedname> : Runs file <seedname>.win' write (6, '(a)') ' wannier90.x -pp <seedname> : Write postprocessing files for <seedname>.win' write (6, '(a)') ' wannier90.x [-d|--dryrun] <seedname> : Perform a dryrun calculation on files <seedname>.win' write (6, '(a)') ' wannier90.x [-v|--version] : print version information' write (6, '(a)') ' wannier90.x [-h|--help] : print this help message' elseif (prog == 'postw90') then write (6, '(a)') 'postw90: Post-processing for the Wannier90 code' write (6, '(a)') 'http://www.wannier.org' write (6, '(a)') ' Usage:' write (6, '(a)') ' First run wannier90.x then' write (6, '(a)') ' postw90.x <seedname> : Runs file <seedname>.win' write (6, '(a)') ' postw90.x [-d|--dryrun] <seedname> : Perform a dryrun calculation on files <seedname>.win' write (6, '(a)') ' postw90.x [-v|--version] : print version information' write (6, '(a)') ' postw90.x [-h|--help] : print this help message' end if stop end if if (print_version) then if (prog == 'wannier90') then write (6, '(a,a)') 'Wannier90: ', trim(w90_version) elseif (prog == 'postw90') then write (6, '(a,a)') 'Postw90: ', trim(w90_version) end if stop end if ! If on the command line the whole seedname.win was passed, I strip the last ".win" if (len(trim(seedname)) .ge. 5) then if (seedname(len(trim(seedname)) - 4 + 1:) .eq. ".win") then seedname = seedname(:len(trim(seedname)) - 4) end if end if end subroutine io_commandline ! !================================================ ! subroutine io_error(error_msg, stdout, seedname) ! !================================================ ! ! ! !! Abort the code giving an error message ! ! ! !================================================ ! ! implicit none ! ! character(len=*), intent(in) :: error_msg ! character(len=50), intent(in) :: seedname ! integer :: stdout ! ! ! calls mpi_abort on mpi_comm_world iff compiled with MPI support ! call comms_abort(seedname, error_msg, stdout) ! close (stdout) ! ! write (*, '(1x,a)') trim(error_msg) ! write (*, '(A)') "Error: examine the output/error file for details" ! !#ifdef EXIT_FLAG ! call exit(1) !#else ! STOP !#endif ! ! end subroutine io_error !================================================ subroutine io_date(cdate, ctime) !================================================ ! !! Returns two strings containing the date and the time !! in human-readable format. Uses a standard f90 call. ! !================================================ implicit none character(len=9), intent(out) :: cdate !! The date character(len=9), intent(out) :: ctime !! The time character(len=3), dimension(12) :: months data months/'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', & 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'/ integer date_time(8) ! call date_and_time(values=date_time) ! write (cdate, '(i2,a3,i4)') date_time(3), months(date_time(2)), date_time(1) write (ctime, '(i2.2,":",i2.2,":",i2.2)') date_time(5), date_time(6), date_time(7) end subroutine io_date !================================================ function io_time() !================================================ ! !! Returns elapsed CPU time in seconds since its first call. !! Uses standard f90 call ! !================================================ use w90_constants, only: dp implicit none real(kind=dp) :: io_time ! t0 contains the time of the first call ! t1 contains the present time real(kind=dp) :: t0, t1 logical :: first = .true. save first, t0 ! call cpu_time(t1) ! if (first) then t0 = t1 io_time = 0.0_dp first = .false. else io_time = t1 - t0 end if return end function io_time !================================================! function io_wallclocktime() !================================================! ! Returns elapsed wall clock time in seconds since its first call ! ! !================================================ use w90_constants, only: dp, i64 implicit none real(kind=dp) :: io_wallclocktime integer(kind=i64) :: c0, c1 integer(kind=i64) :: rate logical :: first = .true. save first, rate, c0 if (first) then call system_clock(c0, rate) io_wallclocktime = 0.0_dp first = .false. else call system_clock(c1) io_wallclocktime = real(c1 - c0)/real(rate) end if return end function io_wallclocktime subroutine prterr(error, ie, istdout, istderr, comm) use w90_comms, only: comms_no_sync_bcast, comms_no_sync_send, comms_no_sync_recv, & w90_comm_type, mpirank, mpisize use w90_error_base, only: code_deactivated, code_remote, w90_error_type ! arguments integer, intent(inout) :: ie ! global error value to be returned integer, intent(in) :: istderr, istdout type(w90_comm_type), intent(in) :: comm type(w90_error_type), allocatable, intent(inout) :: error ! local variables type(w90_error_type), allocatable :: le ! unchecked error state for calls made in this routine integer :: je ! error value on remote ranks integer :: j ! rank index integer :: failrank ! lowest rank reporting an error character(len=128) :: mesg ! only print 128 chars of error ie = 0 mesg = 'not set' if (mpirank(comm) == 0) then ! currently this printout will list only the lowest failing rank, not all failing ranks do j = mpisize(comm) - 1, 1, -1 call comms_no_sync_recv(je, 1, j, le, comm) if (je /= code_remote .and. je /= 0) then failrank = j ie = je call comms_no_sync_recv(mesg, 128, j, le, comm) end if end do ! if the error is on rank0 if (error%code /= code_remote .and. error%code /= 0) then failrank = 0 ie = error%code mesg = error%message end if write (istdout, *) 'Exiting.......' write (istdout, '(1x,a)') trim(mesg) write (istdout, '(1x,a,i0,a)') '(rank: ', failrank, ')' write (istderr, *) 'Exiting.......' write (istderr, '(1x,a)') trim(mesg) write (istderr, '(1x,a,i0,a)') '(rank: ', failrank, ')' !write (istderr, '(1x,a)') 'error encountered; check .wout log' else ! non 0 ranks je = error%code call comms_no_sync_send(je, 1, 0, le, comm) if (je /= code_remote .and. je /= 0) then ie = je ! also set failed status on non 0 ranks mesg = error%message call comms_no_sync_send(mesg, 128, 0, le, comm) end if end if ! Every rank must report the same failure. A non-root rank whose own error is ! code_remote (the error originated elsewhere) leaves ie at 0 above and would ! otherwise return "success" to a library caller while root returns the failure. call comms_no_sync_bcast(ie, 1, le, comm) flush (istdout) flush (istderr) error%code = code_deactivated deallocate (error) ! else allocated error trips uncaught error mechanism (ifdef W90DEV, see io.F90) end subroutine prterr subroutine print_error_halt(error, ie, istdout, istderr, comm) use w90_comms, only: w90_comm_type use w90_error_base, only: w90_error_type ! arguments integer, intent(inout) :: ie ! global error value to be returned integer, intent(in) :: istderr, istdout type(w90_comm_type), intent(in) :: comm type(w90_error_type), allocatable, intent(inout) :: error call prterr(error, ie, istdout, istderr, comm) stop 1 end subroutine print_error_halt end module w90_io