TABLE OF CONTENTS


::pwtk::pseudo::etot_test

SYNOPSIS

proc ::pwtk::pseudo::etot_test {args} {

USAGE

   ::pwtk::pseudo::etot_test [options] pseudopotentialFile

DESCRIPTION

Test a pseudopotential for the minimum ecutwfc and, for US & PAW, also ecutrho, using the criterium based on total-energy. If the -d option is explicitly specified, then ecutrho is tested also for a NC pseudopotential.

OPTIONS

In most case no options are needed, hence use them only if the default setup fails.

    -e ECUTWFC_LIST --- (default = {30 60 90 120}) list of ecutwfc values for initial scanning,
                         minimum of 4 values needed
    -d DUAL         --- (default = 16) initial value of dual for US and PAW potentials
    -thr THRESHOLD  --- (default = 1e-3) ecutwfc convergence treshold for total energy (in Ry)
    -u UPSCALE      --- (default = 4) upscale factor for the ecutrho threshold, i.e.,
                        rhothr = wfcthr/upscale
    -n NCALCS       --- (default = 6) number of the pw.x SCF calcs during the refinement stage
    -l LOW_FACTOR   --- (default = 0.8) "low" factor for ecutwfc for the refinement stage, i.e.,
                        ecutwfc_min = low_factor*ecutwfc
    -h HIGH_FACTOR  --- (default = 2.0) "high" factor for ecutwfc for the refinement stage, i.e.,
                        ecutwfc_max = high_factor*ecutwfc
    -m MIN_ECUTWFC  --- (default = 10) minimum allowed ecutwfc (in Ry)
    -M MAX_ECUTWFC  --- (default = 1000) maximum allowed ecutwfc (in Ry)
    -t TERM         --- a Gnuplot file terminal to plot to (one of eps, pdf, png, svg)
    -v              --- visualize the resulting plot

ARGUMENTS

RETURN VALUE

The determined ecutwfc and ecutrho values as the "$ecutwfc $ecutrho" list.

SOURCE

    variable E
    variable gpData
    variable opts
    
    ::pwtk::printTitle PSEUDO "Automatic search of \"ecutwfc\" & \"ecutrho\" for a pseudo-potential\n(based on simple total-energy criterium)"

    if { [auto_execok gnuplot] == {} } {
        ::pwtk::error "[::pwtk::procName] requires gnuplot, but gnuplot was not found" 1
    }

    # just in case
    catch { unset E }
    
    ::pwtk::input::push; # do not move this push down the code, it must be called at the beginning

    # ------------------------------------------------------------------------
    #
    # 0. option-processing & init setup
    #
    # ------------------------------------------------------------------------
    
    set narg 1
    set usage "?-e ECUTWFC_LIST?  ?-d DUAL?  ?-t THRESHOLD?  ?-u UPSCALE?  ?-l LOW_FACTOR?  ?-h HIGH_FACTOR?  ?-m MIN_ECUTWFC?  ?-M MAX_ECUTWFC?  ?-v?  pseudopotentialFile"
    set options [subst {
        {e.arg   {}  "list of ecutwfc value for initial scanning (minimum of 4 value needed; default = 30 60 90 120)"}
        {d.arg   $opts(dual)    "value of dual for US and PAW potentials"}
        {thr.arg $opts(thr)     "ecutwfc convergence treshold for total energy (in Ry)"}
        {u.arg   $opts(upscale) "upscale factor for ecutrho threshold, i.e., rhothr = thr/upscale"}
        {n.arg   $opts(ncalc)   "number of pw.x calcs during the refinement stage"}
        {l.arg   $opts(low_factor)  "low factor of ecutwfc for the refinement stage"}
        {h.arg   $opts(high_factor) "high factor of ecutwfc for the refinement stage"}
        {m.arg   $opts(min_ecutwfc) "minimum allowed ecutwfc (if ecutwfc gets lower, drop error)"}
        {M.arg   $opts(max_ecutwfc) "maximum allowed ecutwfc (if ecutwfc gets larger, drop error)"}
        {t.arg   $::pwtk::gp::gp(workflow.terminal)  "a Gnuplot file terminal to plot to"}
        {v       "visualize the resulting plot"}
    }]
    
    ::pwtk::parseOpt_
    ::pwtk::checkOType_ -e $opt(e) {numberlist double}  "list of numbers"
    ::pwtk::checkOTypeStrict_ -d $opt(d) double number; # if dual < 0 ==> dual tests performed only for US & PAW pseudos
    ::pwtk::checkOTypeStrict_ -thr $opt(thr) {number posreal} "positive number"
    ::pwtk::checkOTypeStrict_ -u $opt(u) {number posreal} "positive number"
    ::pwtk::checkOTypeStrict_ -n $opt(n) {number posint}  "positive integer"
    ::pwtk::checkOTypeStrict_ -l $opt(l) {number posreal} "positive number"
    ::pwtk::checkOTypeStrict_ -h $opt(h) {number posreal} "positive number"
    ::pwtk::checkOTypeStrict_ -M $opt(M) {number posreal} "positive number"
    ::pwtk::checkOTypeStrict_ -m $opt(m) {number posreal} "positive number"
    ::pwtk::checkOTypeStrict_ -t $opt(t) {optionlist eps pdf png svg}

    set TERM   [string toupper $opt(t)]
    set ecutL  [lsort -real [lmap e [regsub -all , $opt(e) { }] { expr round($e) }]]
    set dual   $opt(d)
    set thr    $opt(thr)
    set rhothr [format %.1e [expr $opt(thr)/$opt(u)]]
    set thr_orig $thr

    if { $dual < 0 } {
        if { [needDual $args] } {
            # use the default daul 
            set dual [expr abs($dual)]        
        } else {
            # no dual tests for norm-conserving PP
            set dual 4
        }
    }

    # get info from the PP filename

    set ppFile $args
    set name [file rootname [file tail $ppFile]]
    set elem [elemFromPPName $name]
    set type [typeFromPPName $name]

    set suggested {}
    if { $ecutL eq {} } {
        # try to read suggested minimum cutoff for wavefunctions from PP file
        set e_ [ecutwfcFromPPFile $ppFile]
        if { $e_ ne {} } {
            set ecutL  [::pwtk::doubleSeq $opt(l)*$e_  $opt(h)*$e_  4 %.1f]
            set suggested "(the suggested minimum ecutwfc = $e_ Ry from the PP file was used)\n"
        } else {
            # use the defaults
            set ecutL $opts(ecutwfc_list)
        }
    }
    set eFirst [lindex $ecutL 0]
    set eLast  [lindex $ecutL end]

    # setup pw.x input
    
    minimalSetup $elem $ppFile
    
    # printout
    
    ::pwtk::print PSEUDO "Basic info:
   pseudopotential file     :  $ppFile
   pseudopotential type     :  $type
   chemical element         :  $elem
   total energy threshold   :  $thr Ry (for ecutwfc),  $rhothr Ry (for ecutrho)
   initial dual for ecutrho :  $dual
   initial ecutwfc scan     :  [join $ecutL {, }] Ry
   $suggested   
   Using ...          ibrav :  [::pwtk::pwi::ibrav]
                       alat :  [::pwtk::pwi::alat]
"

    # defs for running pw.x calcs and storing the results
    
    set runPW_ {
        SYSTEM [subst {
            ecutwfc = $ecut
            ecutrho = $dual*$ecut
        }]
        set scf scf.$name.ecut_$ecut.dual_$dual
        if { [info exists E($ecut,$dual)] } {
            ::pwtk::print "The pw.x calculation \"$scf.out\" already performed.\n"
        } else {          
            ::pwtk::runPW $scf
        }
        set E($ecut,$dual) [::pwtk::pwo::totene $scf.out]
    }
    
    set buildDat_ {
        set dat "# (dual = $dual)
# ecutwfc   E(total)  (in Ry)\n"
        foreach ecut $ecutL {
            append dat [format "   %6.1f %12.6f\n" $ecut $E($ecut,$dual)]
        }
        ::pwtk::print $dat
        ::pwtk::writeFile $head.dat $dat
    }

    # ------------------------------------------------------------------------
    #
    # 1. initial SCAN of the ecut estimate
    #
    # ------------------------------------------------------------------------

    set head ecutwfc.init.$name    
    set stage_ init
    set term {}
    set plot {}
    set param [::fileutil::tempfile param.]; # file where gnuplot stores the parameters
    
    foreach ecut $ecutL {
        eval $runPW_
    }
    
    # if total-energy difference is too low/high, perform one more calculation
    
    if { [expr abs($E($eLast,$dual) - $E($eFirst,$dual))] < 2*$thr } {
        # too-low
        set ecut [expr round(0.67*$eFirst)]        
        set ecutL [linsert $ecutL 0 [set eFirst $ecut]]

        ::pwtk::print PSEUDO "Total-energy difference between min and max tested ecutwfc is too low.
Performing additional calculation for ecutwfc = $ecut Ry
"        
        eval $runPW_
        
    } elseif { [expr abs($E($eLast,$dual) - $E([lindex $ecutL end-1],$dual))] >= $thr } {
        # too-high
        set ecut [expr $eLast + 30]
        set eFirst [lindex $ecutL 1]; # drop the 1st point (take the 2nd point as the first)
        set eLast  $ecut
        lappend ecutL $ecut

        ::pwtk::print PSEUDO "Total-energy difference between the last two points is too high.
Performing additional calculation for ecutwfc = $ecut Ry
"
        eval $runPW_
    }

    set Ntrials 3;  # maximum number of trials
    for {set i 1} {$i <= $Ntrials} {incr i} {

        eval $buildDat_
        ::pwtk::print "Initial energy datafile written to:   $head.dat"

        # initial Gnuplot fitting, f(ecut) = E0 + a*exp(-b*ecut)
        # parameters E0, a, b will be estimated and "loaded" into PWTK

        gpFit_
    
        # thr = a*exp(-b*ecutwfc) --> ecutwfc = -1/b ln(thr/a)
    
        set ecutwfc [expr round(-1.0/$b * log ($thr/$a))]
        ::pwtk::print PSEUDO "Initial estimate of ecutwfc = $ecutwfc Ry  (attempt #.$i)"

        # check that ecutwfc is within a reasonable range

        if { $ecutwfc > 0.0 && $ecutwfc <= $opt(M) } {
            # success !
            break
        } else {
            # perform another calculation or give-up

            if { $ecutwfc <= 0.0 } {
                set str "estimated ecutwfc <= 0"
            } elseif { $ecutwfc > $opt(M) } {
                set str "estimated ecutwfc is too high"
            }

            if { $i < $Ntrials } {
                ::pwtk::warning "The estimation of ecutwfc failed ($str)."
                ::pwtk::print PSEUDO "Expanding initial ecutwfc scan..."

                if { $eFirst >= $opt(m) && [expr abs($E($eLast,$dual) - $E($eFirst,$dual))] < 2*$thr } {
                    # super-soft && $eFirst high-enough
                    set ecut  [expr round(0.67*$eFirst)]
                    set ecutL [linsert $ecutL 0 [set eFirst $ecut]]
                } elseif { $ecutwfc < $opt(M) } {
                    # super-soft && $eFirst too-low
                    set ecut [expr $eLast + 30]
                    lappend ecutL [set eLast $ecut]
                } else {
                    # super-hard case
                    set ecut [expr $eLast + 100]
                    lappend ecutL [set eLast $ecut]
                }
                ::pwtk::print PSEUDO "Performing additional calculation for ecutwfc = $ecut Ry\n"
                eval $runPW_
            } elseif { $ecutwfc > $opt(M) } {
                # maximum number of trials exceeded & still $ecutwfc > $opt(M);
                # give-up
                clean_
                ::pwtk::error "The estimation of ecutwfc failed ($str)." 1
            }
        }
    }
    
    # ------------------------------------------------------------------------
    #
    # 2. Refined estimation of the ecut
    #
    # ------------------------------------------------------------------------

    set head ecutwfc.$name
    
    if { $ecutwfc < $opt(m) } {
        set thr [expr 0.5*$thr]; # new
        ::pwtk::warning "The estimated ecutwfc ($ecutwfc Ry) is too low. Using $opt(m) Ry instead.
The ecutwfc threshold was made tighter, $thr Ry."
        set ecutwfc $opt(m)
    }

    set stage_ refine        
    set eFirst [expr round($opt(l)*$ecutwfc)]    
    set dEmin 5; # dE should not be too low (min = $dEmin Ry)
    set dE [expr max($dEmin, round(($opt(h)*$ecutwfc - $eFirst)/($opt(n)-1)))]
    unset ecutL 
    for {set i 0} {$i < $opt(n)} {incr i} {
        set ecut [expr $eFirst + $i*$dE]
        lappend ecutL $ecut
    }
    set eLast $ecut

    ::pwtk::print PSEUDO "Performing refining calculations at ecutwfc = [join $ecutL {, }] Ry\n"

    foreach ecut $ecutL {
        eval $runPW_
    }
    eval $buildDat_
    ::pwtk::print "Refined energy datafile written to:   $head.dat"

    # refined gnuplot fitting & plotting
    
    gpPlot_
    gpFit_
    
    set ecutwfc [expr round(-1.0/$b * log ($thr/$a))]
    ::pwtk::print PSEUDO "Refined estimate of ecutwfc = $ecutwfc Ry\n"

    set super_soft 0
    if { $ecutwfc < $opt(m) } {        
        ::pwtk::warning "Pseudopotential \"$ppFile\" appears super soft, ecutwfc < $opt(m) Ry
Using $opt(m) Ry instead."
        set ecutwfc $opt(m)
        set super_soft 1

    } elseif { $ecutwfc > $opt(M) } {
        set posErr "The estimation of ecutwfc failed (predicted ecutwfc is too high).
Maximum allowed ecutwfc = $opt(M) Ry. It can be increased with the -M option"        
        ::pwtk::warning $posErr

        # make a new calc with ecutwfc += 100 Ry
        
        lappend ecutL [set ecut [incr eLast 100]]
        ::pwtk::print PSEUDO "Performing additional pw.x calculation at ecutwfc = $ecut Ry\n"
        eval $runPW_
        eval $buildDat_

        # refined gnuplot fitting & plotting
        
        gpPlot_
        gpFit_
        
        set ecutwfc [expr round(-1.0/$b * log ($thr/$a))]
        ::pwtk::print PSEUDO "2nd refined estimate of ecutwfc = $ecutwfc Ry\n"
        
        if { $ecutwfc > $opt(M) } {
            # still too-high, give-up
            clean_
            ::pwtk::error $posErr 1
        }
    }
    
    set txt "  ecutwfc  =  [format %3d $ecutwfc] Ry (for threshold = $thr Ry)"
    if { $super_soft } {
        ::pwtk::print $txt\n        
    } else {
        set E2 [expr round(-1.0/$b * log (2*$thr/$a))]
        set E3 [expr round(-1.0/$b * log (3*$thr/$a))]
        ::pwtk::print "$txt
         ( =  [format %3d $E2] Ry (for threshold = 2*$thr Ry) )
         ( =  [format %3d $E3] Ry (for threshold = 3*$thr Ry) )\n"
    }
    
    # for norm-conserving PP (dual = 4)
    set ecutrho [expr $dual*$ecutwfc]
    set ecutrho_dat N/A
    set ecutrho_thr N/A
    
    # ------------------------------------------------------------------------
    #
    # 3. check if dual can be lowered
    #
    # ------------------------------------------------------------------------
    
    if { $dual > 4 } {
        set dHigh $dual
        set dLow  4
        set ecut  $ecutwfc; # BEWARE: this is needed by $runPW_ !
        for {set i $dual} { $i >= $dLow } {incr i -2} {
            lappend dualL $i
        }
        ::pwtk::print PSEUDO "Checking if dual can be decreased...\n"
        ::pwtk::print "  Performing calculations for ecutwfc = $ecut Ry
                             and dual = [join $dualL {, }] 
"
        foreach dual $dualL {
            eval $runPW_
        }
        
        # write the ecutrho datafile
        
        set dat "# (data for ecutwfc = $ecut Ry)
# dual   E(total)      ecutrho  (in Ry)\n"
        foreach dual $dualL {
            append dat [format "   %3d %12.6f   %6.1f\n" $dual $E($ecut,$dual) [expr $dual*$ecut]]
        }
        ::pwtk::print $dat
        ::pwtk::writeFile ecutrho.$name.dat $dat

        # get a minimum dual: be $opt(u)-times more tight for the dual threshold

        set N [expr int($opt(u)) + 1]        
        for {set i 1} {$i <= $N} {incr i} {            
            set dualMin($i) $dLow      
            set t($i) [format %.1e [expr double($i*$thr_orig)/$opt(u)]]
            foreach dual $dualL {
                if { abs($E($ecut,$dual) - $E($ecut,$dHigh)) <= $t($i) } {
                    set dualMin($i) $dual
                } else {
                    break
                }
            }
        }

        # print-out
        ::pwtk::print PSEUDO "Estimated dual based on total-energy threshold:"
        ::pwtk::print "  dual = [format %2d $dualMin(1)], ecutrho = [format %3d [expr $ecut*$dualMin(1)]] Ry (for threshold = $t(1) Ry)"                
        for {set i 2} {$i <= $N} {incr i} {
            set i1 [expr $i-1]
            if { $dualMin($i1) != $dualMin($i) } {
                ::pwtk::print "     ( = [format %2d $dualMin($i)],         = [format %3d [expr $ecut*$dualMin($i)]] Ry (for threshold = $t($i) Ry) )"
            }
        }                
        puts ""
        
        set dual    $dualMin(1)
        set ecutrho [expr $dual*$ecutwfc]
        set ecutrho_dat ecutrho.$name.dat
        set ecutrho_thr [format {%.1e Ry} $t(1)]
    }
    
    ::pwtk::print PSEUDO "Final results for \"$ppFile\""
    ::pwtk::print "
  Generated files:
     ecutwfc data files    :  ecutwfc.$name.dat  ecutwfc.init.$name.dat
     Gnuplot plotting files:  ecutwfc.$name.gp   ecutwfc.init.$name.gp
     results plotted to $TERM:  ecutwfc.$name.$opt(t)

     ecutrho data file     :  $ecutrho_dat

  Used total-energy based thresholds:
     ecutwfc_threshold = [format %.1e $thr] Ry
     ecutrho_threshold = $ecutrho_thr

  Estimated energy cutoffs:
     ecutwfc = $ecutwfc Ry
     ecutrho = $ecutrho Ry  (dual = $dual)
"

    if { $opt(v) } {
        ::pwtk::display ecutwfc.$name.$opt(t)
    }

    # cleanup
    clean_

    return [list $ecutwfc $ecutrho]
}