diff --git a/docs/_modules/custodian/ansible/actions.html b/docs/_modules/custodian/ansible/actions.html
index 5c9bd3ef..93bda826 100644
--- a/docs/_modules/custodian/ansible/actions.html
+++ b/docs/_modules/custodian/ansible/actions.html
@@ -4,15 +4,14 @@
# coding: utf-8
-from __future__ import unicode_literals, division
+from __future__ import unicode_literals, division
import logging
import subprocess
import sys
import datetime
import time
-from glob import glob
+from glob import glob
import tarfile
import os
-from abc import ABCMeta, abstractmethod
-from itertools import islice
+from abc import ABCMeta, abstractmethod
+from itertools import islice
import warnings
-from ast import literal_eval
+from ast import literal_eval
-from .utils import get_execution_host_info
+from .utils import get_execution_host_info
-from monty.tempfile import ScratchDir
-from monty.shutil import gzip_dir
-from monty.json import MSONable, MontyEncoder, MontyDecoder
-from monty.serialization import loadfn, dumpfn
+from monty.tempfile import ScratchDir
+from monty.shutil import gzip_dir
+from monty.json import MSONable, MontyEncoder, MontyDecoder
+from monty.serialization import loadfn, dumpfn
"""
This module implements the main Custodian class, which manages a list of jobs
@@ -87,29 +86,10 @@ Source code for custodian.custodian
logger = logging.getLogger(__name__)
-if "SENTRY_DSN" in os.environ:
- # Sentry.io is a service to aggregate logs remotely, this is useful
- # for Custodian to get statistics on which errors are most common.
- # If you do not have a SENTRY_DSN environment variable set, Sentry
- # will not be used.
-
- import sentry_sdk
-
- sentry_sdk.init(dsn=os.environ["SENTRY_DSN"])
-
- with sentry_sdk.configure_scope() as scope:
-
- from getpass import getuser
-
- try:
- scope.user = {"username": getuser()}
- except Exception:
- pass
-
# Sentry.io is a service to aggregate logs remotely, this is useful
# for Custodian to get statistics on which errors are most common.
# If you do not have a SENTRY_DSN environment variable set, or do
-# not have CUSTODIAN_ERROR_REPORTING_OPT_IN set to True, then
+# not have CUSTODIAN_REPORTING_OPT_IN set to True, then
# Sentry will not be enabled.
SENTRY_DSN = None
@@ -129,7 +109,7 @@ Source code for custodian.custodian
with sentry_sdk.configure_scope() as scope:
- from getpass import getuser
+ from getpass import getuser
try:
scope.user = {"username": getuser()}
@@ -182,14 +162,25 @@ Source code for custodian.custodian
30, this means that Custodian uses the monitors to check for errors
every 30 x 10 = 300 seconds, i.e., 5 minutes.
"""
+
LOG_FILE = "custodian.json"
- def __init__(self, handlers, jobs, validators=None,
- max_errors_per_job=None,
- max_errors=1, polling_time_step=10, monitor_freq=30,
- skip_over_errors=False, scratch_dir=None,
- gzipped_output=False, checkpoint=False, terminate_func=None,
- terminate_on_nonzero_returncode=True):
+ def __init__(
+ self,
+ handlers,
+ jobs,
+ validators=None,
+ max_errors_per_job=None,
+ max_errors=1,
+ polling_time_step=10,
+ monitor_freq=30,
+ skip_over_errors=False,
+ scratch_dir=None,
+ gzipped_output=False,
+ checkpoint=False,
+ terminate_func=None,
+ terminate_on_nonzero_returncode=True,
+ ):
"""
Initializes a Custodian from a list of jobs and error handler.s
@@ -293,11 +284,12 @@ Source code for custodian.custodian
Custodian._delete_checkpoints(cwd)
n = os.path.join(cwd, "custodian.chk.{}.tar.gz".format(index))
with tarfile.open(n, mode="w:gz", compresslevel=3) as f:
- f.add(cwd, arcname='.')
+ f.add(cwd, arcname=".")
logger.info("Checkpoint written to {}".format(n))
except Exception as ex:
logger.info("Checkpointing failed")
import traceback
+
logger.error(traceback.format_exc())
[docs] @classmethod
@@ -388,8 +380,9 @@
Source code for custodian.custodian
custodian_params = process_params(spec.get("custodian_params", {}))
- return cls(jobs=jobs, handlers=handlers, validators=validators,
- **custodian_params)
+
return cls(
+
jobs=jobs, handlers=handlers, validators=validators, **custodian_params
+
)
[docs] def run(self):
"""
@@ -409,26 +402,25 @@
Source code for custodian.custodian
"""
cwd = os.getcwd()
- with ScratchDir(self.scratch_dir, create_symbolic_link=True,
- copy_to_current_on_exit=True,
- copy_from_current_on_enter=True) as temp_dir:
+ with ScratchDir(
+ self.scratch_dir,
+ create_symbolic_link=True,
+ copy_to_current_on_exit=True,
+ copy_from_current_on_enter=True,
+ ) as temp_dir:
self.total_errors = 0
start = datetime.datetime.now()
- logger.info("Run started at {} in {}.".format(
- start, temp_dir))
+ logger.info("Run started at {} in {}.".format(start, temp_dir))
v = sys.version.replace("\n", " ")
logger.info("Custodian running on Python version {}".format(v))
- logger.info("Hostname: {}, Cluster: {}".format(
- *get_execution_host_info()))
+ logger.info("Hostname: {}, Cluster: {}".format(*get_execution_host_info()))
try:
# skip jobs until the restart
- for job_n, job in islice(enumerate(self.jobs, 1),
- self.restart, None):
+ for job_n, job in islice(enumerate(self.jobs, 1), self.restart, None):
self._run_job(job_n, job)
# We do a dump of the run log after each job.
- dumpfn(self.run_log, Custodian.LOG_FILE, cls=MontyEncoder,
- indent=4)
+ dumpfn(self.run_log, Custodian.LOG_FILE, cls=MontyEncoder, indent=4)
# Checkpoint after each job so that we can recover from last
# point and remove old checkpoints
if self.checkpoint:
@@ -441,13 +433,11 @@ Source code for custodian.custodian
finally:
# Log the corrections to a json file.
logger.info("Logging to {}...".format(Custodian.LOG_FILE))
- dumpfn(self.run_log, Custodian.LOG_FILE, cls=MontyEncoder,
- indent=4)
+ dumpfn(self.run_log, Custodian.LOG_FILE, cls=MontyEncoder, indent=4)
end = datetime.datetime.now()
logger.info("Run ended at {}.".format(end))
run_time = end - start
- logger.info("Run completed. Total time taken = {}."
- .format(run_time))
+ logger.info("Run completed. Total time taken = {}.".format(run_time))
if self.gzipped_output:
gzip_dir(".")
@@ -473,11 +463,18 @@ Source code for custodian.custodian
MaxCorrectionsError: if max_errors is reached
MaxCorrectionsPerHandlerError: if max_errors_per_handler is reached
"""
- self.run_log.append({"job": job.as_dict(), "corrections": [],
- "handler": None, "validator": None,
- "max_errors": False, "max_errors_per_job": False,
- "max_errors_per_handler": False,
- "nonzero_return_code": False})
+ self.run_log.append(
+ {
+ "job": job.as_dict(),
+ "corrections": [],
+ "handler": None,
+ "validator": None,
+ "max_errors": False,
+ "max_errors_per_job": False,
+ "max_errors_per_handler": False,
+ "nonzero_return_code": False,
+ }
+ )
self.errors_current_job = 0
# reset the counters of the number of times a correction has been
# applied for each handler
@@ -487,14 +484,17 @@ Source code for custodian.custodian
job.setup()
attempt = 0
- while (self.total_errors < self.max_errors and
- self.errors_current_job < self.max_errors_per_job):
+ while (
+ self.total_errors < self.max_errors
+ and self.errors_current_job < self.max_errors_per_job
+ ):
attempt += 1
logger.info(
"Starting job no. {} ({}) attempt no. {}. Total errors and "
"errors in job thus far = {}, {}.".format(
- job_n, job.name, attempt, self.total_errors,
- self.errors_current_job))
+ job_n, job.name, attempt, self.total_errors, self.errors_current_job
+ )
+ )
p = job.run()
# Check for errors using the error handlers and perform
@@ -514,27 +514,28 @@ Source code for custodian.custodian
break
terminate = self.terminate_func or p.terminate
if n % self.monitor_freq == 0:
- has_error = self._do_check(self.monitors,
- terminate)
+ has_error = self._do_check(self.monitors, terminate)
if terminate is not None and terminate != p.terminate:
time.sleep(self.polling_time_step)
else:
p.wait()
- if self.terminate_func is not None and \
- self.terminate_func != p.terminate:
+ if (
+ self.terminate_func is not None
+ and self.terminate_func != p.terminate
+ ):
self.terminate_func()
time.sleep(self.polling_time_step)
zero_return_code = p.returncode == 0
- logger.info("{}.run has completed. "
- "Checking remaining handlers".format(job.name))
+ logger.info(
+ "{}.run has completed. " "Checking remaining handlers".format(job.name)
+ )
# Check for errors again, since in some cases non-monitor
# handlers fix the problems detected by monitors
# if an error has been found, not all handlers need to run
if has_error:
- self._do_check([h for h in self.handlers
- if not h.is_monitor])
+ self._do_check([h for h in self.handlers if not h.is_monitor])
else:
has_error = self._do_check(self.handlers)
@@ -553,13 +554,14 @@ Source code for custodian.custodian
if not zero_return_code:
if self.terminate_on_nonzero_returncode:
self.run_log[-1]["nonzero_return_code"] = True
- s = "Job return code is %d. Terminating..." % \
- p.returncode
+ s = "Job return code is %d. Terminating..." % p.returncode
logger.info(s)
raise ReturnCodeError(s, True)
else:
- warnings.warn("subprocess returned a non-zero return "
- "code. Check outputs carefully...")
+ warnings.warn(
+ "subprocess returned a non-zero return "
+ "code. Check outputs carefully..."
+ )
job.postprocess()
return
@@ -606,8 +608,9 @@ Source code for custodian.custodian
try:
cwd = os.getcwd()
v = sys.version.replace("\n", " ")
- logger.info("Custodian started in singleshot mode at {} in {}."
- .format(start, cwd))
+ logger.info(
+ "Custodian started in singleshot mode at {} in {}.".format(start, cwd)
+ )
logger.info("Custodian running on Python version {}".format(v))
# load run log
@@ -620,31 +623,32 @@ Source code for custodian.custodian
job = self.jobs[job_n]
logger.info("Setting up job no. 1 ({}) ".format(job.name))
job.setup()
- self.run_log.append({"job": job.as_dict(), "corrections": [],
- 'job_n': job_n})
+ self.run_log.append(
+ {"job": job.as_dict(), "corrections": [], "job_n": job_n}
+ )
return len(self.jobs)
else:
# Continuing after running calculation
- job_n = self.run_log[-1]['job_n']
+ job_n = self.run_log[-1]["job_n"]
job = self.jobs[job_n]
# If we had to fix errors from a previous run, insert clean log
# dict
- if len(self.run_log[-1]['corrections']) > 0:
- logger.info("Reran {}.run due to fixable errors".format(
- job.name))
+ if len(self.run_log[-1]["corrections"]) > 0:
+ logger.info("Reran {}.run due to fixable errors".format(job.name))
# check error handlers
- logger.info("Checking error handlers for {}.run".format(
- job.name))
+ logger.info("Checking error handlers for {}.run".format(job.name))
if self._do_check(self.handlers):
logger.info("Failed validation based on error handlers")
# raise an error for an unrecoverable error
for x in self.run_log[-1]["corrections"]:
if not x["actions"] and x["handler"].raises_runtime_error:
self.run_log[-1]["handler"] = x["handler"]
- s = "Unrecoverable error for handler: {}. " \
+ s = (
+ "Unrecoverable error for handler: {}. "
"Raising RuntimeError".format(x["handler"])
+ )
raise NonRecoverableError(s, True, x["handler"])
logger.info("Corrected input based on error handlers")
# Return with more jobs to run if recoverable error caught
@@ -672,8 +676,9 @@ Source code for custodian.custodian
# Setup next job_n
job_n += 1
job = self.jobs[job_n]
- self.run_log.append({"job": job.as_dict(), "corrections": [],
- 'job_n': job_n})
+ self.run_log.append(
+ {"job": job.as_dict(), "corrections": [], "job_n": job_n}
+ )
job.setup()
return len(self.jobs) - job_n
@@ -685,13 +690,11 @@ Source code for custodian.custodian
finally:
# Log the corrections to a json file.
logger.info("Logging to {}...".format(Custodian.LOG_FILE))
- dumpfn(self.run_log, Custodian.LOG_FILE, cls=MontyEncoder,
- indent=4)
+ dumpfn(self.run_log, Custodian.LOG_FILE, cls=MontyEncoder, indent=4)
end = datetime.datetime.now()
logger.info("Run ended at {}.".format(end))
run_time = end - start
- logger.info("Run completed. Total time taken = {}."
- .format(run_time))
+ logger.info("Run completed. Total time taken = {}.".format(run_time))
if self.finished and self.gzipped_output:
gzip_dir(".")
@@ -703,14 +706,20 @@ Source code for custodian.custodian
for h in handlers:
try:
if h.check():
- if h.max_num_corrections is not None \
- and h.n_applied_corrections >= h.max_num_corrections:
- msg = "Maximum number of corrections {} reached " \
- "for handler {}".format(h.max_num_corrections, h)
+ if (
+ h.max_num_corrections is not None
+ and h.n_applied_corrections >= h.max_num_corrections
+ ):
+ msg = (
+ "Maximum number of corrections {} reached "
+ "for handler {}".format(h.max_num_corrections, h)
+ )
if h.raise_on_max:
self.run_log[-1]["handler"] = h
self.run_log[-1]["max_errors_per_handler"] = True
- raise MaxCorrectionsPerHandlerError(msg, True, h.max_num_corrections, h)
+ raise MaxCorrectionsPerHandlerError(
+ msg, True, h.max_num_corrections, h
+ )
else:
logger.warning(msg + " Correction not applied.")
continue
@@ -729,17 +738,17 @@ Source code for custodian.custodian
raise
else:
import traceback
+
logger.error("Bad handler %s " % h)
logger.error(traceback.format_exc())
corrections.append(
- {"errors": ["Bad handler %s " % h],
- "actions": []})
+ {"errors": ["Bad handler %s " % h], "actions": []}
+ )
self.total_errors += len(corrections)
self.errors_current_job += len(corrections)
self.run_log[-1]["corrections"].extend(corrections)
# We do a dump of the run log after each check.
- dumpfn(self.run_log, Custodian.LOG_FILE, cls=MontyEncoder,
- indent=4)
+ dumpfn(self.run_log, Custodian.LOG_FILE, cls=MontyEncoder, indent=4)
return len(corrections) > 0
@@ -908,7 +917,7 @@ Source code for custodian.custodian
Exception class for Custodian errors.
"""
- def __init__(self, message, raises=False):
+ def __init__(self, message, raises=False):
"""
Initializes the error with a message.
@@ -926,7 +935,7 @@ Source code for custodian.custodian
Error raised when a validator does not pass the check
"""
- def __init__(self, message, raises, validator):
+ def __init__(self, message, raises, validator):
"""
Args:
message (str): Message passed to Exception
@@ -942,7 +951,7 @@ Source code for custodian.custodian
Error raised when a handler found an error but could not fix it
"""
- def __init__(self, message, raises, handler):
+ def __init__(self, message, raises, handler):
"""
Args:
message (str): Message passed to Exception
@@ -957,6 +966,7 @@ Source code for custodian.custodian
"""
Error raised when the process gave non zero return code
"""
+
pass
@@ -965,7 +975,7 @@ Source code for custodian.custodian
Error raised when the maximum allowed number of errors is reached
"""
- def __init__(self, message, raises, max_errors):
+ def __init__(self, message, raises, max_errors):
"""
Args:
message (str): Message passed to Exception
@@ -981,7 +991,7 @@ Source code for custodian.custodian
Error raised when the maximum allowed number of errors per job is reached
"""
- def __init__(self, message, raises, max_errors_per_job, job):
+ def __init__(self, message, raises, max_errors_per_job, job):
"""
Args:
message (str): Message passed to Exception
@@ -999,7 +1009,7 @@ Source code for custodian.custodian
Error raised when the maximum allowed number of errors per handler is reached
"""
- def __init__(self, message, raises, max_errors_per_handler, handler):
+ def __init__(self, message, raises, max_errors_per_handler, handler):
"""
Args:
message (str): Message passed to Exception
@@ -1032,14 +1042,14 @@ Quick search
-
+
@@ -50,12 +49,12 @@ Navigation
Source code for custodian.feff.handlers
# coding: utf-8
-from __future__ import unicode_literals, division
-from custodian.custodian import ErrorHandler
+from __future__ import unicode_literals, division
+from custodian.custodian import ErrorHandler
import re
-from custodian.utils import backup
-from pymatgen.io.feff.sets import FEFFDictSet
-from custodian.feff.interpreter import FeffModder
+from custodian.utils import backup
+from pymatgen.io.feff.sets import FEFFDictSet
+from custodian.feff.interpreter import FeffModder
import logging
""" This module implements specific error handler for FEFF runs. """
@@ -67,7 +66,15 @@ Source code for custodian.feff.handlers
__email__ = "chz022@ucsd.edu"
__date__ = "Oct 18, 2017"
-FEFF_BACKUP_FILES = ["ATOMS", "HEADER", "PARAMETERS", "POTENTIALS", "feff.inp", "*.cif", "pot.bin"]
+FEFF_BACKUP_FILES = [
+ "ATOMS",
+ "HEADER",
+ "PARAMETERS",
+ "POTENTIALS",
+ "feff.inp",
+ "*.cif",
+ "pot.bin",
+]
logger = logging.getLogger(__name__)
@@ -79,7 +86,7 @@ Source code for custodian.feff.handlers
is_monitor = False
- def __init__(self, output_filename='log1.dat'):
+ def __init__(self, output_filename="log1.dat"):
"""
Initializes the handler with the output file to check
@@ -101,7 +108,7 @@ Source code for custodian.feff.handlers
# Process the output file and get converge information
not_converge_pattern = re.compile("Convergence not reached.*")
- converge_pattern = re.compile('Convergence reached.*')
+ converge_pattern = re.compile("Convergence reached.*")
for _, line in enumerate(open(self.output_filename)):
if len(not_converge_pattern.findall(line)) > 0:
return True
@@ -120,22 +127,23 @@ Source code for custodian.feff.handlers
# Add RESTART card to PARAMETERS
if "RESTART" not in feff_input.tags:
- actions.append({"dict": "PARAMETERS",
- "action": {"_set": {"RESTART": []}}})
+ actions.append({"dict": "PARAMETERS", "action": {"_set": {"RESTART": []}}})
if nscmt < 100 and ca == 0.2:
scf_values[2] = 100
scf_values[4] = 3 # Set nmix = 3
- actions.append({"dict": "PARAMETERS",
- "action": {"_set": {"SCF": scf_values}}})
+ actions.append(
+ {"dict": "PARAMETERS", "action": {"_set": {"SCF": scf_values}}}
+ )
FeffModder().apply_actions(actions)
return {"errors": ["Non-converging job"], "actions": actions}
elif nscmt == 100 and nmix == 3 and ca > 0.01:
# Reduce the convergence accelerator factor
scf_values[3] = round(ca / 2, 2)
- actions.append({"dict": "PARAMETERS",
- "action": {"_set": {"SCF": scf_values}}})
+ actions.append(
+ {"dict": "PARAMETERS", "action": {"_set": {"SCF": scf_values}}}
+ )
FeffModder().apply_actions(actions)
return {"errors": ["Non-converging job"], "actions": actions}
@@ -143,8 +151,9 @@ Source code for custodian.feff.handlers
# Set ca = 0.05 and set nmix
scf_values[3] = 0.05
scf_values[4] = 5
- actions.append({"dict": "PARAMETERS",
- "action": {"_set": {"SCF": scf_values}}})
+ actions.append(
+ {"dict": "PARAMETERS", "action": {"_set": {"SCF": scf_values}}}
+ )
FeffModder().apply_actions(actions)
return {"errors": ["Non-converging job"], "actions": actions}
@@ -152,16 +161,18 @@ Source code for custodian.feff.handlers
# Set ca = 0.05 and set nmix
scf_values[3] = 0.05
scf_values[4] = 10
- actions.append({"dict": "PARAMETERS",
- "action": {"_set": {"SCF": scf_values}}})
+ actions.append(
+ {"dict": "PARAMETERS", "action": {"_set": {"SCF": scf_values}}}
+ )
FeffModder().apply_actions(actions)
return {"errors": ["Non-converging job"], "actions": actions}
elif nmix == 10 and ca < 0.2:
# loop through ca with nmix = 10
scf_values[3] = round(ca * 2, 2)
- actions.append({"dict": "PARAMETERS",
- "action": {"_set": {"SCF": scf_values}}})
+ actions.append(
+ {"dict": "PARAMETERS", "action": {"_set": {"SCF": scf_values}}}
+ )
FeffModder().apply_actions(actions)
return {"errors": ["Non-converging job"], "actions": actions}
@@ -190,14 +201,14 @@ Quick search
-
+
@@ -50,16 +49,16 @@ Navigation
Source code for custodian.feff.interpreter
# coding: utf-8
-from __future__ import unicode_literals
+from __future__ import unicode_literals
-from custodian.ansible.actions import FileActions, DictActions
-from custodian.ansible.interpreter import Modder
-from pymatgen.io.feff.sets import FEFFDictSet
+from custodian.ansible.actions import FileActions, DictActions
+from custodian.ansible.interpreter import Modder
+from pymatgen.io.feff.sets import FEFFDictSet
import os
[docs]class FeffModder(Modder):
-
def __init__(self, actions=None, strict=True, feffinp=None):
+
def __init__(self, actions=None, strict=True, feffinp=None):
"""
Initializes a Modder for FeffInput sets
@@ -76,7 +75,7 @@
Source code for custodian.feff.interpreter
Initialized automatically if not passed (but passing it will
avoid having to reparse the directory).
"""
- self.feffinp = feffinp or FEFFDictSet.from_directory('.')
+ self.feffinp = feffinp or FEFFDictSet.from_directory(".")
self.feffinp = self.feffinp.all_input()
actions = actions or [FileActions, DictActions]
super(FeffModder, self).__init__(actions, strict)
@@ -103,14 +102,16 @@ Source code for custodian.feff.interpreter
raise ValueError("Unrecognized format: {}".format(a))
if modified:
feff = self.feffinp
- feff_input = "\n\n".join(str(feff[k]) for k in
- ["HEADER", "PARAMETERS", "POTENTIALS", "ATOMS"]
- if k in feff)
+ feff_input = "\n\n".join(
+ str(feff[k])
+ for k in ["HEADER", "PARAMETERS", "POTENTIALS", "ATOMS"]
+ if k in feff
+ )
for k, v in feff.items():
- with open(os.path.join('.', k), "w") as f:
+ with open(os.path.join(".", k), "w") as f:
f.write(str(v))
- with open(os.path.join('.', "feff.inp"), "w") as f:
+ with open(os.path.join(".", "feff.inp"), "w") as f:
f.write(feff_input)
@@ -134,14 +135,14 @@ Quick search
-
+
@@ -54,10 +53,10 @@ Source code for custodian.feff.jobs
import os
import shutil
import logging
-from monty.shutil import decompress_dir
+from monty.shutil import decompress_dir
-from custodian.custodian import Job
-from custodian.utils import backup
+from custodian.custodian import Job
+from custodian.utils import backup
""" This module implements basic kinds of jobs for FEFF runs."""
@@ -68,7 +67,7 @@ Source code for custodian.feff.jobs
__maintainer__ = "Chen Zheng"
__email__ = "chz022@ucsd.edu"
__status__ = "Alpha"
-__date__ = '10/20/17'
+__date__ = "10/20/17"
FEFF_INPUT_FILES = {"feff.inp"}
FEFF_BACKUP_FILES = {"ATOMS", "HEADER", "PARAMETERS", "POTENTIALS"}
@@ -79,9 +78,15 @@ Source code for custodian.feff.jobs
A basic FEFF job, run whatever is in the directory.
"""
- def __init__(self, feff_cmd, output_file="feff.out",
- stderr_file="std_feff_err.txt", backup=True,
- gzipped=False, gzipped_prefix='feff_out'):
+ def __init__(
+ self,
+ feff_cmd,
+ output_file="feff.out",
+ stderr_file="std_feff_err.txt",
+ backup=True,
+ gzipped=False,
+ gzipped_prefix="feff_out",
+ ):
"""
This constructor is used for a standard FEFF initialization
@@ -112,7 +117,7 @@ Source code for custodian.feff.jobs
Returns:
"""
- decompress_dir('.')
+ decompress_dir(".")
if self.backup:
for f in FEFF_INPUT_FILES:
@@ -129,8 +134,9 @@ Source code for custodian.feff.jobs
Returns:
(subprocess.Popen) Used for monitoring.
"""
- with open(self.output_file, "w") as f_std, \
- open(self.stderr_file, "w", buffering=1) as f_err:
+ with open(self.output_file, "w") as f_std, open(
+ self.stderr_file, "w", buffering=1
+ ) as f_err:
# Use line buffering for stderr
# On TSCC, need to run shell command
p = subprocess.Popen(self.feff_cmd, stdout=f_std, stderr=f_err, shell=True)
@@ -165,14 +171,14 @@ Quick search
-
+
@@ -50,7 +49,7 @@ Navigation
Source code for custodian.nwchem.handlers
# coding: utf-8
-from __future__ import unicode_literals, division
+from __future__ import unicode_literals, division
"""
This module implements error handlers for Nwchem runs. Currently tested only
@@ -64,10 +63,10 @@ Source code for custodian.nwchem.handlers
__status__ = "Beta"
__date__ = "5/20/13"
-from custodian.custodian import ErrorHandler
-from custodian.utils import backup
-from pymatgen.io.nwchem import NwOutput, NwInput
-from custodian.ansible.interpreter import Modder
+from custodian.custodian import ErrorHandler
+from custodian.utils import backup
+from pymatgen.io.nwchem import NwOutput, NwInput
+from custodian.ansible.interpreter import Modder
[docs]class NwchemErrorHandler(ErrorHandler):
@@ -76,7 +75,7 @@
Source code for custodian.nwchem.handlers
generated by pymatgen.
"""
- def __init__(self, output_filename="mol.nwout"):
+ def __init__(self, output_filename="mol.nwout"):
"""
Initializes with an output file name.
@@ -93,7 +92,7 @@ Source code for custodian.nwchem.handlers
# Checks output file for errors.
out = NwOutput(self.output_filename)
self.errors = []
- self.input_file = out.job_info['input']
+ self.input_file = out.job_info["input"]
if out.data[-1]["has_error"]:
self.errors.extend(out.data[-1]["errors"])
self.errors = list(set(self.errors))
@@ -118,9 +117,9 @@ Source code for custodian.nwchem.handlers
nwi = NwInput.from_file(self.input_file)
for e in self.errors:
if e == "autoz error":
- action = {"_set": {"geometry_options": ["units",
- "angstroms",
- "noautoz"]}}
+ action = {
+ "_set": {"geometry_options": ["units", "angstroms", "noautoz"]}
+ }
actions.append(action)
elif e == "Bad convergence":
t = nwi.tasks[self.ntasks - 1]
@@ -145,7 +144,7 @@ Source code for custodian.nwchem.handlers
nwi.write_file(self.input_file)
return {"errors": self.errors, "actions": actions}
- def __str__(self):
+ def __str__(self):
return "NwchemErrorHandler"
@@ -169,14 +168,14 @@ Quick search
-
+
@@ -50,15 +49,15 @@ Navigation
Source code for custodian.nwchem.jobs
# coding: utf-8
-from __future__ import unicode_literals, division
+from __future__ import unicode_literals, division
import subprocess
import shutil
-from monty.io import zopen
+from monty.io import zopen
-from custodian.custodian import Job
-from monty.shutil import gzip_dir
+from custodian.custodian import Job
+from monty.shutil import gzip_dir
"""
This module implements basic kinds of jobs for Nwchem runs.
@@ -77,9 +76,15 @@ Source code for custodian.nwchem.jobs
A basic Nwchem job.
"""
- def __init__(self, nwchem_cmd, input_file="mol.nw",
- output_file="mol.nwout", gzipped=False,
- backup=True, settings_override=None):
+ def __init__(
+ self,
+ nwchem_cmd,
+ input_file="mol.nw",
+ output_file="mol.nwout",
+ gzipped=False,
+ backup=True,
+ settings_override=None,
+ ):
"""
Initializes a basic NwChem job.
@@ -116,9 +121,8 @@ Source code for custodian.nwchem.jobs
"""
Performs actual nwchem run.
"""
- with zopen(self.output_file, 'w') as fout:
- return subprocess.Popen(self.nwchem_cmd + [self.input_file],
- stdout=fout)
+ with zopen(self.output_file, "w") as fout:
+ return subprocess.Popen(self.nwchem_cmd + [self.input_file], stdout=fout)
[docs] def postprocess(self):
"""
@@ -148,14 +152,14 @@
Quick search
-
+
@@ -50,15 +49,15 @@ Navigation
Source code for custodian.qchem.handlers
# coding: utf-8
-from __future__ import unicode_literals, division
+from __future__ import unicode_literals, division
# This module implements new error handlers for QChem runs.
import os
-from pymatgen.io.qchem.inputs import QCInput
-from pymatgen.io.qchem.outputs import QCOutput
-from custodian.custodian import ErrorHandler
-from custodian.utils import backup
+from pymatgen.io.qchem.inputs import QCInput
+from pymatgen.io.qchem.outputs import QCOutput
+from custodian.custodian import ErrorHandler
+from custodian.utils import backup
__author__ = "Samuel Blau, Brandon Wood, Shyam Dwaraknath"
__copyright__ = "Copyright 2018, The Materials Project"
@@ -78,11 +77,13 @@ Source code for custodian.qchem.handlers
is_monitor = False
- def __init__(self,
- input_file="mol.qin",
- output_file="mol.qout",
- scf_max_cycles=200,
- geom_max_cycles=200):
+ def __init__(
+ self,
+ input_file="mol.qin",
+ output_file="mol.qout",
+ scf_max_cycles=200,
+ geom_max_cycles=200,
+ ):
"""
Initializes the error handler from a set of input and output files.
@@ -110,7 +111,10 @@ Source code for custodian.qchem.handlers
if "out_of_opt_cycles" not in self.errors and len(self.opt_error_history) > 0:
self.opt_error_history = []
# If we're out of optimization cycles and we have unconnected fragments, no need to handle any errors
- if "out_of_opt_cycles" in self.errors and self.outdata["structure_change"] == "unconnected_fragments":
+ if (
+ "out_of_opt_cycles" in self.errors
+ and self.outdata["structure_change"] == "unconnected_fragments"
+ ):
return False
return len(self.errors) > 0
@@ -124,8 +128,7 @@ Source code for custodian.qchem.handlers
# increase to that value and rerun. If already set, check if
# scf_algorithm is unset or set to DIIS, in which case set to GDM.
# Otherwise, tell user to call SCF error handler and do nothing.
- if str(self.qcinp.rem.get("max_scf_cycles")) != str(
- self.scf_max_cycles):
+ if str(self.qcinp.rem.get("max_scf_cycles")) != str(self.scf_max_cycles):
self.qcinp.rem["max_scf_cycles"] = self.scf_max_cycles
actions.append({"max_scf_cycles": self.scf_max_cycles})
elif self.qcinp.rem.get("thresh", "10") != "14":
@@ -148,13 +151,15 @@ Source code for custodian.qchem.handlers
elif "out_of_opt_cycles" in self.errors:
# Check number of opt cycles. If less than geom_max_cycles, increase
# to that value, set last geom as new starting geom and rerun.
- if str(self.qcinp.rem.get(
- "geom_opt_max_cycles")) != str(self.geom_max_cycles):
+ if str(self.qcinp.rem.get("geom_opt_max_cycles")) != str(
+ self.geom_max_cycles
+ ):
self.qcinp.rem["geom_opt_max_cycles"] = self.geom_max_cycles
actions.append({"geom_max_cycles:": self.scf_max_cycles})
if len(self.outdata.get("energy_trajectory")) > 1:
self.qcinp.molecule = self.outdata.get(
- "molecule_from_last_geometry")
+ "molecule_from_last_geometry"
+ )
actions.append({"molecule": "molecule_from_last_geometry"})
elif self.qcinp.rem.get("thresh", "10") != "14":
self.qcinp.rem["thresh"] = "14"
@@ -173,7 +178,11 @@ Source code for custodian.qchem.handlers
if self.opt_error_history[-1] == "no_change":
# If no structural changes occured in two consecutive optimizations,
# and we still haven't converged, then just exit.
- return {"errors": self.errors, "actions": None, "opt_error_history": self.opt_error_history}
+ return {
+ "errors": self.errors,
+ "actions": None,
+ "opt_error_history": self.opt_error_history,
+ }
self.qcinp.molecule = self.outdata.get("molecule_from_last_geometry")
actions.append({"molecule": "molecule_from_last_geometry"})
@@ -181,16 +190,13 @@ Source code for custodian.qchem.handlers
# Set last geom as new starting geom and rerun. If no opt cycles,
# use diff SCF strat? Diff initial guess? Change basis?
if len(self.outdata.get("energy_trajectory")) > 1:
- self.qcinp.molecule = self.outdata.get(
- "molecule_from_last_geometry")
+ self.qcinp.molecule = self.outdata.get("molecule_from_last_geometry")
actions.append({"molecule": "molecule_from_last_geometry"})
elif self.qcinp.rem.get("thresh", "10") != "14":
self.qcinp.rem["thresh"] = "14"
actions.append({"thresh": "14"})
else:
- print(
- "Use a different initial guess? Perhaps a different basis?"
- )
+ print("Use a different initial guess? Perhaps a different basis?")
elif "premature_end_FileMan_error" in self.errors:
if self.qcinp.rem.get("thresh", "10") != "14":
@@ -200,20 +206,23 @@ Source code for custodian.qchem.handlers
self.qcinp.rem["scf_guess_always"] = True
actions.append({"scf_guess_always": True})
else:
- print("We're in a bad spot if we get a FileMan error while always generating a new SCF guess...")
+ print(
+ "We're in a bad spot if we get a FileMan error while always generating a new SCF guess..."
+ )
elif "hessian_eigenvalue_error" in self.errors:
if self.qcinp.rem.get("thresh", "10") != "14":
self.qcinp.rem["thresh"] = "14"
actions.append({"thresh": "14"})
else:
- print("Not sure how to fix hessian_eigenvalue_error if thresh is already 14!")
+ print(
+ "Not sure how to fix hessian_eigenvalue_error if thresh is already 14!"
+ )
elif "failed_to_transform_coords" in self.errors:
# Check for symmetry flag in rem. If not False, set to False and rerun.
# If already False, increase threshold?
- if not self.qcinp.rem.get("sym_ignore") or self.qcinp.rem.get(
- "symmetry"):
+ if not self.qcinp.rem.get("sym_ignore") or self.qcinp.rem.get("symmetry"):
self.qcinp.rem["sym_ignore"] = True
self.qcinp.rem["symmetry"] = False
actions.append({"sym_ignore": True})
@@ -258,11 +267,14 @@ Source code for custodian.qchem.handlers
# You should never get here. If correct is being called then errors should have at least one entry,
# in which case it should have been caught by the if/elifs above.
print("Errors:", self.errors)
- print("Must have gotten an error which is correctly parsed but not included in the handler. FIX!!!")
+ print(
+ "Must have gotten an error which is correctly parsed but not included in the handler. FIX!!!"
+ )
return {"errors": self.errors, "actions": None}
- if {"molecule": "molecule_from_last_geometry"} in actions and \
- str(self.qcinp.rem.get("geom_opt_hessian")).lower() == "read":
+ if {"molecule": "molecule_from_last_geometry"} in actions and str(
+ self.qcinp.rem.get("geom_opt_hessian")
+ ).lower() == "read":
del self.qcinp.rem["geom_opt_hessian"]
actions.append({"geom_opt_hessian": "deleted"})
os.rename(self.input_file, self.input_file + ".last")
@@ -277,11 +289,13 @@ Source code for custodian.qchem.handlers
is_monitor = False
- def __init__(self,
- input_file="mol.qin",
- output_file="mol.qout",
- rca_gdm_thresh=1.0E-3,
- scf_max_cycles=200):
+ def __init__(
+ self,
+ input_file="mol.qin",
+ output_file="mol.qout",
+ rca_gdm_thresh=1.0e-3,
+ scf_max_cycles=200,
+ ):
"""
Initializes the error handler from a set of input and output files.
@@ -333,14 +347,14 @@ Quick search
-
+
@@ -50,7 +49,7 @@ Navigation
Source code for custodian.qchem.jobs
# coding: utf-8
-from __future__ import unicode_literals, division
+from __future__ import unicode_literals, division
import math
# New QChem job module
@@ -62,12 +61,12 @@ Source code for custodian.qchem.jobs
import subprocess
import random
import numpy as np
-from pymatgen.core import Molecule
-from pymatgen.io.qchem.inputs import QCInput
-from pymatgen.io.qchem.outputs import QCOutput, check_for_structure_changes
-from pymatgen.analysis.graphs import MoleculeGraph
-from pymatgen.analysis.local_env import OpenBabelNN
-from custodian.custodian import Job
+from pymatgen.core import Molecule
+from pymatgen.io.qchem.inputs import QCInput
+from pymatgen.io.qchem.outputs import QCOutput, check_for_structure_changes
+from pymatgen.analysis.graphs import MoleculeGraph
+from pymatgen.analysis.local_env import OpenBabelNN
+from custodian.custodian import Job
__author__ = "Samuel Blau, Brandon Wood, Shyam Dwaraknath"
__copyright__ = "Copyright 2018, The Materials Project"
@@ -84,18 +83,20 @@ Source code for custodian.qchem.jobs
A basic QChem Job.
"""
- def __init__(self,
- qchem_command,
- max_cores,
- multimode="openmp",
- input_file="mol.qin",
- output_file="mol.qout",
- qclog_file="mol.qclog",
- suffix="",
- scratch_dir=os.getcwd(),
- save_scratch=False,
- save_name="saved_scratch",
- backup=True):
+ def __init__(
+ self,
+ qchem_command,
+ max_cores,
+ multimode="openmp",
+ input_file="mol.qin",
+ output_file="mol.qout",
+ qclog_file="mol.qclog",
+ suffix="",
+ scratch_dir=os.getcwd(),
+ save_scratch=False,
+ save_name="saved_scratch",
+ backup=True,
+ ):
"""
Args:
qchem_command (str): Command to run QChem.
@@ -131,7 +132,12 @@ Source code for custodian.qchem.jobs
multi = {"openmp": "-nt", "mpi": "-np"}
if self.multimode not in multi:
raise RuntimeError("ERROR: Multimode should only be set to openmp or mpi")
- command = [multi[self.multimode], str(self.max_cores), self.input_file, self.output_file]
+ command = [
+ multi[self.multimode],
+ str(self.max_cores),
+ self.input_file,
+ self.output_file,
+ ]
if self.save_scratch:
command.append(self.save_name)
command = self.qchem_command + command
@@ -144,9 +150,9 @@ Source code for custodian.qchem.jobs
if self.backup:
shutil.copy(self.input_file, "{}.orig".format(self.input_file))
os.environ["QCSCRATCH"] = self.scratch_dir
- if self.multimode == 'openmp':
- os.environ['QCTHREADS'] = str(self.max_cores)
- os.environ['OMP_NUM_THREADS'] = str(self.max_cores)
+ if self.multimode == "openmp":
+ os.environ["QCTHREADS"] = str(self.max_cores)
+ os.environ["OMP_NUM_THREADS"] = str(self.max_cores)
[docs] def postprocess(self):
if self.suffix != "":
@@ -165,22 +171,24 @@
Source code for custodian.qchem.jobs
mydir = os.path.join("/tmp", "qchem" + myrand)
os.mkdir(mydir)
os.environ["QCLOCALSCR"] = mydir
- qclog = open(self.qclog_file, 'w')
+ qclog = open(self.qclog_file, "w")
p = subprocess.Popen(self.current_command, stdout=qclog, shell=True)
return p
[docs] @classmethod
-
def opt_with_frequency_flattener(cls,
-
qchem_command,
-
multimode="openmp",
-
input_file="mol.qin",
-
output_file="mol.qout",
-
qclog_file="mol.qclog",
-
max_iterations=10,
-
max_molecule_perturb_scale=0.3,
-
check_connectivity=True,
-
linked=True,
-
**QCJob_kwargs):
+
def opt_with_frequency_flattener(
+
cls,
+
qchem_command,
+
multimode="openmp",
+
input_file="mol.qin",
+
output_file="mol.qout",
+
qclog_file="mol.qclog",
+
max_iterations=10,
+
max_molecule_perturb_scale=0.3,
+
check_connectivity=True,
+
linked=True,
+
**QCJob_kwargs
+
):
"""
Optimize a structure and calculate vibrational frequencies to check if the
structure is in a true minima. If a frequency is negative, iteratively
@@ -202,7 +210,7 @@
Source code for custodian.qchem.jobs
:class:`custodian.qchem.jobs.QCJob`.
"""
if not os.path.exists(input_file):
- raise AssertionError('Input file must be present!')
+ raise AssertionError("Input file must be present!")
if linked:
@@ -218,22 +226,30 @@ Source code for custodian.qchem.jobs
energy_history = []
for ii in range(max_iterations):
- yield (QCJob(
- qchem_command=qchem_command,
- multimode=multimode,
- input_file=input_file,
- output_file=output_file,
- qclog_file=qclog_file,
- suffix=".opt_" + str(ii),
- scratch_dir=os.getcwd(),
- save_scratch=True,
- save_name="chain_scratch",
- backup=first,
- **QCJob_kwargs))
+ yield (
+ QCJob(
+ qchem_command=qchem_command,
+ multimode=multimode,
+ input_file=input_file,
+ output_file=output_file,
+ qclog_file=qclog_file,
+ suffix=".opt_" + str(ii),
+ scratch_dir=os.getcwd(),
+ save_scratch=True,
+ save_name="chain_scratch",
+ backup=first,
+ **QCJob_kwargs
+ )
+ )
opt_outdata = QCOutput(output_file + ".opt_" + str(ii)).data
first = False
- if opt_outdata["structure_change"] == "unconnected_fragments" and not opt_outdata["completion"]:
- print("Unstable molecule broke into unconnected fragments which failed to optimize! Exiting...")
+ if (
+ opt_outdata["structure_change"] == "unconnected_fragments"
+ and not opt_outdata["completion"]
+ ):
+ print(
+ "Unstable molecule broke into unconnected fragments which failed to optimize! Exiting..."
+ )
break
else:
energy_history.append(opt_outdata.get("final_energy"))
@@ -243,49 +259,66 @@ Source code for custodian.qchem.jobs
opt=orig_input.opt,
pcm=orig_input.pcm,
solvent=orig_input.solvent,
- smx=orig_input.smx)
+ smx=orig_input.smx,
+ )
freq_QCInput.write_file(input_file)
- yield (QCJob(
- qchem_command=qchem_command,
- multimode=multimode,
- input_file=input_file,
- output_file=output_file,
- qclog_file=qclog_file,
- suffix=".freq_" + str(ii),
- scratch_dir=os.getcwd(),
- save_scratch=True,
- save_name="chain_scratch",
- backup=first,
- **QCJob_kwargs))
+ yield (
+ QCJob(
+ qchem_command=qchem_command,
+ multimode=multimode,
+ input_file=input_file,
+ output_file=output_file,
+ qclog_file=qclog_file,
+ suffix=".freq_" + str(ii),
+ scratch_dir=os.getcwd(),
+ save_scratch=True,
+ save_name="chain_scratch",
+ backup=first,
+ **QCJob_kwargs
+ )
+ )
outdata = QCOutput(output_file + ".freq_" + str(ii)).data
errors = outdata.get("errors")
if len(errors) != 0:
- raise AssertionError('No errors should be encountered while flattening frequencies!')
- if outdata.get('frequencies')[0] > 0.0:
+ raise AssertionError(
+ "No errors should be encountered while flattening frequencies!"
+ )
+ if outdata.get("frequencies")[0] > 0.0:
print("All frequencies positive!")
break
- elif abs(outdata.get('frequencies')[0]) < 15.0 and outdata.get('frequencies')[1] > 0.0:
- print("One negative frequency smaller than 15.0 - not worth further flattening!")
+ elif (
+ abs(outdata.get("frequencies")[0]) < 15.0
+ and outdata.get("frequencies")[1] > 0.0
+ ):
+ print(
+ "One negative frequency smaller than 15.0 - not worth further flattening!"
+ )
break
else:
if len(energy_history) > 1:
- if abs(energy_history[-1] - energy_history[-2]) < energy_diff_cutoff:
+ if (
+ abs(energy_history[-1] - energy_history[-2])
+ < energy_diff_cutoff
+ ):
print("Energy change below cutoff!")
break
opt_QCInput = QCInput(
- molecule=opt_outdata.get("molecule_from_optimized_geometry"),
+ molecule=opt_outdata.get(
+ "molecule_from_optimized_geometry"
+ ),
rem=opt_rem,
opt=orig_input.opt,
pcm=orig_input.pcm,
solvent=orig_input.solvent,
- smx=orig_input.smx)
+ smx=orig_input.smx,
+ )
opt_QCInput.write_file(input_file)
if os.path.exists(os.path.join(os.getcwd(), "chain_scratch")):
shutil.rmtree(os.path.join(os.getcwd(), "chain_scratch"))
else:
if not os.path.exists(input_file):
- raise AssertionError('Input file must be present!')
+ raise AssertionError("Input file must be present!")
orig_opt_input = QCInput.from_file(input_file)
orig_opt_rem = copy.deepcopy(orig_opt_input.rem)
orig_freq_rem = copy.deepcopy(orig_opt_input.rem)
@@ -294,24 +327,32 @@ Source code for custodian.qchem.jobs
history = []
for ii in range(max_iterations):
- yield (QCJob(
- qchem_command=qchem_command,
- multimode=multimode,
- input_file=input_file,
- output_file=output_file,
- qclog_file=qclog_file,
- suffix=".opt_" + str(ii),
- backup=first,
- **QCJob_kwargs))
+ yield (
+ QCJob(
+ qchem_command=qchem_command,
+ multimode=multimode,
+ input_file=input_file,
+ output_file=output_file,
+ qclog_file=qclog_file,
+ suffix=".opt_" + str(ii),
+ backup=first,
+ **QCJob_kwargs
+ )
+ )
opt_outdata = QCOutput(output_file + ".opt_" + str(ii)).data
if first:
- orig_species = copy.deepcopy(opt_outdata.get('species'))
- orig_charge = copy.deepcopy(opt_outdata.get('charge'))
- orig_multiplicity = copy.deepcopy(opt_outdata.get('multiplicity'))
- orig_energy = copy.deepcopy(opt_outdata.get('final_energy'))
+ orig_species = copy.deepcopy(opt_outdata.get("species"))
+ orig_charge = copy.deepcopy(opt_outdata.get("charge"))
+ orig_multiplicity = copy.deepcopy(opt_outdata.get("multiplicity"))
+ orig_energy = copy.deepcopy(opt_outdata.get("final_energy"))
first = False
- if opt_outdata["structure_change"] == "unconnected_fragments" and not opt_outdata["completion"]:
- print("Unstable molecule broke into unconnected fragments which failed to optimize! Exiting...")
+ if (
+ opt_outdata["structure_change"] == "unconnected_fragments"
+ and not opt_outdata["completion"]
+ ):
+ print(
+ "Unstable molecule broke into unconnected fragments which failed to optimize! Exiting..."
+ )
break
else:
freq_QCInput = QCInput(
@@ -320,33 +361,49 @@ Source code for custodian.qchem.jobs
opt=orig_opt_input.opt,
pcm=orig_opt_input.pcm,
solvent=orig_opt_input.solvent,
- smx=orig_opt_input.smx)
+ smx=orig_opt_input.smx,
+ )
freq_QCInput.write_file(input_file)
- yield (QCJob(
- qchem_command=qchem_command,
- multimode=multimode,
- input_file=input_file,
- output_file=output_file,
- qclog_file=qclog_file,
- suffix=".freq_" + str(ii),
- backup=first,
- **QCJob_kwargs))
+ yield (
+ QCJob(
+ qchem_command=qchem_command,
+ multimode=multimode,
+ input_file=input_file,
+ output_file=output_file,
+ qclog_file=qclog_file,
+ suffix=".freq_" + str(ii),
+ backup=first,
+ **QCJob_kwargs
+ )
+ )
outdata = QCOutput(output_file + ".freq_" + str(ii)).data
errors = outdata.get("errors")
if len(errors) != 0:
- raise AssertionError('No errors should be encountered while flattening frequencies!')
- if outdata.get('frequencies')[0] > 0.0:
+ raise AssertionError(
+ "No errors should be encountered while flattening frequencies!"
+ )
+ if outdata.get("frequencies")[0] > 0.0:
print("All frequencies positive!")
- if opt_outdata.get('final_energy') > orig_energy:
- print("WARNING: Energy increased during frequency flattening!")
+ if opt_outdata.get("final_energy") > orig_energy:
+ print(
+ "WARNING: Energy increased during frequency flattening!"
+ )
break
else:
hist = {}
- hist["molecule"] = copy.deepcopy(outdata.get("initial_molecule"))
- hist["geometry"] = copy.deepcopy(outdata.get("initial_geometry"))
+ hist["molecule"] = copy.deepcopy(
+ outdata.get("initial_molecule")
+ )
+ hist["geometry"] = copy.deepcopy(
+ outdata.get("initial_geometry")
+ )
hist["frequencies"] = copy.deepcopy(outdata.get("frequencies"))
- hist["frequency_mode_vectors"] = copy.deepcopy(outdata.get("frequency_mode_vectors"))
- hist["num_neg_freqs"] = sum(1 for freq in outdata.get("frequencies") if freq < 0)
+ hist["frequency_mode_vectors"] = copy.deepcopy(
+ outdata.get("frequency_mode_vectors")
+ )
+ hist["num_neg_freqs"] = sum(
+ 1 for freq in outdata.get("frequencies") if freq < 0
+ )
hist["energy"] = copy.deepcopy(opt_outdata.get("final_energy"))
hist["index"] = len(history)
hist["children"] = []
@@ -370,18 +427,24 @@ Source code for custodian.qchem.jobs
history[-1]["parent"] = parent_hist["index"]
else:
raise AssertionError(
- "ERROR: your parent should always be one or two iterations behind you! Exiting...")
+ "ERROR: your parent should always be one or two iterations behind you! Exiting..."
+ )
# if the number of negative frequencies has remained constant or increased from parent to
# child,
- if history[-1]["num_neg_freqs"] >= parent_hist["num_neg_freqs"]:
+ if (
+ history[-1]["num_neg_freqs"]
+ >= parent_hist["num_neg_freqs"]
+ ):
# check to see if the parent only has one child, aka only the positive perturbation has
# been tried,
# in which case just try the negative perturbation from the same parent
if len(parent_hist["children"]) == 1:
ref_mol = parent_hist["molecule"]
geom_to_perturb = parent_hist["geometry"]
- negative_freq_vecs = parent_hist["frequency_mode_vectors"][0]
+ negative_freq_vecs = parent_hist[
+ "frequency_mode_vectors"
+ ][0]
reversed_direction = True
standard = False
parent_hist["children"].append(len(history))
@@ -391,32 +454,56 @@ Source code for custodian.qchem.jobs
# If we're dealing with just one negative frequency,
if parent_hist["num_neg_freqs"] == 1:
make_good_child_next_parent = False
- if history[parent_hist["children"][0]]["energy"] < history[-1]["energy"]:
- good_child = copy.deepcopy(history[parent_hist["children"][0]])
+ if (
+ history[parent_hist["children"][0]][
+ "energy"
+ ]
+ < history[-1]["energy"]
+ ):
+ good_child = copy.deepcopy(
+ history[parent_hist["children"][0]]
+ )
else:
good_child = copy.deepcopy(history[-1])
if good_child["num_neg_freqs"] > 1:
raise Exception(
"ERROR: Child with lower energy has more negative frequencies! "
- "Exiting...")
- elif good_child["energy"] < parent_hist["energy"]:
+ "Exiting..."
+ )
+ elif (
+ good_child["energy"] < parent_hist["energy"]
+ ):
make_good_child_next_parent = True
- elif vector_list_diff(good_child["frequency_mode_vectors"][0],
- parent_hist["frequency_mode_vectors"][0]) > 0.2:
+ elif (
+ vector_list_diff(
+ good_child["frequency_mode_vectors"][0],
+ parent_hist["frequency_mode_vectors"][
+ 0
+ ],
+ )
+ > 0.2
+ ):
make_good_child_next_parent = True
else:
- raise Exception("ERROR: Good child not good enough! Exiting...")
+ raise Exception(
+ "ERROR: Good child not good enough! Exiting..."
+ )
if make_good_child_next_parent:
good_child["index"] = len(history)
history.append(good_child)
ref_mol = history[-1]["molecule"]
geom_to_perturb = history[-1]["geometry"]
- negative_freq_vecs = history[-1]["frequency_mode_vectors"][0]
+ negative_freq_vecs = history[-1][
+ "frequency_mode_vectors"
+ ][0]
else:
raise Exception(
- "ERROR: Can't deal with multiple neg frequencies yet! Exiting...")
+ "ERROR: Can't deal with multiple neg frequencies yet! Exiting..."
+ )
else:
- raise AssertionError("ERROR: Parent cannot have more than two childen! Exiting...")
+ raise AssertionError(
+ "ERROR: Parent cannot have more than two childen! Exiting..."
+ )
# Implicitly, if the number of negative frequencies decreased from parent to child,
# continue normally.
if standard:
@@ -425,26 +512,32 @@ Source code for custodian.qchem.jobs
min_molecule_perturb_scale = 0.1
scale_grid = 10
perturb_scale_grid = (
- max_molecule_perturb_scale - min_molecule_perturb_scale
- ) / scale_grid
+ max_molecule_perturb_scale - min_molecule_perturb_scale
+ ) / scale_grid
structure_successfully_perturbed = False
for molecule_perturb_scale in np.arange(
- max_molecule_perturb_scale, min_molecule_perturb_scale,
- -perturb_scale_grid):
+ max_molecule_perturb_scale,
+ min_molecule_perturb_scale,
+ -perturb_scale_grid,
+ ):
new_coords = perturb_coordinates(
old_coords=geom_to_perturb,
negative_freq_vecs=negative_freq_vecs,
molecule_perturb_scale=molecule_perturb_scale,
- reversed_direction=reversed_direction)
+ reversed_direction=reversed_direction,
+ )
new_molecule = Molecule(
species=orig_species,
coords=new_coords,
charge=orig_charge,
- spin_multiplicity=orig_multiplicity)
+ spin_multiplicity=orig_multiplicity,
+ )
if check_connectivity:
- structure_successfully_perturbed = check_for_structure_changes(
- ref_mol, new_molecule) == "no_change"
+ structure_successfully_perturbed = (
+ check_for_structure_changes(ref_mol, new_molecule)
+ == "no_change"
+ )
if structure_successfully_perturbed:
break
if not structure_successfully_perturbed:
@@ -459,21 +552,24 @@ Source code for custodian.qchem.jobs
opt=orig_opt_input.opt,
pcm=orig_opt_input.pcm,
solvent=orig_opt_input.solvent,
- smx=orig_opt_input.smx)
+ smx=orig_opt_input.smx,
+ )
new_opt_QCInput.write_file(input_file)
-[docs]def perturb_coordinates(old_coords, negative_freq_vecs, molecule_perturb_scale,
-
reversed_direction):
-
max_dis = max(
-
[math.sqrt(sum([x ** 2 for x in vec])) for vec in negative_freq_vecs])
+
[docs]def perturb_coordinates(
+
old_coords, negative_freq_vecs, molecule_perturb_scale, reversed_direction
+
):
+
max_dis = max([math.sqrt(sum([x ** 2 for x in vec])) for vec in negative_freq_vecs])
scale = molecule_perturb_scale / max_dis
normalized_vecs = [[x * scale for x in vec] for vec in negative_freq_vecs]
direction = 1.0
if reversed_direction:
direction = -1.0
-
return [[c + v * direction for c, v in zip(coord, vec)]
-
for coord, vec in zip(old_coords, normalized_vecs)]
+
return [
+
[c + v * direction for c, v in zip(coord, vec)]
+
for coord, vec in zip(old_coords, normalized_vecs)
+
]
[docs]def vector_list_diff(vecs1, vecs2):
@@ -505,14 +601,14 @@
Quick search
-
+
@@ -50,7 +49,7 @@ Navigation
Source code for custodian.utils
# coding: utf-8
-from __future__ import unicode_literals, division
+from __future__ import unicode_literals, division
"""
Utility function and classes.
@@ -64,7 +63,7 @@ Source code for custodian.utils
__email__ = "shyuep@gmail.com"
__date__ = "1/12/14"
-from glob import glob
+from glob import glob
import logging
import os
import tarfile
@@ -81,8 +80,7 @@ Source code for custodian.utils
prefix (str): prefix to the files. Defaults to error, which means a
series of error.1.tar.gz, error.2.tar.gz, ... will be generated.
"""
- num = max([0] + [int(f.split(".")[1])
- for f in glob("{}.*.tar.gz".format(prefix))])
+ num = max([0] + [int(f.split(".")[1]) for f in glob("{}.*.tar.gz".format(prefix))])
filename = "{}.{}.tar.gz".format(prefix, num + 1)
logging.info("Backing up run to {}.".format(filename))
with tarfile.open(filename, "w:gz") as tar:
@@ -99,15 +97,16 @@ Source code for custodian.utils
Returns:
(HOSTNAME, CLUSTER_NAME)
"""
- host = os.environ.get('HOSTNAME', None)
- cluster = os.environ.get('SGE_O_HOST', None)
+ host = os.environ.get("HOSTNAME", None)
+ cluster = os.environ.get("SGE_O_HOST", None)
if host is None:
try:
import socket
+
host = host or socket.gethostname()
except Exception:
pass
- return host or 'unknown', cluster or 'unknown'
+ return host or "unknown", cluster or "unknown"
@@ -130,14 +129,14 @@ Quick search
-
+
@@ -50,34 +49,33 @@ Navigation
Source code for custodian.vasp.handlers
# coding: utf-8
-from __future__ import unicode_literals, division
+from __future__ import unicode_literals, division
-from monty.os.path import zpath
+from monty.os.path import zpath
import os
import time
import datetime
import operator
import shutil
import logging
-from functools import reduce
-from collections import Counter
+from functools import reduce
+from collections import Counter
import re
import numpy as np
-from monty.dev import deprecated
-from monty.serialization import loadfn
+from monty.dev import deprecated
+from monty.serialization import loadfn
-from custodian.custodian import ErrorHandler
-from custodian.utils import backup
-from pymatgen.io.vasp import Poscar, VaspInput, Incar, Kpoints, Vasprun, \
- Oszicar, Outcar
-from pymatgen.transformations.standard_transformations import \
- SupercellTransformation
+from custodian.custodian import ErrorHandler
+from custodian.utils import backup
+from pymatgen.io.vasp.inputs import Poscar, VaspInput, Incar, Kpoints
+from pymatgen.io.vasp.outputs import Vasprun, Oszicar, Outcar
+from pymatgen.transformations.standard_transformations import SupercellTransformation
-from custodian.ansible.interpreter import Modder
-from custodian.ansible.actions import FileActions
-from custodian.vasp.interpreter import VaspModder
+from custodian.ansible.interpreter import Modder
+from custodian.ansible.actions import FileActions
+from custodian.vasp.interpreter import VaspModder
"""
This module implements specific error handlers for VASP runs. These handlers
@@ -85,16 +83,27 @@ Source code for custodian.vasp.handlers
by modifying the input files.
"""
-__author__ = "Shyue Ping Ong, William Davidson Richards, Anubhav Jain, " \
- "Wei Chen, Stephen Dacek"
+__author__ = (
+ "Shyue Ping Ong, William Davidson Richards, Anubhav Jain, "
+ "Wei Chen, Stephen Dacek"
+)
__version__ = "0.1"
__maintainer__ = "Shyue Ping Ong"
__email__ = "ongsp@ucsd.edu"
__status__ = "Beta"
__date__ = "2/4/13"
-VASP_BACKUP_FILES = {"INCAR", "KPOINTS", "POSCAR", "OUTCAR", "CONTCAR",
- "OSZICAR", "vasprun.xml", "vasp.out", "std_err.txt"}
+VASP_BACKUP_FILES = {
+ "INCAR",
+ "KPOINTS",
+ "POSCAR",
+ "OUTCAR",
+ "CONTCAR",
+ "OSZICAR",
+ "vasprun.xml",
+ "vasp.out",
+ "std_err.txt",
+}
[docs]class VaspErrorHandler(ErrorHandler):
@@ -106,20 +115,21 @@
Source code for custodian.vasp.handlers
is_monitor = True
error_msgs = {
- "tet": ["Tetrahedron method fails for NKPT<4",
- "Fatal error detecting k-mesh",
- "Fatal error: unable to match k-point",
- "Routine TETIRR needs special values",
- "Tetrahedron method fails (number of k-points < 4)"],
- "inv_rot_mat": ["inverse of rotation matrix was not found (increase "
- "SYMPREC)"],
+ "tet": [
+ "Tetrahedron method fails for NKPT<4",
+ "Fatal error detecting k-mesh",
+ "Fatal error: unable to match k-point",
+ "Routine TETIRR needs special values",
+ "Tetrahedron method fails (number of k-points < 4)",
+ ],
+ "inv_rot_mat": [
+ "inverse of rotation matrix was not found (increase " "SYMPREC)"
+ ],
"brmix": ["BRMIX: very serious problems"],
- "subspacematrix": ["WARNING: Sub-Space-Matrix is not hermitian in "
- "DAV"],
+ "subspacematrix": ["WARNING: Sub-Space-Matrix is not hermitian in " "DAV"],
"tetirr": ["Routine TETIRR needs special values"],
"incorrect_shift": ["Could not get correct shifts"],
- "real_optlay": ["REAL_OPTLAY: internal error",
- "REAL_OPT: internal ERROR"],
+ "real_optlay": ["REAL_OPTLAY: internal error", "REAL_OPT: internal ERROR"],
"rspher": ["ERROR RSPHER"],
"dentet": ["DENTET"],
"too_few_bands": ["TOO FEW BANDS"],
@@ -129,24 +139,26 @@ Source code for custodian.vasp.handlers
"pricel": ["internal error in subroutine PRICEL"],
"zpotrf": ["LAPACK: Routine ZPOTRF failed"],
"amin": ["One of the lattice vectors is very long (>50 A), but AMIN"],
- "zbrent": ["ZBRENT: fatal internal in",
- "ZBRENT: fatal error in bracketing"],
+ "zbrent": ["ZBRENT: fatal internal in", "ZBRENT: fatal error in bracketing"],
"pssyevx": ["ERROR in subspace rotation PSSYEVX"],
"eddrmm": ["WARNING in EDDRMM: call to ZHEGV failed"],
"edddav": ["Error EDDDAV: Call to ZHEGV failed"],
- "grad_not_orth": [
- "EDWAV: internal error, the gradient is not orthogonal"],
+ "grad_not_orth": ["EDWAV: internal error, the gradient is not orthogonal"],
"nicht_konv": ["ERROR: SBESSELITER : nicht konvergent"],
"zheev": ["ERROR EDDIAG: Call to routine ZHEEV failed!"],
"elf_kpar": ["ELF: KPAR>1 not implemented"],
"elf_ncl": ["WARNING: ELF not implemented for non collinear case"],
"rhosyg": ["RHOSYG internal error"],
"posmap": ["POSMAP internal error: symmetry equivalent atom not found"],
- "point_group": ["Error: point group operation missing"]
+ "point_group": ["Error: point group operation missing"],
}
- def __init__(self, output_filename="vasp.out", natoms_large_cell=100,
- errors_subset_to_catch=None):
+ def __init__(
+ self,
+ output_filename="vasp.out",
+ natoms_large_cell=100,
+ errors_subset_to_catch=None,
+ ):
"""
Initializes the handler with the output file to check.
@@ -178,12 +190,15 @@ Source code for custodian.vasp.handlers
self.error_count = Counter()
# threshold of number of atoms to treat the cell as large.
self.natoms_large_cell = natoms_large_cell
- self.errors_subset_to_catch = errors_subset_to_catch or list(VaspErrorHandler.error_msgs.keys())
+ self.errors_subset_to_catch = errors_subset_to_catch or list(
+ VaspErrorHandler.error_msgs.keys()
+ )
self.logger = logging.getLogger(self.__class__.__name__)
[docs] def check(self):
incar = Incar.from_file("INCAR")
self.errors = set()
+
error_msgs = set()
with open(self.output_filename, "r") as f:
for line in f:
l = line.strip()
@@ -195,10 +210,12 @@
Source code for custodian.vasp.handlers
# computation (e.g., defects) if yes we don't
# want to kill it because there is a change in
# e-density (brmix error)
- if err == "brmix" and 'NELECT' in incar:
+ if err == "brmix" and "NELECT" in incar:
continue
self.errors.add(err)
- self.logger.error(msg, extra={"incar": incar.as_dict()})
+ error_msgs.add(msg)
+ for msg in error_msgs:
+ self.logger.error(msg, extra={"incar": incar.as_dict()})
return len(self.errors) > 0
[docs] def correct(self):
@@ -207,80 +224,112 @@
Source code for custodian.vasp.handlers
vi = VaspInput.from_directory(".")
if self.errors.intersection(["tet", "dentet"]):
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ISMEAR": 0, "SIGMA": 0.05}}})
+ if vi["INCAR"].get("KSPACING"):
+ # decrease KSPACING by 20% in each direction (approximately double no. of kpoints)
+ actions.append(
+ {
+ "dict": "INCAR",
+ "action": {
+ "_set": {"KSPACING": vi["INCAR"].get("KSPACING") * 0.8}
+ },
+ }
+ )
+ else:
+ actions.append(
+ {"dict": "INCAR", "action": {"_set": {"ISMEAR": 0, "SIGMA": 0.05}}}
+ )
if "inv_rot_mat" in self.errors:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"SYMPREC": 1e-8}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"SYMPREC": 1e-8}}})
if "brmix" in self.errors:
# If there is not a valid OUTCAR already, increment
# error count to 1 to skip first fix
- if self.error_count['brmix'] == 0:
+ if self.error_count["brmix"] == 0:
try:
- assert (Outcar(zpath(os.path.join(
- os.getcwd(), "OUTCAR"))).is_stopped is False)
+ assert (
+ Outcar(zpath(os.path.join(os.getcwd(), "OUTCAR"))).is_stopped
+ is False
+ )
except Exception:
- self.error_count['brmix'] += 1
+ self.error_count["brmix"] += 1
- if self.error_count['brmix'] == 0:
+ if self.error_count["brmix"] == 0:
# Valid OUTCAR - simply rerun the job and increment
# error count for next time
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ISTART": 1}}})
- self.error_count['brmix'] += 1
+ actions.append({"dict": "INCAR", "action": {"_set": {"ISTART": 1}}})
+ self.error_count["brmix"] += 1
- elif self.error_count['brmix'] == 1:
+ elif self.error_count["brmix"] == 1:
# Use Kerker mixing w/default values for other parameters
- actions.append({"dict": "INCAR",
- "action": {"_set": {"IMIX": 1}}})
- self.error_count['brmix'] += 1
-
- elif self.error_count['brmix'] == 2 and vi["KPOINTS"].style \
- == Kpoints.supported_modes.Gamma:
- actions.append({"dict": "KPOINTS",
- "action": {"_set": {"generation_style": "Monkhorst"}}})
- actions.append({"dict": "INCAR",
- "action": {"_unset": {"IMIX": 1}}})
- self.error_count['brmix'] += 1
-
- elif self.error_count['brmix'] in [2, 3] and vi["KPOINTS"].style == Kpoints.supported_modes.Monkhorst:
- actions.append({"dict": "KPOINTS",
- "action": {"_set": {"generation_style": "Gamma"}}})
- actions.append({"dict": "INCAR",
- "action": {"_unset": {"IMIX": 1}}})
- self.error_count['brmix'] += 1
+ actions.append({"dict": "INCAR", "action": {"_set": {"IMIX": 1}}})
+ self.error_count["brmix"] += 1
+
+ elif (
+ self.error_count["brmix"] == 2
+ and vi["KPOINTS"].style == Kpoints.supported_modes.Gamma
+ ):
+ actions.append(
+ {
+ "dict": "KPOINTS",
+ "action": {"_set": {"generation_style": "Monkhorst"}},
+ }
+ )
+ actions.append({"dict": "INCAR", "action": {"_unset": {"IMIX": 1}}})
+ self.error_count["brmix"] += 1
+
+ elif (
+ self.error_count["brmix"] in [2, 3]
+ and vi["KPOINTS"].style == Kpoints.supported_modes.Monkhorst
+ ):
+ actions.append(
+ {
+ "dict": "KPOINTS",
+ "action": {"_set": {"generation_style": "Gamma"}},
+ }
+ )
+ actions.append({"dict": "INCAR", "action": {"_unset": {"IMIX": 1}}})
+ self.error_count["brmix"] += 1
if vi["KPOINTS"].num_kpts < 1:
- all_kpts_even = all([
- bool(n % 2 == 0) for n in vi["KPOINTS"].kpts[0]
- ])
+ all_kpts_even = all(
+ [bool(n % 2 == 0) for n in vi["KPOINTS"].kpts[0]]
+ )
if all_kpts_even:
- new_kpts = (
- tuple(n + 1 for n in vi["KPOINTS"].kpts[0]),)
- actions.append({"dict": "KPOINTS", "action": {"_set": {
- "kpoints": new_kpts
- }}})
+ new_kpts = (tuple(n + 1 for n in vi["KPOINTS"].kpts[0]),)
+ actions.append(
+ {
+ "dict": "KPOINTS",
+ "action": {"_set": {"kpoints": new_kpts}},
+ }
+ )
else:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ISYM": 0}}})
-
- if vi["KPOINTS"].style == Kpoints.supported_modes.Monkhorst:
- actions.append({"dict": "KPOINTS",
- "action": {
- "_set": {"generation_style": "Gamma"}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"ISYM": 0}}})
+ if vi["KPOINTS"] is not None:
+ if vi["KPOINTS"].style == Kpoints.supported_modes.Monkhorst:
+ actions.append(
+ {
+ "dict": "KPOINTS",
+ "action": {"_set": {"generation_style": "Gamma"}},
+ }
+ )
# Based on VASP forum's recommendation, you should delete the
# CHGCAR and WAVECAR when dealing with this error.
if vi["INCAR"].get("ICHARG", 0) < 10:
- actions.append({"file": "CHGCAR",
- "action": {
- "_file_delete": {'mode': "actual"}}})
- actions.append({"file": "WAVECAR",
- "action": {
- "_file_delete": {'mode': "actual"}}})
+ actions.append(
+ {
+ "file": "CHGCAR",
+ "action": {"_file_delete": {"mode": "actual"}},
+ }
+ )
+ actions.append(
+ {
+ "file": "WAVECAR",
+ "action": {"_file_delete": {"mode": "actual"}},
+ }
+ )
if "zpotrf" in self.errors:
# Usually caused by short bond distances. If on the first step,
@@ -296,94 +345,108 @@ Source code for custodian.vasp.handlers
if nsteps >= 1:
potim = float(vi["INCAR"].get("POTIM", 0.5)) / 2.0
actions.append(
- {"dict": "INCAR",
- "action": {"_set": {"ISYM": 0, "POTIM": potim}}})
- elif vi["INCAR"].get("NSW", 0) == 0 \
- or vi["INCAR"].get("ISIF", 0) in range(3):
- actions.append(
- {"dict": "INCAR", "action": {"_set": {"ISYM": 0}}})
+ {"dict": "INCAR", "action": {"_set": {"ISYM": 0, "POTIM": potim}}}
+ )
+ elif vi["INCAR"].get("NSW", 0) == 0 or vi["INCAR"].get("ISIF", 0) in range(
+ 3
+ ):
+ actions.append({"dict": "INCAR", "action": {"_set": {"ISYM": 0}}})
else:
s = vi["POSCAR"].structure
s.apply_strain(0.2)
- actions.append({"dict": "POSCAR",
- "action": {"_set": {"structure": s.as_dict()}}})
+ actions.append(
+ {"dict": "POSCAR", "action": {"_set": {"structure": s.as_dict()}}}
+ )
# Based on VASP forum's recommendation, you should delete the
# CHGCAR and WAVECAR when dealing with this error.
if vi["INCAR"].get("ICHARG", 0) < 10:
- actions.append({"file": "CHGCAR",
- "action": {"_file_delete": {'mode': "actual"}}})
- actions.append({"file": "WAVECAR",
- "action": {"_file_delete": {'mode': "actual"}}})
+ actions.append(
+ {"file": "CHGCAR", "action": {"_file_delete": {"mode": "actual"}}}
+ )
+ actions.append(
+ {"file": "WAVECAR", "action": {"_file_delete": {"mode": "actual"}}}
+ )
if self.errors.intersection(["subspacematrix"]):
if self.error_count["subspacematrix"] == 0:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"LREAL": False}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"LREAL": False}}})
else:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"PREC": "Accurate"}}})
+ actions.append(
+ {"dict": "INCAR", "action": {"_set": {"PREC": "Accurate"}}}
+ )
self.error_count["subspacematrix"] += 1
if self.errors.intersection(["rspher", "real_optlay", "nicht_konv"]):
s = vi["POSCAR"].structure
if len(s) < self.natoms_large_cell:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"LREAL": False}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"LREAL": False}}})
else:
# for large supercell, try an in-between option LREAL = True
# prior to LREAL = False
- if self.error_count['real_optlay'] == 0:
+ if self.error_count["real_optlay"] == 0:
# use real space projectors generated by pot
- actions.append({"dict": "INCAR",
- "action": {"_set": {"LREAL": True}}})
- elif self.error_count['real_optlay'] == 1:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"LREAL": False}}})
- self.error_count['real_optlay'] += 1
+ actions.append(
+ {"dict": "INCAR", "action": {"_set": {"LREAL": True}}}
+ )
+ elif self.error_count["real_optlay"] == 1:
+ actions.append(
+ {"dict": "INCAR", "action": {"_set": {"LREAL": False}}}
+ )
+ self.error_count["real_optlay"] += 1
if self.errors.intersection(["tetirr", "incorrect_shift"]):
- if vi["KPOINTS"].style == Kpoints.supported_modes.Monkhorst:
- actions.append({"dict": "KPOINTS",
- "action": {
- "_set": {"generation_style": "Gamma"}}})
+ if vi["KPOINTS"] is not None:
+ if vi["KPOINTS"].style == Kpoints.supported_modes.Monkhorst:
+ actions.append(
+ {
+ "dict": "KPOINTS",
+ "action": {"_set": {"generation_style": "Gamma"}},
+ }
+ )
if "rot_matrix" in self.errors:
- if vi["KPOINTS"].style == Kpoints.supported_modes.Monkhorst:
- actions.append({"dict": "KPOINTS",
- "action": {
- "_set": {"generation_style": "Gamma"}}})
+ if vi["KPOINTS"] is not None:
+ if vi["KPOINTS"].style == Kpoints.supported_modes.Monkhorst:
+ actions.append(
+ {
+ "dict": "KPOINTS",
+ "action": {"_set": {"generation_style": "Gamma"}},
+ }
+ )
else:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ISYM": 0}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"ISYM": 0}}})
if "amin" in self.errors:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"AMIN": "0.01"}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"AMIN": "0.01"}}})
if "triple_product" in self.errors:
s = vi["POSCAR"].structure
trans = SupercellTransformation(((1, 0, 0), (0, 0, 1), (0, 1, 0)))
new_s = trans.apply_transformation(s)
- actions.append({"dict": "POSCAR",
- "action": {"_set": {"structure": new_s.as_dict()}},
- "transformation": trans.as_dict()})
+ actions.append(
+ {
+ "dict": "POSCAR",
+ "action": {"_set": {"structure": new_s.as_dict()}},
+ "transformation": trans.as_dict(),
+ }
+ )
if "pricel" in self.errors:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"SYMPREC": 1e-8, "ISYM": 0}}})
+ actions.append(
+ {"dict": "INCAR", "action": {"_set": {"SYMPREC": 1e-8, "ISYM": 0}}}
+ )
if "brions" in self.errors:
potim = float(vi["INCAR"].get("POTIM", 0.5)) + 0.1
- actions.append({"dict": "INCAR",
- "action": {"_set": {"POTIM": potim}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"POTIM": potim}}})
if "zbrent" in self.errors:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"IBRION": 1}}})
- actions.append({"file": "CONTCAR",
- "action": {"_file_copy": {"dest": "POSCAR"}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"IBRION": 1}}})
+ actions.append(
+ {"file": "CONTCAR", "action": {"_file_copy": {"dest": "POSCAR"}}}
+ )
if "too_few_bands" in self.errors:
if "NBANDS" in vi["INCAR"]:
@@ -398,58 +461,58 @@ Source code for custodian.vasp.handlers
break
except (IndexError, ValueError):
pass
- actions.append({"dict": "INCAR",
- "action": {"_set": {"NBANDS": int(1.1 * nbands)}}})
+ actions.append(
+ {"dict": "INCAR", "action": {"_set": {"NBANDS": int(1.1 * nbands)}}}
+ )
if "pssyevx" in self.errors:
actions.append({"dict": "INCAR", "action": {"_set": {"ALGO": "Normal"}}})
if "eddrmm" in self.errors:
# RMM algorithm is not stable for this calculation
if vi["INCAR"].get("ALGO", "Normal") in ["Fast", "VeryFast"]:
- actions.append({"dict": "INCAR", "action": {"_set": {"ALGO": "Normal"}}})
+ actions.append(
+ {"dict": "INCAR", "action": {"_set": {"ALGO": "Normal"}}}
+ )
else:
potim = float(vi["INCAR"].get("POTIM", 0.5)) / 2.0
- actions.append({"dict": "INCAR",
- "action": {"_set": {"POTIM": potim}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"POTIM": potim}}})
if vi["INCAR"].get("ICHARG", 0) < 10:
- actions.append({"file": "CHGCAR",
- "action": {"_file_delete": {'mode': "actual"}}})
- actions.append({"file": "WAVECAR",
- "action": {"_file_delete": {'mode': "actual"}}})
+ actions.append(
+ {"file": "CHGCAR", "action": {"_file_delete": {"mode": "actual"}}}
+ )
+ actions.append(
+ {"file": "WAVECAR", "action": {"_file_delete": {"mode": "actual"}}}
+ )
if "edddav" in self.errors:
if vi["INCAR"].get("ICHARG", 0) < 10:
- actions.append({"file": "CHGCAR",
- "action": {"_file_delete": {'mode': "actual"}}})
+ actions.append(
+ {"file": "CHGCAR", "action": {"_file_delete": {"mode": "actual"}}}
+ )
actions.append({"dict": "INCAR", "action": {"_set": {"ALGO": "All"}}})
if "grad_not_orth" in self.errors:
if vi["INCAR"].get("ISMEAR", 1) < 0:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ISMEAR": 0, "SIGMA": 0.05}}})
+ actions.append(
+ {"dict": "INCAR", "action": {"_set": {"ISMEAR": 0, "SIGMA": 0.05}}}
+ )
if "zheev" in self.errors:
if vi["INCAR"].get("ALGO", "Fast").lower() != "exact":
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ALGO": "Exact"}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"ALGO": "Exact"}}})
if "elf_kpar" in self.errors:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"KPAR": 1}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"KPAR": 1}}})
if "rhosyg" in self.errors:
if vi["INCAR"].get("SYMPREC", 1e-4) == 1e-4:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ISYM": 0}}})
- actions.append({"dict": "INCAR",
- "action": {"_set": {"SYMPREC": 1e-4}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"ISYM": 0}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"SYMPREC": 1e-4}}})
if "posmap" in self.errors:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"SYMPREC": 1e-6}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"SYMPREC": 1e-6}}})
if "point_group" in self.errors:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ISYM": 0}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"ISYM": 0}}})
VaspModder(vi=vi).apply_actions(actions)
return {"errors": list(self.errors), "actions": actions}
@@ -464,11 +527,9 @@ Source code for custodian.vasp.handlers
is_monitor = True
- error_msgs = {
- "lrf_comm": ["LRF_COMMUTATOR internal error"],
- }
+ error_msgs = {"lrf_comm": ["LRF_COMMUTATOR internal error"]}
- def __init__(self, output_filename="std_err.txt"):
+ def __init__(self, output_filename="std_err.txt"):
"""
Initializes the handler with the output file to check.
@@ -499,11 +560,11 @@ Source code for custodian.vasp.handlers
vi = VaspInput.from_directory(".")
if "lrf_comm" in self.errors:
- if Outcar(zpath(os.path.join(
- os.getcwd(), "OUTCAR"))).is_stopped is False:
+ if Outcar(zpath(os.path.join(os.getcwd(), "OUTCAR"))).is_stopped is False:
if not vi["INCAR"].get("LPEAD"):
- actions.append({"dict": "INCAR",
- "action": {"_set": {"LPEAD": True}}})
+ actions.append(
+ {"dict": "INCAR", "action": {"_set": {"LPEAD": True}}}
+ )
VaspModder(vi=vi).apply_actions(actions)
return {"errors": list(self.errors), "actions": actions}
@@ -519,12 +580,14 @@ Source code for custodian.vasp.handlers
is_monitor = True
error_msgs = {
- "kpoints_trans": ["internal error in GENERATE_KPOINTS_TRANS: "
- "number of G-vector changed in star"],
- "out_of_memory": ["Allocation would exceed memory limit"]
+ "kpoints_trans": [
+ "internal error in GENERATE_KPOINTS_TRANS: "
+ "number of G-vector changed in star"
+ ],
+ "out_of_memory": ["Allocation would exceed memory limit"],
}
- def __init__(self, output_filename="std_err.txt"):
+ def __init__(self, output_filename="std_err.txt"):
"""
Initializes the handler with the output file to check.
@@ -560,15 +623,17 @@ Source code for custodian.vasp.handlers
m = max(int(round(m ** (1 / 3))), 1)
if vi["KPOINTS"].style.name.lower().startswith("m"):
m += m % 2
- actions.append({"dict": "KPOINTS",
- "action": {"_set": {"kpoints": [[m] * 3]}}})
- self.error_count['kpoints_trans'] += 1
+ actions.append(
+ {"dict": "KPOINTS", "action": {"_set": {"kpoints": [[m] * 3]}}}
+ )
+ self.error_count["kpoints_trans"] += 1
if "out_of_memory" in self.errors:
if vi["INCAR"].get("KPAR", 1) > 1:
reduced_kpar = max(vi["INCAR"].get("KPAR", 1) // 2, 1)
- actions.append({"dict": "INCAR",
- "action": {"_set": {"KPAR": reduced_kpar}}})
+ actions.append(
+ {"dict": "INCAR", "action": {"_set": {"KPAR": reduced_kpar}}}
+ )
VaspModder(vi=vi).apply_actions(actions)
return {"errors": list(self.errors), "actions": actions}
@@ -583,13 +648,13 @@ Source code for custodian.vasp.handlers
is_monitor = True
error_msgs = {
- "aliasing": [
- "WARNING: small aliasing (wrap around) errors must be expected"],
- "aliasing_incar": ["Your FFT grids (NGX,NGY,NGZ) are not sufficient "
- "for an accurate"]
+ "aliasing": ["WARNING: small aliasing (wrap around) errors must be expected"],
+ "aliasing_incar": [
+ "Your FFT grids (NGX,NGY,NGZ) are not sufficient " "for an accurate"
+ ],
}
- def __init__(self, output_filename="vasp.out"):
+ def __init__(self, output_filename="vasp.out"):
"""
Initializes the handler with the output file to check.
@@ -615,7 +680,7 @@ Source code for custodian.vasp.handlers
# computation (e.g., defects) if yes we don't
# want to kill it because there is a change in e-
# density (brmix error)
- if err == "brmix" and 'NELECT' in incar:
+ if err == "brmix" and "NELECT" in incar:
continue
self.errors.add(err)
return len(self.errors) > 0
@@ -636,31 +701,44 @@ Source code for custodian.vasp.handlers
changes_dict[m.group(1)] = int(m.group(2))
grid_adjusted = True
# Ensure that all NGX, NGY, NGZ have been checked
- if grid_adjusted and 'NGZ' in line:
+ if grid_adjusted and "NGZ" in line:
actions.append(
- {"dict": "INCAR", "action": {"_set": changes_dict}})
+ {"dict": "INCAR", "action": {"_set": changes_dict}}
+ )
if vi["INCAR"].get("ICHARG", 0) < 10:
- actions.extend([{"file": "CHGCAR",
- "action": {"_file_delete": {
- 'mode': "actual"}}},
- {"file": "WAVECAR",
- "action": {"_file_delete": {
- 'mode': "actual"}}}])
+ actions.extend(
+ [
+ {
+ "file": "CHGCAR",
+ "action": {"_file_delete": {"mode": "actual"}},
+ },
+ {
+ "file": "WAVECAR",
+ "action": {"_file_delete": {"mode": "actual"}},
+ },
+ ]
+ )
break
if "aliasing_incar" in self.errors:
# vasp seems to give different warnings depending on whether the
# aliasing error was caused by user supplied inputs
- d = {k: 1 for k in ['NGX', 'NGY', 'NGZ'] if k in vi['INCAR'].keys()}
+ d = {k: 1 for k in ["NGX", "NGY", "NGZ"] if k in vi["INCAR"].keys()}
actions.append({"dict": "INCAR", "action": {"_unset": d}})
if vi["INCAR"].get("ICHARG", 0) < 10:
- actions.extend([{"file": "CHGCAR",
- "action": {
- "_file_delete": {'mode': "actual"}}},
- {"file": "WAVECAR",
- "action": {
- "_file_delete": {'mode': "actual"}}}])
+ actions.extend(
+ [
+ {
+ "file": "CHGCAR",
+ "action": {"_file_delete": {"mode": "actual"}},
+ },
+ {
+ "file": "WAVECAR",
+ "action": {"_file_delete": {"mode": "actual"}},
+ },
+ ]
+ )
VaspModder(vi=vi).apply_actions(actions)
return {"errors": list(self.errors), "actions": actions}
@@ -671,7 +749,7 @@ Source code for custodian.vasp.handlers
Corrects for total drift exceeding the force convergence criteria.
"""
- def __init__(self, max_drift=None, to_average=3, enaug_multiply=2):
+ def __init__(self, max_drift=None, to_average=3, enaug_multiply=2):
"""
Initializes the handler with max drift
Args:
@@ -700,11 +778,11 @@ Source code for custodian.vasp.handlers
# Can't perform check if Outcar not valid
return False
- if len(outcar.data.get('drift', [])) < self.to_average:
+ if len(outcar.data.get("drift", [])) < self.to_average:
# Ensure enough steps to get average drift
return False
else:
- curr_drift = outcar.data.get("drift", [])[::-1][:self.to_average]
+ curr_drift = outcar.data.get("drift", [])[::-1][: self.to_average]
curr_drift = np.average([np.linalg.norm(d) for d in curr_drift])
return curr_drift > self.max_drift
@@ -717,28 +795,42 @@ Source code for custodian.vasp.handlers
outcar = Outcar("OUTCAR")
# Move CONTCAR to POSCAR
- actions.append({"file": "CONTCAR",
- "action": {"_file_copy": {"dest": "POSCAR"}}})
+ actions.append(
+ {"file": "CONTCAR", "action": {"_file_copy": {"dest": "POSCAR"}}}
+ )
# First try adding ADDGRID
if not incar.get("ADDGRID", False):
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ADDGRID": True}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"ADDGRID": True}}})
# Otherwise set PREC to High so ENAUG can be used to control Augmentation Grid Size
elif incar.get("PREC", "Accurate").lower() != "high":
- actions.append({"dict": "INCAR",
- "action": {"_set": {"PREC": "High"}}})
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ENAUG": incar.get("ENCUT", 520) * 2}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"PREC": "High"}}})
+ actions.append(
+ {
+ "dict": "INCAR",
+ "action": {"_set": {"ENAUG": incar.get("ENCUT", 520) * 2}},
+ }
+ )
# PREC is already high and ENAUG set so just increase it
else:
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ENAUG": int(incar.get("ENAUG", 1040) * self.enaug_multiply)}}})
-
- curr_drift = outcar.data.get("drift", [])[::-1][:self.to_average]
+ actions.append(
+ {
+ "dict": "INCAR",
+ "action": {
+ "_set": {
+ "ENAUG": int(incar.get("ENAUG", 1040) * self.enaug_multiply)
+ }
+ },
+ }
+ )
+
+ curr_drift = outcar.data.get("drift", [])[::-1][: self.to_average]
curr_drift = np.average([np.linalg.norm(d) for d in curr_drift])
VaspModder(vi=vi).apply_actions(actions)
- return {"errors": "Excessive drift {} > {}".format(curr_drift, self.max_drift), "actions": actions}
+ return {
+ "errors": "Excessive drift {} > {}".format(curr_drift, self.max_drift),
+ "actions": actions,
+ }
[docs]class MeshSymmetryErrorHandler(ErrorHandler):
@@ -747,10 +839,10 @@
Source code for custodian.vasp.handlers
non-fatal. So this error handler only checks at the end of the run,
and if the run has converged, no error is recorded.
"""
+
is_monitor = False
- def __init__(self, output_filename="vasp.out",
- output_vasprun="vasprun.xml"):
+ def __init__(self, output_filename="vasp.out", output_vasprun="vasprun.xml"):
"""
Initializes the handler with the output files to check.
@@ -766,16 +858,21 @@ Source code for custodian.vasp.handlers
self.output_vasprun = output_vasprun
[docs] def check(self):
-
msg = "Reciprocal lattice and k-lattice belong to different class of" \
-
" lattices."
+
msg = (
+
"Reciprocal lattice and k-lattice belong to different class of" " lattices."
+
)
+
+
vi = VaspInput.from_directory(".")
+
# disregard this error if KSPACING is set and no KPOINTS file is generated
+
if vi["INCAR"].get("KSPACING", False):
+
return False
-
vi = VaspInput.from_directory('.')
# According to VASP admins, you can disregard this error
# if symmetry is off
# Also disregard if automatic KPOINT generation is used
-
if (not vi["INCAR"].get('ISYM', True)) or \
-
vi[
-
"KPOINTS"].style == Kpoints.supported_modes.Automatic:
+
if (not vi["INCAR"].get("ISYM", True)) or vi[
+
"KPOINTS"
+
].style == Kpoints.supported_modes.Automatic:
return False
try:
@@ -798,8 +895,7 @@
Source code for custodian.vasp.handlers
m = max(int(round(m ** (1 / 3))), 1)
if vi["KPOINTS"].style.name.lower().startswith("m"):
m += m % 2
- actions = [{"dict": "KPOINTS",
- "action": {"_set": {"kpoints": [[m] * 3]}}}]
+ actions = [{"dict": "KPOINTS", "action": {"_set": {"kpoints": [[m] * 3]}}}]
VaspModder(vi=vi).apply_actions(actions)
return {"errors": ["mesh_symmetry"], "actions": actions}
@@ -808,9 +904,10 @@ Source code for custodian.vasp.handlers
"""
Check if a run is converged.
"""
+
is_monitor = False
- def __init__(self, output_filename="vasprun.xml"):
+ def __init__(self, output_filename="vasprun.xml"):
"""
Initializes the handler with the output file to check.
@@ -838,34 +935,36 @@ Source code for custodian.vasp.handlers
# expensive algorithms
algo = v.incar.get("ALGO", "Normal")
if algo == "VeryFast":
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ALGO": "Fast"}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"ALGO": "Fast"}}})
elif algo == "Fast":
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ALGO": "Normal"}}})
+ actions.append(
+ {"dict": "INCAR", "action": {"_set": {"ALGO": "Normal"}}}
+ )
elif algo == "Normal":
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ALGO": "All"}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"ALGO": "All"}}})
else:
# Try mixing as last resort
- new_settings = {"ISTART": 1,
- "ALGO": "Normal",
- "NELMDL": -6,
- "BMIX": 0.001,
- "AMIX_MAG": 0.8,
- "BMIX_MAG": 0.001}
-
- if not all([v.incar.get(k, "") == val for k, val in new_settings.items()]):
- actions.append({"dict": "INCAR",
- "action": {"_set": new_settings}})
+ new_settings = {
+ "ISTART": 1,
+ "ALGO": "Normal",
+ "NELMDL": -6,
+ "BMIX": 0.001,
+ "AMIX_MAG": 0.8,
+ "BMIX_MAG": 0.001,
+ }
+
+ if not all(
+ [v.incar.get(k, "") == val for k, val in new_settings.items()]
+ ):
+ actions.append({"dict": "INCAR", "action": {"_set": new_settings}})
elif not v.converged_ionic:
# Just continue optimizing and let other handles fix ionic
# optimizer parameters
- actions.append({"dict": "INCAR",
- "action": {"_set": {"IBRION": 1}}})
- actions.append({"file": "CONTCAR",
- "action": {"_file_copy": {"dest": "POSCAR"}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"IBRION": 1}}})
+ actions.append(
+ {"file": "CONTCAR", "action": {"_file_copy": {"dest": "POSCAR"}}}
+ )
if actions:
vi = VaspInput.from_directory(".")
@@ -877,16 +976,20 @@ Source code for custodian.vasp.handlers
return {"errors": ["Unconverged"], "actions": None}
-[docs]class MaxForceErrorHandler(ErrorHandler):
+
@deprecated(
+
message="This handler is no longer supported and its use is no "
+
"longer recommended. It will be removed in v2020.x."
+
)
+
class MaxForceErrorHandler(ErrorHandler):
"""
Checks that the desired force convergence has been achieved. Otherwise
restarts the run with smaller EDIFF. (This is necessary since energy
and force convergence criteria cannot be set simultaneously)
"""
+
is_monitor = False
-
def __init__(self, output_filename="vasprun.xml",
-
max_force_threshold=0.25):
+
def __init__(self, output_filename="vasprun.xml", max_force_threshold=0.25):
"""
Args:
input_filename (str): name of the vasp INCAR file
@@ -898,11 +1001,11 @@
Source code for custodian.vasp.handlers
self.output_filename = output_filename
self.max_force_threshold = max_force_threshold
-[docs] def check(self):
+
def check(self):
try:
v = Vasprun(self.output_filename)
-
forces = np.array(v.ionic_steps[-1]['forces'])
-
sdyn = v.final_structure.site_properties.get('selective_dynamics')
+
forces = np.array(v.ionic_steps[-1]["forces"])
+
sdyn = v.final_structure.site_properties.get("selective_dynamics")
if sdyn:
forces[np.logical_not(sdyn)] = 0
max_force = max(np.linalg.norm(forces, axis=1))
@@ -910,20 +1013,20 @@
Source code for custodian.vasp.handlers
return True
except Exception:
pass
- return False
+
return False
-
[docs] def correct(self):
+
def correct(self):
backup(VASP_BACKUP_FILES | {self.output_filename})
vi = VaspInput.from_directory(".")
ediff = float(vi["INCAR"].get("EDIFF", 1e-4))
ediffg = float(vi["INCAR"].get("EDIFFG", ediff * 10))
-
actions = [{"file": "CONTCAR",
-
"action": {"_file_copy": {"dest": "POSCAR"}}},
-
{"dict": "INCAR",
-
"action": {"_set": {"EDIFFG": ediffg * 0.5}}}]
+
actions = [
+
{"file": "CONTCAR", "action": {"_file_copy": {"dest": "POSCAR"}}},
+
{"dict": "INCAR", "action": {"_set": {"EDIFFG": ediffg * 0.5}}},
+
]
VaspModder(vi=vi).apply_actions(actions)
-
return {"errors": ["MaxForce"], "actions": actions}
+ return {"errors": ["MaxForce"], "actions": actions}
[docs]class PotimErrorHandler(ErrorHandler):
@@ -933,10 +1036,12 @@
Source code for custodian.vasp.handlers
end up crashing with some other error (e.g. BRMIX) as the geometry
gets progressively worse.
"""
+
is_monitor = True
- def __init__(self, input_filename="POSCAR", output_filename="OSZICAR",
- dE_threshold=1):
+ def __init__(
+ self, input_filename="POSCAR", output_filename="OSZICAR", dE_threshold=1
+ ):
"""
Initializes the handler with the input and output files to check.
@@ -956,7 +1061,7 @@ Source code for custodian.vasp.handlers
try:
oszicar = Oszicar(self.output_filename)
n = len(Poscar.from_file(self.input_filename).structure)
- max_dE = max([s['dE'] for s in oszicar.ionic_steps[1:]]) / n
+ max_dE = max([s["dE"] for s in oszicar.ionic_steps[1:]]) / n
if max_dE > self.dE_threshold:
return True
except Exception:
@@ -968,15 +1073,13 @@ Source code for custodian.vasp.handlers
potim = float(vi["INCAR"].get("POTIM", 0.5))
ibrion = int(vi["INCAR"].get("IBRION", 0))
if potim < 0.2 and ibrion != 3:
- actions = [{"dict": "INCAR",
- "action": {"_set": {"IBRION": 3,
- "SMASS": 0.75}}}]
+ actions = [
+ {"dict": "INCAR", "action": {"_set": {"IBRION": 3, "SMASS": 0.75}}}
+ ]
elif potim < 0.1:
- actions = [{"dict": "INCAR",
- "action": {"_set": {"SYMPREC": 1e-8}}}]
+ actions = [{"dict": "INCAR", "action": {"_set": {"SYMPREC": 1e-8}}}]
else:
- actions = [{"dict": "INCAR",
- "action": {"_set": {"POTIM": potim * 0.5}}}]
+ actions = [{"dict": "INCAR", "action": {"_set": {"POTIM": potim * 0.5}}}]
VaspModder(vi=vi).apply_actions(actions)
return {"errors": ["POTIM"], "actions": actions}
@@ -990,7 +1093,7 @@ Source code for custodian.vasp.handlers
is_monitor = True
- def __init__(self, output_filename="vasp.out", timeout=21600):
+ def __init__(self, output_filename="vasp.out", timeout=21600):
"""
Initializes the handler with the output file to check.
@@ -1014,14 +1117,12 @@ Source code for custodian.vasp.handlers
[docs] def correct(self):
backup(VASP_BACKUP_FILES | {self.output_filename})
-
vi = VaspInput.from_directory('.')
+
vi = VaspInput.from_directory(".")
actions = []
if vi["INCAR"].get("ALGO", "Normal") == "Fast":
-
actions.append({"dict": "INCAR",
-
"action": {"_set": {"ALGO": "Normal"}}})
+
actions.append({"dict": "INCAR", "action": {"_set": {"ALGO": "Normal"}}})
else:
-
actions.append({"dict": "INCAR",
-
"action": {"_set": {"SYMPREC": 1e-8}}})
+
actions.append({"dict": "INCAR", "action": {"_set": {"SYMPREC": 1e-8}}})
VaspModder(vi=vi).apply_actions(actions)
@@ -1034,9 +1135,10 @@
Source code for custodian.vasp.handlers
last nionic_steps ionic steps (default=10). If so, change ALGO from Fast to
Normal or kill the job.
"""
+
is_monitor = True
- def __init__(self, output_filename="OSZICAR", nionic_steps=10):
+ def __init__(self, output_filename="OSZICAR", nionic_steps=10):
"""
Initializes the handler with the output file to check.
@@ -1057,8 +1159,9 @@ Source code for custodian.vasp.handlers
oszicar = Oszicar(self.output_filename)
esteps = oszicar.electronic_steps
if len(esteps) > self.nionic_steps:
- return all([len(e) == nelm
- for e in esteps[-(self.nionic_steps + 1):-1]])
+ return all(
+ [len(e) == nelm for e in esteps[-(self.nionic_steps + 1): -1]]
+ )
except Exception:
pass
return False
@@ -1074,24 +1177,27 @@ Source code for custodian.vasp.handlers
# These progressively switches to more stable but more
# expensive algorithms
if algo == "VeryFast":
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ALGO": "Fast"}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"ALGO": "Fast"}}})
elif algo == "Fast":
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ALGO": "Normal"}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"ALGO": "Normal"}}})
elif algo == "Normal":
- actions.append({"dict": "INCAR",
- "action": {"_set": {"ALGO": "All"}}})
+ actions.append({"dict": "INCAR", "action": {"_set": {"ALGO": "All"}}})
elif amix > 0.1 and bmix > 0.01:
# Try linear mixing
- actions.append({"dict": "INCAR",
- "action": {"_set": {"AMIX": 0.1, "BMIX": 0.01,
- "ICHARG": 2}}})
+ actions.append(
+ {
+ "dict": "INCAR",
+ "action": {"_set": {"AMIX": 0.1, "BMIX": 0.01, "ICHARG": 2}},
+ }
+ )
elif bmix < 3.0 and amin > 0.01:
# Try increasing bmix
- actions.append({"dict": "INCAR",
- "action": {"_set": {"AMIN": 0.01, "BMIX": 3.0,
- "ICHARG": 2}}})
+ actions.append(
+ {
+ "dict": "INCAR",
+ "action": {"_set": {"AMIN": 0.01, "BMIX": 3.0, "ICHARG": 2}},
+ }
+ )
if actions:
backup(VASP_BACKUP_FILES)
@@ -1099,7 +1205,20 @@ Source code for custodian.vasp.handlers
return {"errors": ["Non-converging job"], "actions": actions}
# Unfixable error. Just return None for actions.
else:
- return {"errors": ["Non-converging job"], "actions": None}
+ return {"errors": ["Non-converging job"], "actions": None}
+
+
[docs] @classmethod
+
def from_dict(cls, d):
+
"""
+
Custom from_dict method to preserve backwards compatibility with
+
older versions of Custodian.
+
"""
+
if "change_algo" in d:
+
del d["change_algo"]
+
return cls(
+
output_filename=d.get("output_filename", "OSZICAR"),
+
nionic_steps=d.get("nionic_steps", 10),
+
)
[docs]class WalltimeHandler(ErrorHandler):
@@ -1110,6 +1229,7 @@
Source code for custodian.vasp.handlers
to be running on a PBS system and the PBS_WALLTIME variable is in the run
environment, the wall time will be automatically determined if not set.
"""
+
is_monitor = True
# The WalltimeHandler should not terminate as we want VASP to terminate
@@ -1120,8 +1240,7 @@ Source code for custodian.vasp.handlers
# error
raises_runtime_error = False
- def __init__(self, wall_time=None, buffer_time=300,
- electronic_step_stop=False):
+ def __init__(self, wall_time=None, buffer_time=300, electronic_step_stop=False):
"""
Initializes the handler with a buffer time.
@@ -1162,11 +1281,13 @@ Source code for custodian.vasp.handlers
# set manually be the user in the batch environment.
if "CUSTODIAN_WALLTIME_START" in os.environ:
self.start_time = datetime.datetime.strptime(
- os.environ["CUSTODIAN_WALLTIME_START"], "%a %b %d %H:%M:%S %Z %Y")
+ os.environ["CUSTODIAN_WALLTIME_START"], "%a %b %d %H:%M:%S %Z %Y"
+ )
else:
self.start_time = datetime.datetime.now()
os.environ["CUSTODIAN_WALLTIME_START"] = datetime.datetime.strftime(
- self.start_time, "%a %b %d %H:%M:%S UTC %Y")
+ self.start_time, "%a %b %d %H:%M:%S UTC %Y"
+ )
self.electronic_step_stop = electronic_step_stop
self.electronic_steps_timings = [0]
@@ -1179,14 +1300,24 @@ Source code for custodian.vasp.handlers
outcar = Outcar("OUTCAR")
if not self.electronic_step_stop:
# Determine max time per ionic step.
- outcar.read_pattern({"timings": "LOOP\+.+real time(.+)"},
- postprocess=float)
- time_per_step = np.max(outcar.data.get('timings')) if outcar.data.get("timings", []) else 0
+ outcar.read_pattern(
+ {"timings": "LOOP\+.+real time(.+)"}, postprocess=float
+ )
+ time_per_step = (
+ np.max(outcar.data.get("timings"))
+ if outcar.data.get("timings", [])
+ else 0
+ )
else:
# Determine max time per electronic step.
- outcar.read_pattern({"timings": "LOOP:.+real time(.+)"},
- postprocess=float)
- time_per_step = np.max(outcar.data.get('timings')) if outcar.data.get("timings", []) else 0
+ outcar.read_pattern(
+ {"timings": "LOOP:.+real time(.+)"}, postprocess=float
+ )
+ time_per_step = (
+ np.max(outcar.data.get("timings"))
+ if outcar.data.get("timings", [])
+ else 0
+ )
# If the remaining time is less than average time for 3
# steps or buffer_time.
@@ -1198,11 +1329,13 @@ Source code for custodian.vasp.handlers
[docs] def correct(self):
-
content = "LSTOP = .TRUE." if not self.electronic_step_stop else \
-
"LABORT = .TRUE."
+
content = (
+
"LSTOP = .TRUE." if not self.electronic_step_stop else "LABORT = .TRUE."
+
)
# Write STOPCAR
-
actions = [{"file": "STOPCAR",
-
"action": {"_file_create": {'content': content}}}]
+
actions = [
+
{"file": "STOPCAR", "action": {"_file_create": {"content": content}}}
+
]
m = Modder(actions=[FileActions])
for a in actions:
@@ -1212,8 +1345,7 @@
Source code for custodian.vasp.handlers
@deprecated(replacement=WalltimeHandler)
class PBSWalltimeHandler(WalltimeHandler):
-
- def __init__(self, buffer_time=300):
+ def __init__(self, buffer_time=300):
super(PBSWalltimeHandler, self).__init__(None, buffer_time=buffer_time)
@@ -1228,13 +1360,14 @@ Source code for custodian.vasp.handlers
checkpoint will be stored in subdirs chk_#. This should be used in
combiantion with the StoppedRunHandler.
"""
+
is_monitor = True
# The CheckpointHandler should not terminate as we want VASP to terminate
# itself naturally with the STOPCAR.
is_terminating = False
- def __init__(self, interval=3600):
+ def __init__(self, interval=3600):
"""
Initializes the handler with an interval.
@@ -1255,15 +1388,20 @@ Source code for custodian.vasp.handlers
[docs] def correct(self):
content = "LSTOP = .TRUE."
-
chkpt_content = "Index: %d\nTime: \"%s\"" % (self.chk_counter,
-
datetime.datetime.now())
+
chkpt_content = 'Index: %d\nTime: "%s"' % (
+
self.chk_counter,
+
datetime.datetime.now(),
+
)
self.chk_counter += 1
# Write STOPCAR
-
actions = [{"file": "STOPCAR",
-
"action": {"_file_create": {'content': content}}},
-
{"file": "chkpt.yaml",
-
"action": {"_file_create": {'content': chkpt_content}}}]
+
actions = [
+
{"file": "STOPCAR", "action": {"_file_create": {"content": content}}},
+
{
+
"file": "chkpt.yaml",
+
"action": {"_file_create": {"content": chkpt_content}},
+
},
+
]
m = Modder(actions=[FileActions])
for a in actions:
@@ -1274,7 +1412,7 @@
Source code for custodian.vasp.handlers
return {"errors": ["Checkpoint reached"], "actions": actions}
-
def __str__(self):
+
def __str__(self):
return "CheckpointHandler with interval %d" % self.interval
@@ -1289,13 +1427,14 @@ Source code for custodian.vasp.handlers
stored in subdirs chk_#. This should be used in combination with the
StoppedRunHandler.
"""
+
is_monitor = False
# The CheckpointHandler should not terminate as we want VASP to terminate
# itself naturally with the STOPCAR.
is_terminating = False
- def __init__(self):
+ def __init__(self):
pass
[docs] def check(self):
@@ -1305,10 +1444,10 @@
Source code for custodian.vasp.handlers
d = loadfn("chkpt.yaml")
i = d["Index"]
name = shutil.make_archive(
- os.path.join(os.getcwd(), "vasp.chk.%d" % i), "gztar")
+ os.path.join(os.getcwd(), "vasp.chk.%d" % i), "gztar"
+ )
- actions = [{"file": "CONTCAR",
- "action": {"_file_copy": {"dest": "POSCAR"}}}]
+ actions = [{"file": "CONTCAR", "action": {"_file_copy": {"dest": "POSCAR"}}}]
m = Modder(actions=[FileActions])
for a in actions:
@@ -1316,8 +1455,7 @@ Source code for custodian.vasp.handlers
actions.append({"Checkpoint": name})
- return {"errors": ["Stopped run."],
- "actions": actions}
+
return {"errors": ["Stopped run."], "actions": actions}
[docs]class PositiveEnergyErrorHandler(ErrorHandler):
@@ -1325,9 +1463,10 @@
Source code for custodian.vasp.handlers
Check if a run has positive absolute energy.
If so, change ALGO from Fast to Normal or kill the job.
"""
+
is_monitor = True
- def __init__(self, output_filename="OSZICAR"):
+ def __init__(self, output_filename="OSZICAR"):
"""
Initializes the handler with the output file to check.
@@ -1350,16 +1489,14 @@ Source code for custodian.vasp.handlers
# change ALGO = Fast to Normal if ALGO is !Normal
vi = VaspInput.from_directory(".")
algo = vi["INCAR"].get("ALGO", "Normal")
- if algo.lower() not in ['normal', 'n']:
+ if algo.lower() not in ["normal", "n"]:
backup(VASP_BACKUP_FILES)
- actions = [{"dict": "INCAR",
- "action": {"_set": {"ALGO": "Normal"}}}]
+ actions = [{"dict": "INCAR", "action": {"_set": {"ALGO": "Normal"}}}]
VaspModder(vi=vi).apply_actions(actions)
return {"errors": ["Positive energy"], "actions": actions}
elif algo == "Normal":
potim = float(vi["INCAR"].get("POTIM", 0.5)) / 2.0
- actions = [{"dict": "INCAR",
- "action": {"_set": {"POTIM": potim}}}]
+ actions = [{"dict": "INCAR", "action": {"_set": {"POTIM": potim}}}]
VaspModder(vi=vi).apply_actions(actions)
return {"errors": ["Positive energy"], "actions": actions}
# Unfixable error. Just return None for actions.
@@ -1387,14 +1524,14 @@ Quick search
-
+
@@ -50,15 +49,15 @@ Navigation
Source code for custodian.vasp.interpreter
# coding: utf-8
-from __future__ import unicode_literals
+from __future__ import unicode_literals
-from custodian.ansible.actions import FileActions, DictActions
-from custodian.ansible.interpreter import Modder
-from pymatgen.io.vasp import VaspInput
+from custodian.ansible.actions import FileActions, DictActions
+from custodian.ansible.interpreter import Modder
+from pymatgen.io.vasp.inputs import VaspInput
[docs]class VaspModder(Modder):
-
def __init__(self, actions=None, strict=True, vi=None):
+
def __init__(self, actions=None, strict=True, vi=None):
"""
Initializes a Modder for VaspInput sets
@@ -74,7 +73,7 @@
Source code for custodian.vasp.interpreter
Initialized automatically if not passed (but passing it will
avoid having to reparse the directory).
"""
- self.vi = vi or VaspInput.from_directory('.')
+ self.vi = vi or VaspInput.from_directory(".")
actions = actions or [FileActions, DictActions]
super(VaspModder, self).__init__(actions, strict)
@@ -121,14 +120,14 @@ Quick search
-
+
@@ -50,7 +49,7 @@ Navigation
Source code for custodian.vasp.jobs
# coding: utf-8
-from __future__ import unicode_literals, division
+from __future__ import unicode_literals, division
import subprocess
import os
import shutil
@@ -59,16 +58,17 @@ Source code for custodian.vasp.jobs
import numpy as np
-from pymatgen import Structure
-from pymatgen.io.vasp import VaspInput, Incar, Poscar, Outcar, Kpoints, Vasprun
-from monty.os.path import which
-from monty.shutil import decompress_dir
-from monty.serialization import dumpfn, loadfn
+from pymatgen import Structure
+from pymatgen.io.vasp.inputs import VaspInput, Incar, Poscar, Kpoints
+from pymatgen.io.vasp.outputs import Vasprun, Outcar
+from monty.os.path import which
+from monty.shutil import decompress_dir
+from monty.serialization import dumpfn, loadfn
-from custodian.custodian import Job, SENTRY_DSN
-from custodian.utils import backup
-from custodian.vasp.interpreter import VaspModder
-from custodian.vasp.handlers import VASP_BACKUP_FILES
+from custodian.custodian import Job, SENTRY_DSN
+from custodian.utils import backup
+from custodian.vasp.interpreter import VaspModder
+from custodian.vasp.handlers import VASP_BACKUP_FILES
"""
This module implements basic kinds of jobs for VASP runs.
@@ -88,18 +88,43 @@ Source code for custodian.vasp.jobs
VASP_INPUT_FILES = {"INCAR", "POSCAR", "POTCAR", "KPOINTS"}
-VASP_OUTPUT_FILES = ['DOSCAR', 'INCAR', 'KPOINTS', 'POSCAR', 'PROCAR',
- 'vasprun.xml', 'CHGCAR', 'CHG', 'EIGENVAL', 'OSZICAR',
- 'WAVECAR', 'CONTCAR', 'IBZKPT', 'OUTCAR']
-
-VASP_NEB_INPUT_FILES = {'INCAR', 'POTCAR', 'KPOINTS'}
-
-VASP_NEB_OUTPUT_FILES = ['INCAR', 'KPOINTS', 'POTCAR', 'vasprun.xml']
-
-VASP_NEB_OUTPUT_SUB_FILES = ['CHG', 'CHGCAR', 'CONTCAR', 'DOSCAR',
- 'EIGENVAL', 'IBZKPT', 'PCDAT', 'POSCAR',
- 'REPORT', 'PROCAR', 'OSZICAR', 'OUTCAR',
- 'WAVECAR', 'XDATCAR']
+VASP_OUTPUT_FILES = [
+ "DOSCAR",
+ "INCAR",
+ "KPOINTS",
+ "POSCAR",
+ "PROCAR",
+ "vasprun.xml",
+ "CHGCAR",
+ "CHG",
+ "EIGENVAL",
+ "OSZICAR",
+ "WAVECAR",
+ "CONTCAR",
+ "IBZKPT",
+ "OUTCAR",
+]
+
+VASP_NEB_INPUT_FILES = {"INCAR", "POTCAR", "KPOINTS"}
+
+VASP_NEB_OUTPUT_FILES = ["INCAR", "KPOINTS", "POTCAR", "vasprun.xml"]
+
+VASP_NEB_OUTPUT_SUB_FILES = [
+ "CHG",
+ "CHGCAR",
+ "CONTCAR",
+ "DOSCAR",
+ "EIGENVAL",
+ "IBZKPT",
+ "PCDAT",
+ "POSCAR",
+ "REPORT",
+ "PROCAR",
+ "OSZICAR",
+ "OUTCAR",
+ "WAVECAR",
+ "XDATCAR",
+]
[docs]class VaspJob(Job):
@@ -108,11 +133,21 @@
Source code for custodian.vasp.jobs
can be a complex processing of inputs etc. with initialization.
"""
- def __init__(self, vasp_cmd, output_file="vasp.out",
- stderr_file="std_err.txt", suffix="", final=True,
- backup=True, auto_npar=False, auto_gamma=True,
- settings_override=None, gamma_vasp_cmd=None,
- copy_magmom=False, auto_continue=False):
+ def __init__(
+ self,
+ vasp_cmd,
+ output_file="vasp.out",
+ stderr_file="std_err.txt",
+ suffix="",
+ final=True,
+ backup=True,
+ auto_npar=False,
+ auto_gamma=True,
+ settings_override=None,
+ gamma_vasp_cmd=None,
+ copy_magmom=False,
+ auto_continue=False,
+ ):
"""
This constructor is necessarily complex due to the need for
flexibility. For standard kinds of runs, it's often better to use one
@@ -180,17 +215,20 @@ Source code for custodian.vasp.jobs
if SENTRY_DSN:
# if using Sentry logging, add specific VASP executable to scope
- from sentry_sdk import configure_scope
+ from sentry_sdk import configure_scope
+
with configure_scope() as scope:
try:
if isinstance(vasp_cmd, str):
- vasp_path = which(vasp_cmd.split(' ')[-1])
+ vasp_path = which(vasp_cmd.split(" ")[-1])
elif isinstance(vasp_cmd, list):
vasp_path = which(vasp_cmd[-1])
scope.set_tag("vasp_path", vasp_path)
scope.set_tag("vasp_cmd", vasp_cmd)
except Exception:
- logger.error("Failed to detect VASP path: {}".format(vasp_cmd), exc_info=True)
+ logger.error(
+ "Failed to detect VASP path: {}".format(vasp_cmd), exc_info=True
+ )
scope.set_tag("vasp_cmd", vasp_cmd)
[docs] def setup(self):
@@ -198,32 +236,36 @@
Source code for custodian.vasp.jobs
Performs initial setup for VaspJob, including overriding any settings
and backing up.
"""
- decompress_dir('.')
+ decompress_dir(".")
if self.backup:
for f in VASP_INPUT_FILES:
- shutil.copy(f, "{}.orig".format(f))
+ try:
+ shutil.copy(f, "{}.orig".format(f))
+ except FileNotFoundError: # handle the situation when there is no KPOINTS file
+ if f == "KPOINTS":
+ pass
if self.auto_npar:
try:
incar = Incar.from_file("INCAR")
# Only optimized NPAR for non-HF and non-RPA calculations.
- if not (incar.get("LHFCALC") or incar.get("LRPA") or
- incar.get("LEPSILON")):
+ if not (
+ incar.get("LHFCALC") or incar.get("LRPA") or incar.get("LEPSILON")
+ ):
if incar.get("IBRION") in [5, 6, 7, 8]:
# NPAR should not be set for Hessian matrix
# calculations, whether in DFPT or otherwise.
del incar["NPAR"]
else:
import multiprocessing
+
# try sge environment variable first
# (since multiprocessing counts cores on the current
# machine only)
- ncores = os.environ.get('NSLOTS') or \
- multiprocessing.cpu_count()
+ ncores = os.environ.get("NSLOTS") or multiprocessing.cpu_count()
ncores = int(ncores)
- for npar in range(int(math.sqrt(ncores)),
- ncores):
+ for npar in range(int(math.sqrt(ncores)), ncores):
if ncores % npar == 0:
incar["NPAR"] = npar
break
@@ -242,10 +284,13 @@ Source code for custodian.vasp.jobs
# Default functionality is to copy CONTCAR to POSCAR and set
# ISTART to 1 in the INCAR, but other actions can be specified
if self.auto_continue is True:
- actions = [{"file": "CONTCAR",
- "action": {"_file_copy": {"dest": "POSCAR"}}},
- {"dict": "INCAR",
- "action": {"_set": {"ISTART": 1}}}]
+ actions = [
+ {
+ "file": "CONTCAR",
+ "action": {"_file_copy": {"dest": "POSCAR"}},
+ },
+ {"dict": "INCAR", "action": {"_set": {"ISTART": 1}}},
+ ]
else:
actions = self.auto_continue
dumpfn({"actions": actions}, "continue.json")
@@ -264,16 +309,20 @@ Source code for custodian.vasp.jobs
if self.auto_gamma:
vi = VaspInput.from_directory(".")
kpts = vi["KPOINTS"]
- if kpts.style == Kpoints.supported_modes.Gamma \
- and tuple(kpts.kpts[0]) == (1, 1, 1):
- if self.gamma_vasp_cmd is not None and which(
- self.gamma_vasp_cmd[-1]):
- cmd = self.gamma_vasp_cmd
- elif which(cmd[-1] + ".gamma"):
- cmd[-1] += ".gamma"
+ if kpts is not None:
+ if kpts.style == Kpoints.supported_modes.Gamma and tuple(
+ kpts.kpts[0]
+ ) == (1, 1, 1):
+ if self.gamma_vasp_cmd is not None and which(
+ self.gamma_vasp_cmd[-1]
+ ):
+ cmd = self.gamma_vasp_cmd
+ elif which(cmd[-1] + ".gamma"):
+ cmd[-1] += ".gamma"
logger.info("Running {}".format(" ".join(cmd)))
- with open(self.output_file, 'w') as f_std, \
- open(self.stderr_file, "w", buffering=1) as f_err:
+ with open(self.output_file, "w") as f_std, open(
+ self.stderr_file, "w", buffering=1
+ ) as f_err:
# use line buffering for stderr
p = subprocess.Popen(cmd, stdout=f_std, stderr=f_err)
return p
@@ -293,12 +342,12 @@ Source code for custodian.vasp.jobs
if self.copy_magmom and not self.final:
try:
outcar = Outcar("OUTCAR")
- magmom = [m['tot'] for m in outcar.magnetization]
+ magmom = [m["tot"] for m in outcar.magnetization]
incar = Incar.from_file("INCAR")
- incar['MAGMOM'] = magmom
+ incar["MAGMOM"] = magmom
incar.write_file("INCAR")
except Exception:
- logger.error('MAGMOM copy from OUTCAR to INCAR failed')
+ logger.error("MAGMOM copy from OUTCAR to INCAR failed")
# Remove continuation so if a subsequent job is run in
# the same directory, will not restart this job.
@@ -306,8 +355,14 @@ Source code for custodian.vasp.jobs
os.remove("continue.json")
[docs] @classmethod
-
def double_relaxation_run(cls, vasp_cmd, auto_npar=True, ediffg=-0.05,
-
half_kpts_first_relax=False, auto_continue=False):
+
def double_relaxation_run(
+
cls,
+
vasp_cmd,
+
auto_npar=True,
+
ediffg=-0.05,
+
half_kpts_first_relax=False,
+
auto_continue=False,
+
):
"""
Returns a list of two jobs corresponding to an AFLOW style double
relaxation run.
@@ -334,37 +389,57 @@
Source code for custodian.vasp.jobs
incar_update["EDIFFG"] = ediffg
settings_overide_1 = None
settings_overide_2 = [
- {"dict": "INCAR",
- "action": {"_set": incar_update}},
- {"file": "CONTCAR",
- "action": {"_file_copy": {"dest": "POSCAR"}}}]
- if half_kpts_first_relax and os.path.exists("KPOINTS") and \
- os.path.exists("POSCAR"):
+ {"dict": "INCAR", "action": {"_set": incar_update}},
+ {"file": "CONTCAR", "action": {"_file_copy": {"dest": "POSCAR"}}},
+ ]
+ if (
+ half_kpts_first_relax
+ and os.path.exists("KPOINTS")
+ and os.path.exists("POSCAR")
+ ):
kpts = Kpoints.from_file("KPOINTS")
orig_kpts_dict = kpts.as_dict()
# lattice vectors with length < 8 will get >1 KPOINT
- kpts.kpts = np.round(np.maximum(np.array(kpts.kpts) / 2,
- 1)).astype(int).tolist()
+ kpts.kpts = (
+ np.round(np.maximum(np.array(kpts.kpts) / 2, 1)).astype(int).tolist()
+ )
low_kpts_dict = kpts.as_dict()
settings_overide_1 = [
- {"dict": "KPOINTS",
- "action": {"_set": low_kpts_dict}}
+ {"dict": "KPOINTS", "action": {"_set": low_kpts_dict}}
]
settings_overide_2.append(
- {"dict": "KPOINTS",
- "action": {"_set": orig_kpts_dict}}
+ {"dict": "KPOINTS", "action": {"_set": orig_kpts_dict}}
)
- return [VaspJob(vasp_cmd, final=False, suffix=".relax1",
- auto_npar=auto_npar, auto_continue=auto_continue,
- settings_override=settings_overide_1),
- VaspJob(vasp_cmd, final=True, backup=False, suffix=".relax2",
- auto_npar=auto_npar, auto_continue=auto_continue,
- settings_override=settings_overide_2)]
+
return [
+
VaspJob(
+
vasp_cmd,
+
final=False,
+
suffix=".relax1",
+
auto_npar=auto_npar,
+
auto_continue=auto_continue,
+
settings_override=settings_overide_1,
+
),
+
VaspJob(
+
vasp_cmd,
+
final=True,
+
backup=False,
+
suffix=".relax2",
+
auto_npar=auto_npar,
+
auto_continue=auto_continue,
+
settings_override=settings_overide_2,
+
),
+
]
[docs] @classmethod
-
def metagga_opt_run(cls, vasp_cmd, auto_npar=True, ediffg=-0.05,
-
half_kpts_first_relax=False, auto_continue=False):
+
def metagga_opt_run(
+
cls,
+
vasp_cmd,
+
auto_npar=True,
+
ediffg=-0.05,
+
half_kpts_first_relax=False,
+
auto_continue=False,
+
):
"""
Returns a list of thres jobs to perform an optimization for any
metaGGA functional. There is an initial calculation of the
@@ -378,29 +453,50 @@
Source code for custodian.vasp.jobs
metaGGA = incar.get("METAGGA", "SCAN")
# Pre optimze WAVECAR and structure using regular GGA
- pre_opt_setings = [{"dict": "INCAR",
- "action": {"_set": {"METAGGA": None,
- "LWAVE": True,
- "NSW": 0}}}]
- jobs = [VaspJob(vasp_cmd, auto_npar=auto_npar,
- final=False, suffix=".precondition",
- settings_override=pre_opt_setings)]
+ pre_opt_setings = [
+ {
+ "dict": "INCAR",
+ "action": {"_set": {"METAGGA": None, "LWAVE": True, "NSW": 0}},
+ }
+ ]
+ jobs = [
+ VaspJob(
+ vasp_cmd,
+ auto_npar=auto_npar,
+ final=False,
+ suffix=".precondition",
+ settings_override=pre_opt_setings,
+ )
+ ]
# Finish with regular double relaxation style run using SCAN
- jobs.extend(VaspJob.double_relaxation_run(vasp_cmd, auto_npar=auto_npar,
- ediffg=ediffg,
- half_kpts_first_relax=half_kpts_first_relax))
+ jobs.extend(
+ VaspJob.double_relaxation_run(
+ vasp_cmd,
+ auto_npar=auto_npar,
+ ediffg=ediffg,
+ half_kpts_first_relax=half_kpts_first_relax,
+ )
+ )
# Ensure the first relaxation doesn't overwrite the original inputs
jobs[1].backup = False
# Update double_relaxation job to start from pre-optimized run
- post_opt_settings = [{"dict": "INCAR",
- "action": {"_set": {"METAGGA": metaGGA, "ISTART": 1,
- "NSW": incar.get("NSW", 99),
- "LWAVE": incar.get("LWAVE", False)}}},
- {"file": "CONTCAR",
- "action": {"_file_copy": {"dest": "POSCAR"}}}]
+ post_opt_settings = [
+ {
+ "dict": "INCAR",
+ "action": {
+ "_set": {
+ "METAGGA": metaGGA,
+ "ISTART": 1,
+ "NSW": incar.get("NSW", 99),
+ "LWAVE": incar.get("LWAVE", False),
+ }
+ },
+ },
+ {"file": "CONTCAR", "action": {"_file_copy": {"dest": "POSCAR"}}},
+ ]
if jobs[1].settings_override:
post_opt_settings = jobs[1].settings_override + post_opt_settings
jobs[1].settings_override = post_opt_settings
@@ -408,9 +504,15 @@ Source code for custodian.vasp.jobs
[docs] @classmethod
-
def full_opt_run(cls, vasp_cmd, vol_change_tol=0.02,
-
max_steps=10, ediffg=-0.05, half_kpts_first_relax=False,
-
**vasp_job_kwargs):
+
def full_opt_run(
+
cls,
+
vasp_cmd,
+
vol_change_tol=0.02,
+
max_steps=10,
+
ediffg=-0.05,
+
half_kpts_first_relax=False,
+
**vasp_job_kwargs
+
):
"""
Returns a generator of jobs for a full optimization run. Basically,
this runs an infinite series of geometry optimization jobs until the
@@ -439,16 +541,16 @@
Source code for custodian.vasp.jobs
if i == 0:
settings = None
backup = True
- if half_kpts_first_relax and os.path.exists("KPOINTS") and \
- os.path.exists("POSCAR"):
+ if (
+ half_kpts_first_relax
+ and os.path.exists("KPOINTS")
+ and os.path.exists("POSCAR")
+ ):
kpts = Kpoints.from_file("KPOINTS")
orig_kpts_dict = kpts.as_dict()
kpts.kpts = np.maximum(np.array(kpts.kpts) / 2, 1).tolist()
low_kpts_dict = kpts.as_dict()
- settings = [
- {"dict": "KPOINTS",
- "action": {"_set": low_kpts_dict}}
- ]
+ settings = [{"dict": "KPOINTS", "action": {"_set": low_kpts_dict}}]
else:
backup = False
initial = Poscar.from_file("POSCAR").structure
@@ -464,22 +566,37 @@ Source code for custodian.vasp.jobs
if ediffg:
incar_update["EDIFFG"] = ediffg
settings = [
- {"dict": "INCAR",
- "action": {"_set": incar_update}},
- {"file": "CONTCAR",
- "action": {"_file_copy": {"dest": "POSCAR"}}}]
+ {"dict": "INCAR", "action": {"_set": incar_update}},
+ {
+ "file": "CONTCAR",
+ "action": {"_file_copy": {"dest": "POSCAR"}},
+ },
+ ]
if i == 1 and half_kpts_first_relax:
- settings.append({"dict": "KPOINTS",
- "action": {"_set": orig_kpts_dict}})
- logger.info("Generating job = %d!" % (i+1))
- yield VaspJob(vasp_cmd, final=False, backup=backup,
- suffix=".relax%d" % (i+1), settings_override=settings,
- **vasp_job_kwargs)
+ settings.append(
+ {"dict": "KPOINTS", "action": {"_set": orig_kpts_dict}}
+ )
+ logger.info("Generating job = %d!" % (i + 1))
+ yield VaspJob(
+ vasp_cmd,
+ final=False,
+ backup=backup,
+ suffix=".relax%d" % (i + 1),
+ settings_override=settings,
+ **vasp_job_kwargs
+ )
[docs] @classmethod
-
def constrained_opt_run(cls, vasp_cmd, lattice_direction, initial_strain,
-
atom_relax=True, max_steps=20, algo="bfgs",
-
**vasp_job_kwargs):
+
def constrained_opt_run(
+
cls,
+
vasp_cmd,
+
lattice_direction,
+
initial_strain,
+
atom_relax=True,
+
max_steps=20,
+
algo="bfgs",
+
**vasp_job_kwargs
+
):
"""
Returns a generator of jobs for a constrained optimization run. Typical
use case is when you want to approximate a biaxial strain situation,
@@ -544,8 +661,8 @@
Source code for custodian.vasp.jobs
for i in range(max_steps):
if i == 0:
settings = [
- {"dict": "INCAR",
- "action": {"_set": {"ISIF": 2, "NSW": nsw}}}]
+ {"dict": "INCAR", "action": {"_set": {"ISIF": 2, "NSW": nsw}}}
+ ]
structure = Poscar.from_file("POSCAR").structure
x = structure.lattice.abc[lattice_index]
backup = True
@@ -561,7 +678,7 @@ Source code for custodian.vasp.jobs
energies[x] = energy
if i == 1:
- x *= (1 + initial_strain)
+ x *= 1 + initial_strain
else:
# Sort the lattice parameter by energies.
min_x = min(energies.keys(), key=lambda e: energies[e])
@@ -572,14 +689,16 @@ Source code for custodian.vasp.jobs
elif ind == len(sorted_x) - 1:
other = ind - 1
else:
- other = ind + 1 \
- if energies[sorted_x[ind + 1]] \
- < energies[sorted_x[ind - 1]] \
+ other = (
+ ind + 1
+ if energies[sorted_x[ind + 1]] < energies[sorted_x[ind - 1]]
else ind - 1
- if abs(energies[min_x]
- - energies[sorted_x[other]]) < etol:
- logger.info("Stopping optimization! Final %s = %f"
- % (lattice_direction, min_x))
+ )
+ if abs(energies[min_x] - energies[sorted_x[other]]) < etol:
+ logger.info(
+ "Stopping optimization! Final %s = %f"
+ % (lattice_direction, min_x)
+ )
break
if ind == 0 and len(sorted_x) > 2:
@@ -588,16 +707,20 @@ Source code for custodian.vasp.jobs
# iteration to find a minimum. This applies only when
# there are at least 3 values.
x = sorted_x[0] - abs(sorted_x[1] - sorted_x[0])
- logger.info("Lowest energy lies below bounds. "
- "Setting %s = %f." % (lattice_direction, x))
+ logger.info(
+ "Lowest energy lies below bounds. "
+ "Setting %s = %f." % (lattice_direction, x)
+ )
elif ind == len(sorted_x) - 1 and len(sorted_x) > 2:
# Lowest energy lies outside of range of highest value.
# we increase the lattice parameter in the next
# iteration to find a minimum. This applies only when
# there are at least 3 values.
x = sorted_x[-1] + abs(sorted_x[-1] - sorted_x[-2])
- logger.info("Lowest energy lies above bounds. "
- "Setting %s = %f." % (lattice_direction, x))
+ logger.info(
+ "Lowest energy lies above bounds. "
+ "Setting %s = %f." % (lattice_direction, x)
+ )
else:
if algo.lower() == "bfgs" and len(sorted_x) >= 4:
try:
@@ -607,30 +730,33 @@ Source code for custodian.vasp.jobs
y1 = [energies[j] for j in x1]
z1 = np.polyfit(x1, y1, 2)
pp = np.poly1d(z1)
- from scipy.optimize import minimize
+ from scipy.optimize import minimize
+
result = minimize(
- pp, min_x,
- bounds=[(sorted_x[0], sorted_x[-1])])
+ pp, min_x, bounds=[(sorted_x[0], sorted_x[-1])]
+ )
if (not result.success) or result.x[0] < 0:
- raise ValueError(
- "Negative lattice constant!")
+ raise ValueError("Negative lattice constant!")
x = result.x[0]
- logger.info("BFGS minimized %s = %f."
- % (lattice_direction, x))
+ logger.info(
+ "BFGS minimized %s = %f." % (lattice_direction, x)
+ )
except ValueError as ex:
# Fall back on bisection algo if the bfgs fails.
logger.info(str(ex))
x = (min_x + sorted_x[other]) / 2
- logger.info("Falling back on bisection %s = %f."
- % (lattice_direction, x))
+ logger.info(
+ "Falling back on bisection %s = %f."
+ % (lattice_direction, x)
+ )
else:
x = (min_x + sorted_x[other]) / 2
- logger.info("Bisection %s = %f."
- % (lattice_direction, x))
+ logger.info("Bisection %s = %f." % (lattice_direction, x))
lattice = lattice.matrix
- lattice[lattice_index] = lattice[lattice_index] / \
- np.linalg.norm(lattice[lattice_index]) * x
+ lattice[lattice_index] = (
+ lattice[lattice_index] / np.linalg.norm(lattice[lattice_index]) * x
+ )
s = Structure(lattice, structure.species, structure.frac_coords)
fname = "POSCAR.%f" % x
@@ -639,15 +765,19 @@ Source code for custodian.vasp.jobs
incar_update = {"ISTART": 1, "NSW": nsw, "ISIF": 2}
settings = [
- {"dict": "INCAR",
- "action": {"_set": incar_update}},
- {"file": fname,
- "action": {"_file_copy": {"dest": "POSCAR"}}}]
+ {"dict": "INCAR", "action": {"_set": incar_update}},
+ {"file": fname, "action": {"_file_copy": {"dest": "POSCAR"}}},
+ ]
logger.info("Generating job = %d with parameter %f!" % (i + 1, x))
- yield VaspJob(vasp_cmd, final=False, backup=backup,
- suffix=".static.%f" % x,
- settings_override=settings, **vasp_job_kwargs)
+ yield VaspJob(
+ vasp_cmd,
+ final=False,
+ backup=backup,
+ suffix=".static.%f" % x,
+ settings_override=settings,
+ **vasp_job_kwargs
+ )
with open("EOS.txt", "wt") as f:
f.write("# %s energy\n" % lattice_direction)
@@ -670,11 +800,21 @@ Source code for custodian.vasp.jobs
arrangement in NEB calculation.
"""
- def __init__(self, vasp_cmd,
- output_file="neb_vasp.out", stderr_file="neb_std_err.txt",
- suffix="", final=True, backup=True, auto_npar=True,
- half_kpts=False, auto_gamma=True, auto_continue=False,
- gamma_vasp_cmd=None, settings_override=None):
+ def __init__(
+ self,
+ vasp_cmd,
+ output_file="neb_vasp.out",
+ stderr_file="neb_std_err.txt",
+ suffix="",
+ final=True,
+ backup=True,
+ auto_npar=True,
+ half_kpts=False,
+ auto_gamma=True,
+ auto_continue=False,
+ gamma_vasp_cmd=None,
+ settings_override=None,
+ ):
"""
This constructor is a simplified version of VaspJob, which satisfies
the need for flexibility. For standard kinds of runs, it's often
@@ -746,7 +886,7 @@ Source code for custodian.vasp.jobs
if os.path.isdir(path) and path.isdigit():
self.neb_dirs.append(path)
self.neb_dirs = sorted(self.neb_dirs)
- self.neb_sub = self.neb_dirs[1: -1]
+ self.neb_sub = self.neb_dirs[1:-1]
[docs] def setup(self):
"""
@@ -770,7 +910,7 @@
Source code for custodian.vasp.jobs
kpts.kpts = kpts.kpts.astype(int).tolist()
if tuple(kpts.kpts[0]) == (1, 1, 1):
kpt_dic = kpts.as_dict()
- kpt_dic["generation_style"] = 'Gamma'
+ kpt_dic["generation_style"] = "Gamma"
kpts = Kpoints.from_dict(kpt_dic)
kpts.write_file("KPOINTS")
@@ -778,13 +918,13 @@ Source code for custodian.vasp.jobs
try:
incar = Incar.from_file("INCAR")
import multiprocessing
+
# Try sge environment variable first
# (since multiprocessing counts cores on the current
# machine only)
- ncores = os.environ.get('NSLOTS') or multiprocessing.cpu_count()
+ ncores = os.environ.get("NSLOTS") or multiprocessing.cpu_count()
ncores = int(ncores)
- for npar in range(int(math.sqrt(ncores)),
- ncores):
+ for npar in range(int(math.sqrt(ncores)), ncores):
if ncores % npar == 0:
incar["NPAR"] = npar
break
@@ -792,9 +932,11 @@ Source code for custodian.vasp.jobs
except Exception:
pass
- if self.auto_continue and \
- os.path.exists("STOPCAR") and \
- not os.access("STOPCAR", os.W_OK):
+ if (
+ self.auto_continue
+ and os.path.exists("STOPCAR")
+ and not os.access("STOPCAR", os.W_OK)
+ ):
# Remove STOPCAR
os.chmod("STOPCAR", 0o644)
os.remove("STOPCAR")
@@ -818,16 +960,19 @@ Source code for custodian.vasp.jobs
cmd = list(self.vasp_cmd)
if self.auto_gamma:
kpts = Kpoints.from_file("KPOINTS")
- if kpts.style == Kpoints.supported_modes.Gamma \
- and tuple(kpts.kpts[0]) == (1, 1, 1):
- if self.gamma_vasp_cmd is not None and which(
- self.gamma_vasp_cmd[-1]):
+ if kpts.style == Kpoints.supported_modes.Gamma and tuple(kpts.kpts[0]) == (
+ 1,
+ 1,
+ 1,
+ ):
+ if self.gamma_vasp_cmd is not None and which(self.gamma_vasp_cmd[-1]):
cmd = self.gamma_vasp_cmd
elif which(cmd[-1] + ".gamma"):
cmd[-1] += ".gamma"
logger.info("Running {}".format(" ".join(cmd)))
- with open(self.output_file, 'w') as f_std, \
- open(self.stderr_file, "w", buffering=1) as f_err:
+ with open(self.output_file, "w") as f_std, open(
+ self.stderr_file, "w", buffering=1
+ ) as f_err:
# Use line buffering for stderr
p = subprocess.Popen(cmd, stdout=f_std, stderr=f_err)
@@ -857,8 +1002,7 @@ Source code for custodian.vasp.jobs
-
+
@@ -50,11 +49,11 @@ Navigation
Source code for custodian.vasp.validators
# coding: utf-8
-from __future__ import unicode_literals, division
+from __future__ import unicode_literals, division
-from custodian.custodian import Validator
-from pymatgen.io.vasp import Vasprun, Incar, Outcar, Chgcar
-from collections import deque
+from custodian.custodian import Validator
+from pymatgen.io.vasp import Vasprun, Incar, Outcar, Chgcar
+from collections import deque
import os
import logging
@@ -65,7 +64,7 @@ Source code for custodian.vasp.validators
Checks that a valid vasprun.xml was generated
"""
- def __init__(self, output_file="vasp.out", stderr_file="std_err.txt"):
+ def __init__(self, output_file="vasp.out", stderr_file="std_err.txt"):
"""
Args:
output_file (str): Name of file VASP standard output is directed to.
@@ -104,8 +103,9 @@ Source code for custodian.vasp.validators
vasprun_tail = deque(vasprun, maxlen=10)
exception_context["vasprun_tail"] = "".join(vasprun_tail)
- self.logger.error("Failed to load vasprun.xml",
- exc_info=True, extra=exception_context)
+ self.logger.error(
+ "Failed to load vasprun.xml", exc_info=True, extra=exception_context
+ )
return True
return False
@@ -117,7 +117,7 @@ Source code for custodian.vasp.validators
normally create upon running.
"""
- def __init__(self):
+ def __init__(self):
pass
[docs] def check(self):
@@ -133,7 +133,7 @@
Source code for custodian.vasp.validators
Currently, VASP only have Langevin thermostat (MDALGO = 3) for NpT ensemble.
"""
- def __init__(self):
+ def __init__(self):
pass
[docs] def check(self):
@@ -145,7 +145,7 @@
Source code for custodian.vasp.validators
outcar = Outcar("OUTCAR")
patterns = {"MDALGO": "MDALGO\s+=\s+([\d]+)"}
outcar.read_pattern(patterns=patterns)
- if outcar.data["MDALGO"] == [['3']]:
+ if outcar.data["MDALGO"] == [["3"]]:
return False
else:
return True
@@ -156,7 +156,7 @@ Source code for custodian.vasp.validators
Check if the data in the AECCAR is corrupted
"""
- def __init__(self):
+ def __init__(self):
pass
[docs] def check(self):
@@ -166,16 +166,29 @@
Source code for custodian.vasp.validators
return check_broken_chgcar(aeccar)
-[docs]def check_broken_chgcar(chgcar):
-
chgcar_data = chgcar.data['total']
+
[docs]def check_broken_chgcar(chgcar, diff_thresh=None):
+
"""
+
Check if the charge density file is corrupt
+
Args:
+
chgcar (Chgcar): Chgcar-like object.
+
diff_thresh (Float): Threshhold for diagonal difference.
+
None means we won't check for this.
+
"""
+
chgcar_data = chgcar.data["total"]
if (chgcar_data < 0).sum() > 100:
-
# a decent bunch of the values are negative
+
# a decent bunch of the values are negative this for sure means a broken charge density
return True
-
diff = chgcar_data[:-1, :-1, :-1] - chgcar_data[1:, 1:, 1:]
-
if diff.max() / (chgcar_data.max() - chgcar_data.min()) > 0.95:
-
# Some single diagonal finite difference is more than 95% of the entire range
-
return True
+
if diff_thresh:
+
"""
+
If any one diagonal difference accounts for more than a particular portion of
+
the total difference between highest and lowest density.
+
When we are looking at AECCAR data, since the charge density is so high near the core
+
and we have a course grid, this threshhold can be as high as 0.99
+
"""
+
diff = chgcar_data[:-1, :-1, :-1] - chgcar_data[1:, 1:, 1:]
+
if diff.max() / (chgcar_data.max() - chgcar_data.min()) > diff_thresh:
+
return True
return False
@@ -200,14 +213,14 @@ Quick search
-
+
@@ -58,6 +57,8 @@ All modules for which code is available
custodian.feff.handlers
custodian.feff.interpreter
custodian.feff.jobs
+
custodian.lobster.handlers
+
custodian.lobster.jobs
custodian.nwchem.handlers
custodian.nwchem.jobs
custodian.qchem.handlers
@@ -87,14 +88,14 @@
Quick search
-
+