Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

qmc-fit: Added equation of states and morse fits with jackknife #4518

Merged
merged 9 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 69 additions & 3 deletions docs/analyzing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1061,9 +1061,38 @@ Using the qmc-fit tool for statistical time step extrapolation, trial wavefuncti
-----------------------------------------------------------------------------------------------------------------

The ``qmc-fit`` tool is used to provide statistical estimates of curve-fitting parameters based on QMCPACK data.
``qmc-fit`` is currently limited to estimating fitting parameters related to time step extrapolation and trial wavefunction
optimization (optimal U for DFT+U, EXX fractions), it will eventually support many types of fitted curves (e.g., Morse
potential binding curves and various equation-of-state fitting curves).
``qmc-fit`` is currently estimates fitting parameters related to time step extrapolation and trial wavefunction
optimization (optimal U for DFT+U, EXX fractions) and supports many types of fitted curves (e.g., Morse
potential binding curves and various equation-of-state fitting curves). An overview of all supported input flags to
``qmc-fit`` can be obtained by typing ``qmc-fit -h`` at the command line:

::

>qmc-fit -h
usage: qmc-fit [-h] [-f FIT_FUNCTION] (-t TIMESTEPS | -u HUBBARDS | --exx EXX | --eos EOS) [-s SERIES_START] [-e EQUILS] [-b REBLOCK_FACTORS] [--noplot] {ts,u,eos} scalar_files [scalar_files ...]

This utility provides a fit to the one-dimensional parameter scans of QMC observables. Currently, the functionality in place is to fit linear/quadratic polynomial fits to the timestep VMC/DMC studies and single parameter optimization of trial wavefunctions using DMC local
energies and quadratic, cubic and quartic fits and equation-of-state and Morse potential fits.

positional arguments:
{ts,u,eos} One dimensional parameter used to fit QMC local energies. Options are ts for timestep and u for hubbard_u parameter fitting
scalar_files Scalar files used in the fit. An explicit list of scalar files with space or a wildcard (e.g. dmc*/dmc.s001.scalar.dat) is acceptable.

optional arguments:
-h, --help show this help message and exit
-f FIT_FUNCTION, --fit FIT_FUNCTION
Fitting function, options are (for each fit type) ts:{linear, quadratic, sqrt} u:{cubic, quadratic, quartic} eos:{birch, morse, murnaghan, vinet}. (default: linear)
-t TIMESTEPS Timesteps corresponding to scalar files, excluding any prior to --series_start (default: None)
-u HUBBARDS Hubbard U values (eV) (default: None)
--exx EXX EXX ratios (default: None)
--eos EOS Structural parameter for EOS fitting (volume or distance) (default: None)
-s SERIES_START, --series_start SERIES_START
Series number for first DMC run. Use to exclude prior VMC scalar files if they have been provided (default: None)
-e EQUILS, --equils EQUILS
Equilibration lengths corresponding to scalar files, excluding any prior to --series_start. Can be a single value for all files. If not provided, equilibration periods will be estimated. (default: None)
-b REBLOCK_FACTORS, --reblock_factors REBLOCK_FACTORS
Reblocking factors corresponding to scalar files, excluding any prior to --series_start. Can be a single value for all files. If not provided, reblocking factors will be estimated. (default: None)
--noplot Do not show plots. (default: False)

The jackknife statistical technique
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -1281,6 +1310,43 @@ An example of a cubic fit is given as below:

Quadratic Hubbard-U fits to DMC data for a 24-atom supercell of monolayer FeCl\ :sub:`2`\ obtained with ``qmc-fit``. DMC local energy minima are indicated by the red data point on the bottom halves of either panel.


