"""Functions and classes for CHAMP I/O."""
from glob import glob
from os import remove
from os.path import exists
from pathlib import Path, PosixPath
from typing import Optional, Type, Union
from pydantic import BaseModel, DirectoryPath, Field, FilePath, Extra
[docs]class Settings(BaseModel, extra=Extra.allow):
"""Data class containing CHAMP configuration.
This class can hold the neccessery configuration to run CHAMP.
"""
[docs] class General(BaseModel, extra=Extra.allow):
"""General module class.
"""
title: str
""":obj:`str`: title of the simulation."""
pool: DirectoryPath = Field(PosixPath('./pool/'), postfix="/")
""":obj:`path <pathlib.Path>`, optional: location of the pool directory."""
basis: str
""":obj:`str`: basis to use (located in pool)."""
pseudopot: Optional[str]
""":obj:`str`, optional: pseudopotential to use."""
mode: str = 'vmc_one_mpi1'
""":obj:`str`, optional: QMC mode."""
seed: int = 1837465927472523
"""int, optional: seed for CHAMP."""
eunit: str = "Hartrees"
""":obj:`str`, optional: energy units."""
[docs] class Ase(BaseModel, extra=Extra.allow):
"""ASE module class.
"""
iase: int = 1
iforce_analy: int = 0
node_cutoff: int = 1
enode_cutoff: float = 0.05
[docs] class Electrons(BaseModel, extra=Extra.allow):
"""Electrons module class.
"""
nup: int
""":obj:`int`: amount of electrons with upspin."""
nelec: int
""":obj:`int`: total amount of electrons."""
[docs] class Optwf(BaseModel, extra=Extra.allow):
"""Wavefunction optimization module class.
"""
ioptwf: int = 1
ioptci: int = 1
ioptjas: int = 1
ioptorb: int = 1
nextorb: int = 100
no_active: int = 1
nopt_iter: int = 1
isample_cmat: int = 0
energy_tol: float = 0.0
[docs] class BlockingVmc(BaseModel, extra=Extra.allow):
"""VMC module class.
"""
vmc_nstep: int = 20
vmc_nblk: int = 400
vmc_nblkeq: int = 1
vmc_nconf_new: int = 0
[docs] class Pseudo(BaseModel, extra=Extra.allow):
"""Pseudopotential module class.
"""
nloc: int = 4
nquad: int = 6
general: General
molecule: Path = Field(prefix="load ")
""":obj:`path <pathlib.Path>`: path to molecule geometry."""
basis_num_info: FilePath = Field(prefix="load ")
""":obj:`path <pathlib.Path>`: path to basis info."""
determinants: FilePath = Field(prefix="load ")
""":obj:`path <pathlib.Path>`: path to the determinants file."""
orbitals: FilePath = Field(prefix="load ")
""":obj:`path <pathlib.Path>`: path to the orbitals file."""
jastrow: FilePath = Field(prefix="load ")
""":obj:`path <pathlib.Path>`: path to the jastrow file."""
jastrow_der: FilePath = Field(prefix="load ")
""":obj:`path <pathlib.Path>`: path to the jastrow derivatives file."""
symmetry: Optional[FilePath] = Field(prefix="load ")
"""optional: path to the symmetry file."""
ase: Ase = Ase()
electrons: Electrons
optwf: Optwf = Optwf()
pseudo: Pseudo = Pseudo()
blocking_vmc: BlockingVmc = BlockingVmc()
[docs] def write(self, filename='vmc.inp'):
"""Write a this dataclass containing the CHAMP configuration to an input file.
Args:
filename (:obj:`str`, optional): input file to write to.
"""
input_file = open(filename, 'w', encoding="utf-8")
schema = self.schema()['properties']
for _, item in enumerate(self):
if isinstance(item[1], BaseModel):
if callable(getattr(item[1], 'schema')):
schema2 = item[1].schema()['properties']
else:
schema2 = []
input_file.write("\n%module " + item[0] + "\n")
for _, item2 in enumerate(item[1]):
if item2[0] in schema2 and 'postfix' in schema2[item2[0]]:
if item2[1] is not None:
input_file.write("\t" + item2[0] + " " + str(item2[1]) +\
schema2[item2[0]]['postfix'] + "\n")
else:
if item2[1] is not None:
input_file.write("\t" + item2[0] + " " + str(item2[1]) + "\n")
input_file.write("%endmodule\n\n")
else:
if item[0] in schema and 'prefix' in schema[item[0]]:
if item[1] is not None:
input_file.write(schema[item[0]]['prefix'] + item[0] + " " +\
str(item[1]) + '\n')
else:
if item[1] is not None:
input_file.write(item[0] + " " + str(item[1]) + '\n')
input_file.close()
[docs] @classmethod
def read(cls: Type['BaseModel'], filename: Union[str, Path]) -> 'BaseModel':
"""Read the CHAMP input file and convert it to a dictionary format
Args:
filename (:obj:`str`, optional): file of the input file to read and write to.
Returns:
:obj:`Settings`: Settings object containing CHAMP configuration.
"""
# Check if the file exists
if not exists(filename):
raise FileNotFoundError(filename + " was not found!")
path = PosixPath(filename).resolve().parent
try:
path = path.relative_to(Path.cwd()).as_posix() + "/"
except:
path = path.as_posix() + "/"
output = {}
input_file = open(filename, 'r', encoding='utf-8')
# Set some variables to keep track of modules
curmod = ""
inmod = False
for line in input_file:
# We skip empty lines
if not line.strip():
continue
# Beginning of a module
if line.startswith("%module"):
curmod = line[8:-1]
inmod = True
output[curmod] = {}
# End of a module
elif line.startswith("%endmodule"):
inmod = False
# Loading in a file
elif line.startswith("load"):
temp = line[5:-1]
temp = temp.split()
key = temp[0]
temp.pop(0)
value = ' '.join(temp)
output[key] = path + value
# All other tags
else:
temp = line[:-1].split()
key = temp[0]
temp.pop(0)
value = ' '.join(temp)
if inmod:
# If we are in a module, add the tag to that subdictionary.
if key != "pool":
output[curmod][key] = value
else:
output[curmod][key] = path + value
else:
output[key] = value
input_file.close()
return cls(**output)
[docs] def use_opt_wf(self):
"""Function to replace the orbitals, determinants and jastrow with the optimized files.
"""
opt = ["det_optimal.1.iter*", "orbitals_optimal.1.iter*", "jastrow_optimal.1.iter*"]
keys = ["determinants", "orbitals", "jastrow"]
for ind, name in enumerate(opt):
num = len(glob(name))
# If there are no optimized WF files, we do not use them
index = min(num, self.optwf.nopt_iter)
if num >= index and index > 0:
opt[ind] = opt[ind].strip('*') + str(index)
setattr(self, keys[ind], opt[ind])
[docs] def todict(self):
"""Return a dictionary of the class.
Returns:
:obj:`str`: json dictionary.
"""
return self.json(exclude_none=True)
[docs]def cleanup(*args):
"""Remove files created by CHAMP.
This function can cleanup the directory of the simulation.
Add files as arguments to include (if not in default list)
or exclude (if in default list) files.
Args:
*args: filesnames to include (if not in default list) or exclude (if in default list).
"""
# Default list of the files that will be removed
standard = ["force_analytic", "restart_vmc", "output.log", "parser.log", "orbitals_optimal.1.*",
"jastrow_optimal.1.*", "det_optimal.1.*", "geo_optimal.*", "geo_optimal_final",
"mc_configs_start", "nohup.out", "vmc_temp.inp"]
# Add or remove file depending on the input arguments
for item in args:
if item in standard:
standard.remove(item)
else:
standard.append(item)
for file in standard:
# Take care of the wildcard files
if file.find('*') != -1:
for file2 in glob(file):
remove(file2)
else:
if exists(file):
remove(file)