Performing equation-of-state and Morse potential binding curve fits
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For a systematic series of statistical data, such as QMC calculations performed at different interatomic distances, or at a series of volumes
for an equation-of-states calculation, it is advised to perform jackknife fitting to determine quantities such as equilibrium distance, volume and
bulk moduli. For interatomic distances and equation of states fits to QMC calculations, ``qmc-fit`` has the capability to perform Morse and Birch, Murnaghan
and Vinet equation-of-state fits. In this example, we determine the equilibrium volume and bulk modulus of C-diamond using a 16 atom supercell using DMC and Murnaghan
equation-of-state fit. For the 16 atom supercell, we uniformly scan over the volumes between :math:`78.16` and :math:`99.62 A^3`. Assuming that all these DMC calculation
folders are located under the same parent folder and ordered from smaller to the large volume (e.g. dmc_78.16, dmc_80.65 ...), the following script can be used to make a
Murnaghan fit to the DMC energies.

::

>qmc-fit eos -e 50 -b 6 --eos "78.16 80.65 83.20 85.80 88.46 91.16 93.92 96.74 99.62" --fit murnaghan dmc_*/dmc.s001.scalar.dat

fit function : murnaghan
fitted formula: E_inf + B/Bp*V*((V_0/V)**Bp/(Bp-1)+1)-V_0*B/(Bp-1)
minimum_x: 89.00 +/- 0.12
e_inf: -91.2659 +/- 0.0012
B: 0.1053 +/- 0.0050
Bp: 0.000189 +/- 0.000011
pressure: -0.00000 +/- 0.00015

Here, the minimum volume is reported as :math:`89.00 \pm 0.12A^3` consistent with the input volume units. Considering that this is a 16-atom cell, the per atom
quantity would be :math:`5.56 \pm 0.01 A^3` per C. Bulk modulus, B, is reported as :math:`0.1053 \pm 0.005 Ha/A^3`. In SI units, this bulk modulus value corresponds to
:math:`459 \pm 21` GPa. Different fitting functions are supported via the ``-f`` option. Currently supported options include ``Vinet`` , ``Murnaghan``, ``Birch`` and ``Morse``.
For more information and default options, please refer to ``qmc-fit -h``.

.. _fig14:
.. figure:: /figs/qmcfit_eos.png
:width: 400
:align: center

Murnaghan equation-of-state fits to DMC data for a 16-atom supercell of C-diamond obtained with ``qmc-fit``. DMC structural minimum is indicated by the red data point with an error bar smaller than its marker size.


.. _qdens:

Using the qdens tool to obtain electron densities
Expand Down
Binary file added docs/figs/qmcfit_eos.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
162 changes: 152 additions & 10 deletions nexus/bin/qmc-fit
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import os
import sys
import argparse


try:
import numpy as np
except:
Expand Down Expand Up @@ -41,13 +42,11 @@ def import_nexus_module(module_name):
return importlib.import_module(module_name)
#end def import_nexus_module


# Load Nexus modules
try:
# Attempt specialized path-based imports.
# (The executable should still work even if Nexus is not installed)
find_nexus_modules()

versions = import_nexus_module('versions')
nexus_version = versions.nexus_version
del versions
Expand All @@ -70,6 +69,23 @@ try:
equilibration_length = numerics.equilibration_length
curve_fit = numerics.curve_fit
least_squares = numerics.least_squares
num_eos_fit = numerics.eos_fit
morse_fit = numerics.morse_fit
morse = numerics.morse
morse_einf = numerics.morse_Einf
morse_re = numerics.morse_re
morse_De = numerics.morse_De
morse_a = numerics.morse_a
morse_width = numerics.morse_width
morse_depth = numerics.morse_depth
morse_Ee = numerics.morse_Ee
morse_k = numerics.morse_k
murnaghan = numerics.murnaghan
birch = numerics.birch
vinet = numerics.vinet
murnaghan_pressure = numerics.murnaghan_pressure
birch_pressure = numerics.birch_pressure
vinet_pressure = numerics.vinet_pressure
del numerics
except:
# Failing path-based imports, import installed Nexus modules.
Expand All @@ -82,11 +98,11 @@ except:
#end try


# Polynomial functions
roots_n = lambda p, x: np.roots(np.polyder(p[::-1], x))
root_vals_n = lambda p, x: np.polyval(p[::-1], roots_n(p, x))
moment_n = lambda p, x: np.polyval(np.polyder(p[::-1], x), roots_n(p, x-1))


all_fit_functions = obj(
ts = obj(
linear = obj(
Expand Down Expand Up @@ -133,9 +149,53 @@ all_fit_functions = obj(
('minimum_e',lambda p: root_vals_n(p,1)[moment_n(p,2) > 0]),
('curvature',lambda p: moment_n(p, 2)[moment_n(p,2) > 0])],
),
),
),
eos = obj(
morse = obj(
nparam = 4,
function = morse,
format = 'De ( (1-e^-a(r-re))^2 - 1 ) + E_infinity',
params = [('minimum_x', morse_re),
('a', morse_a),
('de', morse_De),
('einf', morse_einf),
('well_width',morse_width),
('well_depth',morse_depth),
('minimum_e' ,morse_Ee),
('morse_k' ,morse_k)]
),
birch = obj(
nparam = 4,
format = 'E_inf + 9*V_0*B/16*((V_0/V)**(2./3)-1)**2*( 2 + (Bp-4)*((V_0/V)**(2./3)-1) )',
function = birch,
params = [('minimum_x', lambda p: p[1]),
('e_inf', lambda p: p[0]),
('B', lambda p: p[2]),
('Bp', lambda p: p[3]),
('pressure' , lambda p, V: birch_pressure(p[1:], V))],
),
vinet = obj(
nparam = 4,
format = 'E_inf + 2*V_0*B/(Bp-1)**2*( 2 - (2+3*(Bp-1)*((V/V_0)**(1./3)-1))*exp(-1.5*(Bp-1)*((V/V_0)**(1./3)-1)) ) ',
function = vinet,
params = [('minimum_x', lambda p: p[1]),
('e_inf', lambda p: p[0]),
('B', lambda p: p[2]),
('Bp', lambda p: p[3]),
('pressure' , lambda p, V: vinet_pressure(p[1:], V))],
),
murnaghan = obj(
nparam = 4,
format = 'E_inf + B/Bp*V*((V_0/V)**Bp/(Bp-1)+1)-V_0*B/(Bp-1)',
function = murnaghan,
params = [('minimum_x', lambda p: p[1]),
('e_inf', lambda p: p[0]),
('B', lambda p: p[2]),
('Bp', lambda p: p[3]),
('pressure' , lambda p, V: murnaghan_pressure(p[1:], V))],
),
),
)

fit_functions = obj()


Expand Down Expand Up @@ -164,9 +224,9 @@ def qmcfit(q,E,fname='linear',minimizer=least_squares):
# setup initial guess parameters
if fname=='quartic':
pp = np.polyfit(q,E,4)
if fname=='cubic':
elif fname=='cubic':
pp = np.polyfit(q,E,3)
if fname=='quadratic':
elif fname=='quadratic':
pp = np.polyfit(q,E,2)
else:
pp = np.polyfit(q,E,1)
Expand Down Expand Up @@ -416,7 +476,7 @@ def hubbard_u_fit(args):
series_start = opt.series_start,
equils = opt.equils,
reblock_factors = opt.reblock_factors,
)
)
hubbard_u = True
if opt.hubbards is not None:
parse_list(opt,'hubbards',float)
Expand Down Expand Up @@ -501,6 +561,84 @@ def hubbard_u_fit(args):
#end if
#end def hubbard_u_fit

def eos_fit(args):
opt = obj(**args.__dict__)
scalar_files = sorted(opt.scalar_files)
if opt.fit_function not in fit_functions:
error('invalid fitting function: {0}\nvalid options are: {1}'.format(opt.fit_function,sorted(fit_functions.keys())))
#end if
Edata,Emean,Eerror,scalar_files = process_scalar_files(
scalar_files = scalar_files,
series_start = opt.series_start,
equils = opt.equils,
reblock_factors = opt.reblock_factors,
)

parse_list(opt,'eos',float)
parse_list(opt,'equils',int,len1=True)
parse_list(opt,'reblock_factors',int,len1=True)

# perform jackknife analysis of the fit
x = opt.eos

jcapture = obj()
auxfuncs = obj()
auxres = obj()
finfo = fit_functions[opt.fit_function]
if 'params' in finfo.keys():
for name,func in finfo.params:
auxfuncs[name]=func
#end for
if opt.fit_function == 'morse':
_, pmean, perror = morse_fit(x, np.transpose(Edata), jackknife=True, auxfuncs=auxfuncs, auxres=auxres, capture=jcapture)
else:
_, pmean, perror = eos_fit(x, np.transpose(Edata), type = opt.fit_function, jackknife=True, auxfuncs=auxfuncs, auxres=auxres, capture=jcapture)

func_info = fit_functions[opt.fit_function]
pvals = []
for n in range(len(pmean)):
pvals.append('({0} +/- {1})'.format(*stat_strings(pmean[n],perror[n])))
#end for
log('\nfit function : '+opt.fit_function)
log('fitted formula: '+func_info.format.format(*pvals))

if 'params' in func_info.keys():
for pname,pfunc in func_info.params:
pm,pe = stat_strings(*np.array(auxres[pname]))
log('{0}: {1} +/- {2} '.format(pname,pm,pe))
#end for

# plot the fit (if available)
if plots_available and not opt.noplot:
lw = 2
ms = 10
ts = x
tsmin = ts.min()
tsmax = ts.max()
tsrange = tsmax-tsmin
x_min,x_err = auxres.minimum_x
if 'minimum_e' in auxres.keys():
e_min,e_err = auxres.minimum_e
else:
e_min = func_info.function(pmean, x_min)
#end if
tsfit = np.linspace(tsmin,1.1*tsmax,400)
Efit = func_info.function(pmean,tsfit)
plt.figure()
plt.plot(tsfit,Efit,'k-',lw=lw)
plt.errorbar(ts,Emean,Eerror,fmt='b.',ms=ms)
plt.errorbar(x_min,e_min,xerr=x_err,fmt='r.',ms=ms)
plt.xlim([tsmin-0.1*tsrange,tsmax + 0.1*tsrange])
if opt.fit_function == 'morse':
plt.xlabel('Distance (A)')
else:
plt.xlabel('Volume (A^3)')
#end if
plt.ylabel('DMC Energy (Ha)')
plt.show()
#end if
#end def eos_fit

def parse_args():
"""This utility provides a fit to the one-dimensional parameter scans of QMC
observables. Currently, the functionality in place is to fit linear/quadratic polynomial
Expand All @@ -511,7 +649,7 @@ def parse_args():
parser = argparse.ArgumentParser(description=parse_args.__doc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)

parser.add_argument('fit_type', choices=['ts', 'u'], default = 'ts',
parser.add_argument('fit_type', choices=['ts', 'u', 'eos'], default = 'ts',
help='One dimensional parameter used to fit QMC local energies. Options are ts for timestep and u for hubbard_u parameter fitting'
)
parser.add_argument('-f','--fit',dest='fit_function',default='linear',
Expand All @@ -528,7 +666,9 @@ def parse_args():
parameters.add_argument('--exx',dest='exx',default=None,
help='EXX ratios'
)

parameters.add_argument('--eos',dest='eos',default=None,
help='Structural parameter for EOS fitting'
)
parser.add_argument('-s', '--series_start',dest='series_start',type=int, default=None,
help='Series number for first DMC run. Use to exclude prior VMC scalar files if they have been provided'
)
Expand Down Expand Up @@ -568,6 +708,8 @@ if __name__=='__main__':
timestep_fit(args)
elif fit_type == 'u':
hubbard_u_fit(args)
elif fit_type == 'eos':
eos_fit(args)
else:
error('unsupported fit type: {0}'.format(fit_type))
#end if
Expand Down
Loading