Source code for custodian.cli.converge_kpoints
"until a converged of 1meV is reached." ) - args = parser.parse_args()
diff --git a/custodian/__init__.py b/custodian/__init__.py index 6d94eeab..7f535106 100644 --- a/custodian/__init__.py +++ b/custodian/__init__.py @@ -2,6 +2,6 @@ __author__ = "Shyue Ping Ong, William Davidson Richards, Stephen Dacek, " \ "Xiaohui Qu" -__version__ = "2018.3.10" +__version__ = "2018.6.11" from .custodian import Custodian \ No newline at end of file diff --git a/docs/_modules/custodian/ansible/actions.html b/docs/_modules/custodian/ansible/actions.html index 64cb6216..5c2b2c42 100644 --- a/docs/_modules/custodian/ansible/actions.html +++ b/docs/_modules/custodian/ansible/actions.html @@ -6,10 +6,10 @@
-
if tok not in current and i < n - 1:
current[tok] = {}
elif i == n - 1:
- return current, toks[-1]
[docs] @staticmethod
def set(input_dict, settings):
for k, v in settings.items():
- (d, key) = get_nested_dict(input_dict, k)
- d[key] = v
+ (d, key) = get_nested_dict(input_dict, k)
+ d[key] = v
(d, key) = get_nested_dict(input_dict, k)
if key in d:
d[key].append(v)
- else:
(d, key) = get_nested_dict(input_dict, k)
if key in d:
d[key].extend(v)
- else:
(d, key) = get_nested_dict(input_dict, k)
if key in d:
d[key] += v
- else:
.format(k))
if key in d and v not in d[key]:
d[key].append(v)
- elif key not in d:
if key in d and (not isinstance(d[key], list)):
raise ValueError("Keyword {} does not refer to an array."
.format(k))
- if key in d:
if k in input_dict and (not isinstance(input_dict[k], list)):
raise ValueError("Keyword {} does not refer to an array."
.format(k))
- for i in v:
.format(k))
if v == 1:
d[key].pop()
- elif v == -1:
"'content'.")
for k, v in settings.items():
if k == "content":
- with open(filename, 'w') as f:
raise ValueError("Settings must only contain one item with key "
"'dest'.")
for k, v in settings.items():
- if k == "dest":
except OSError:
#Skip file not found error.
pass
- elif k == "mode" and v == "simulated":
settings (dict): Must be {"dest": path of new file}
"""
for k, v in settings.items():
- if k.startswith("dest"):
for k, v in settings.items():
if k == "mode":
os.chmod(filename,v)
- if k == "owners":
{"filename": "CONTCAR", "action": {"_file_copy": {"dest": "POSCAR"}}}] yield VaspJob(vasp_command, final=False, backup=backup, - suffix=".kpoints.{}".format("x".join(map(str, m))),
c = Custodian(handlers, get_runs(vasp_command=args.command.split(), target=args.target, mode=args.mode, max_steps=args.max_steps), - max_errors=10)
"until a converged of 1meV is reached." ) - args = parser.parse_args()
logging.basicConfig(format=FORMAT, level=logging.INFO, filename="run.log")
logging.info("Spec file is %s" % args.spec_file)
d = loadfn(args.spec_file[0])
- c = Custodian.from_spec(d)
a = getattr(args, "func")
except AttributeError:
parser.print_help()
- sys.exit(0)
output_file=args.outfile)
c = Custodian([NwchemErrorHandler(output_filename=args.outfile)], [job],
max_errors=5, scratch_dir=args.scratch,
- gzipped_output=args.gzipped, checkpoint=True)
"are going to perform an additional static run."
)
- args = parser.parse_args()
elif len(toks) > 2:
print("Bad handler specification")
sys.exit(-1)
- mod = __import__(mod, globals(), locals(), [toks[0]], 0)
if not job_type.startswith("full_relax"):
yield VaspJob(vasp_command, final=final, suffix=suffix,
- backup=backup, settings_override=settings,
c = Custodian(handlers, get_jobs(args), validators,
max_errors=args.max_errors, scratch_dir=args.scratch,
gzipped_output=args.gzip,
- checkpoint=True)
"but this can be overrridden by adding a number to the job"
"type, e.g. relax5 relax6 relax7")
- args = parser.parse_args()
custodian_params = process_params(spec.get("custodian_params", {}))
- return cls(jobs=jobs, handlers=handlers, validators=validators,
# Cleanup checkpoint files (if any) if run is successful.
Custodian._delete_checkpoints(cwd)
-
else:
has_error = self._do_check(self.handlers)
+ if has_error:
+ # This makes sure the job is killed cleanly for certain systems.
+ job.terminate()
+
# If there are no errors detected, perform
# postprocessing and exit.
if not has_error:
@@ -605,8 +609,8 @@ Source code for custodian.custodian
run_time = end - start
logger.info("Run completed. Total time taken = {}."
.format(run_time))
- if self.finished and self.gzipped_output:
- gzip_dir(".")
+ if self.finished and self.gzipped_output:
+ gzip_dir(".")
"actions": []})
self.total_errors += len(corrections)
self.errors_current_job += len(corrections)
- self.run_log[-1]["corrections"].extend(corrections)
"""
This method is run before the start of a job. Allows for some
pre-processing.
- """
This method is called at the end of a job, *after* error detection.
This allows post-processing, such as cleanup, analysis of results,
etc.
- """
Returns:
(bool) Indicating if errors are detected.
- """
actions taken. E.g.
{"errors": list_of_errors, "actions": list_of_actions_taken}.
If this is an unfixable error, actions should be set to None.
- """
Returns:
(bool) Indicating if errors are detected.
- """
"""
super(CustodianError, self).__init__(self, message)
self.raises = raises
- self.validator = validator
"""
If the FEFF run does not converge, the check will return
"TRUE"
- """
return {"errors": ["Non-converging job"], "actions": actions}
# Unfixable error. Just return None for actions.
- else:
with open(os.path.join('.', k), "w") as f:
f.write(str(v))
- with open(os.path.join('.', "feff.inp"), "w") as f:
shutil.copy(f, "{}.orig".format(f))
for f in FEFF_BACKUP_FILES:
- if os.path.isfile(f):
# 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)
-
if out.data[-1]["has_error"]:
self.errors.extend(out.data[-1]["errors"])
self.errors = list(set(self.errors))
- self.ntasks = len(out.data)
m = Modder()
for action in actions:
nwi = m.modify_object(action, nwi)
- nwi.write_file(self.input_file)
"""
Performs backup if necessary.
"""
- if self.backup:
self.error_step_id = i
self.fix_step = self.qcinp.jobs[i]
self.errors = sorted(list(set(od["errors"])))
- return True
return {"errors": self.errors, "actions": None}
else:
return {"errors": self.errors, "actions": None}
- self.qcinp.write_file(self.input_file)
if self.qchem_job.current_command_name != "half_cpus":
self.qchem_job.select_command("half_cpus", self.qcinp)
return "half_cpus"
- else:
else:
if self.fix_step.params['rem']["jobtype"] == "freq":
act = self.fix_not_enough_total_memory()
- return act
# noinspection PyProtectedMember
self.qchem_job._set_qchem_memory(self.qcinp)
return "Increase Static Memory"
- else:
elif self.qchem_job.current_command_name != "openmp":
self.qchem_job.select_command("openmp", self.qcinp)
return "Use OpenMP"
- else:
comments = scf_pattern.sub(strategy_text, comments)
else:
comments += "\n" + strategy_text
- self.fix_step.params["comment"] = comments
comments = geom_pattern.sub(strategy_text, comments)
else:
comments += "\n" + strategy_text
- self.fix_step.params["comment"] = comments
list(self.ex_backup_list)
for f in bak_list:
if os.path.exists(f):
- tar.add(f)
"outdata": self.outdata,
"qcinp": self.qcinp.as_dict() if self.qcinp else None,
"error_step_id": self.error_step_id,
- "errors": self.errors,
h.qcinp = QcInput.from_dict(d["qcinp"]) if d["qcinp"] else None
h.error_step_id = d["error_step_id"]
h.errors = d["errors"]
- h.fix_step = QcTask.from_dict(d["fix_step"]) if d["fix_step"] else None
if j.params["rem"]["exchange"] in ["pbe", "b"] \
and "correlation" in j.params['rem'] \
and j.params["rem"]["correlation"] in ["pbe", "lyp"]:
- return False
else:
self.current_command = self.alt_cmd[cmd_name]
self.current_command_name = cmd_name
- self._set_qchem_memory(qcinp)
shutil.copy(self.output_file,
"{}.{}.orig".format(self.output_file, i))
if self.qclog_file and os.path.exists(self.qclog_file):
- shutil.copy(self.qclog_file,
subprocess.call(tmp_creation_cmd)
returncode = self._run_qchem()
if tmp_clean_cmd:
- subprocess.call(tmp_clean_cmd)
"backup": self.backup,
"large_static_mem": self.large_static_mem,
"alt_cmd": self.alt_cmd,
- "run_name": self.run_name}
gzipped=d["gzipped"],
backup=d["backup"],
alt_cmd=d["alt_cmd"],
- large_static_mem=d["large_static_mem"],
gzip_cmd = shlex.split("srun -N 1 --ntasks-per-node 1 --nodelist "
"{} gzip".format(nodelist)) + file_list
subprocess.call(gzip_cmd)
- else:
+# coding: utf-8
+
+from __future__ import unicode_literals, division
+
+# This module implements new error handlers for QChem runs.
+
+import os
+from pymatgen.io.qchem_io.inputs import QCInput
+from pymatgen.io.qchem_io.outputs import QCOutput
+from custodian.custodian import ErrorHandler
+from custodian.utils import backup
+
+__author__ = "Samuel Blau, Brandon Woods, Shyam Dwaraknath"
+__copyright__ = "Copyright 2018, The Materials Project"
+__version__ = "0.1"
+__maintainer__ = "Samuel Blau"
+__email__ = "samblau1@gmail.com"
+__status__ = "Alpha"
+__date__ = "3/26/18"
+__credits__ = "Xiaohui Qu"
+
+
+[docs]class QChemErrorHandler(ErrorHandler):
+ """
+ Master QChemErrorHandler class that handles a number of common errors
+ that occur during QChem runs.
+ """
+
+ is_monitor = False
+
+ 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.
+
+ Args:
+ input_file (str): Name of the QChem input file.
+ output_file (str): Name of the QChem output file.
+ scf_max_cycles (int): The max iterations to set to fix SCF failure.
+ geom_max_cycles (int): The max iterations to set to fix geometry
+ optimization failure.
+ """
+ self.input_file = input_file
+ self.output_file = output_file
+ self.scf_max_cycles = scf_max_cycles
+ self.geom_max_cycles = geom_max_cycles
+ self.qcinp = QCInput.from_file(self.input_file)
+ self.outdata = None
+ self.errors = []
+
+[docs] def check(self):
+ # Checks output file for errors.
+ self.outdata = QCOutput(self.output_file).data
+ self.errors = self.outdata.get("errors")
+ return len(self.errors) > 0
+
+[docs] def correct(self):
+ backup({self.input_file, self.output_file})
+ actions = []
+
+ if "SCF_failed_to_converge" in self.errors:
+ # Check number of SCF cycles. If not set or less than scf_max_cycles,
+ # increase to that value and rerun. If already set, check if
+ # scf_algorithm is unset or set to DIIS, in which case set to RCA-DIIS.
+ # Otherwise, tell user to call SCF error handler and do nothing.
+ if 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("scf_algorithm", "diis").lower() == "diis":
+ self.qcinp.rem["scf_algorithm"] = "rca_diis"
+ actions.append({"scf_algorithm": "rca_diis"})
+ if self.qcinp.rem.get("gen_scfman"):
+ self.qcinp.rem["gen_scfman"] = False
+ actions.append({"gen_scfman": False})
+ else:
+ print(
+ "More advanced changes may impact the SCF result. Use the SCF error handler"
+ )
+
+ 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 self.qcinp.rem.get(
+ "geom_opt_max_cycles") != 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:
+ if self.qcinp.molecule.spin_multiplicity != self.outdata.get(
+ "molecule_from_last_geometry").spin_multiplicity:
+ raise AssertionError('Multiplicities should match!')
+ if self.qcinp.molecule.charge != self.outdata.get(
+ "molecule_from_last_geometry").charge:
+ raise AssertionError('Charges should match!')
+ self.qcinp.molecule = self.outdata.get(
+ "molecule_from_last_geometry")
+ actions.append({"molecule": "molecule_from_last_geometry"})
+ else:
+ print(
+ "How do I get the geometry optimization converged when already at the maximum number of cycles?"
+ )
+
+ elif "unable_to_determine_lamda" in self.errors:
+ # 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:
+ if self.qcinp.molecule.spin_multiplicity != self.outdata.get(
+ "molecule_from_last_geometry").spin_multiplicity:
+ raise AssertionError('Multiplicities should match!')
+ if self.qcinp.molecule.charge != self.outdata.get(
+ "molecule_from_last_geometry").charge:
+ raise AssertionError('Charges should match!')
+ self.qcinp.molecule = self.outdata.get(
+ "molecule_from_last_geometry")
+ actions.append({"molecule": "molecule_from_last_geometry"})
+ elif self.qcinp.rem.get("scf_algorithm", "diis").lower() == "diis":
+ self.qcinp.rem["scf_algorithm"] = "rca_diis"
+ actions.append({"scf_algorithm": "rca_diis"})
+ if self.qcinp.rem.get("gen_scfman"):
+ self.qcinp.rem["gen_scfman"] = False
+ actions.append({"gen_scfman": False})
+ else:
+ print(
+ "Use a different initial guess? Perhaps a different basis?"
+ )
+
+ elif "linear_dependent_basis" in self.errors:
+ # DIIS -> RCA_DIIS. If already RCA_DIIS, change basis?
+ if self.qcinp.rem.get("scf_algorithm", "diis").lower() == "diis":
+ self.qcinp.rem["scf_algorithm"] = "rca_diis"
+ actions.append({"scf_algorithm": "rca_diis"})
+ if self.qcinp.rem.get("gen_scfman"):
+ self.qcinp.rem["gen_scfman"] = False
+ actions.append({"gen_scfman": False})
+ else:
+ print("Perhaps use a better basis?")
+
+ 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"):
+ self.qcinp.rem["sym_ignore"] = True
+ self.qcinp.rem["symmetry"] = False
+ actions.append({"sym_ignore": True})
+ actions.append({"symmetry": False})
+ else:
+ print("Perhaps increase the threshold?")
+
+ elif "input_file_error" in self.errors:
+ print(
+ "Something is wrong with the input file. Examine error message by hand."
+ )
+ return {"errors": self.errors, "actions": None}
+
+ elif "failed_to_read_input" in self.errors:
+ # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
+ actions.append({"rerun job as-is"})
+
+ elif "IO_error" in self.errors:
+ # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
+ actions.append({"rerun job as-is"})
+
+ elif "unknown_error" in self.errors:
+ print("Examine error message by hand.")
+ return {"errors": self.errors, "actions": None}
+
+ else:
+ # 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(
+ "If you get this message, something has gone terribly wrong!")
+ return {"errors": self.errors, "actions": None}
+
+ os.rename(self.input_file, self.input_file + ".last")
+ self.qcinp.write_file(self.input_file)
+ return {"errors": self.errors, "actions": actions}
+
+
+[docs]class QChemSCFErrorHandler(ErrorHandler):
+ """
+ QChem ErrorHandler class that addresses SCF non-convergence.
+ """
+
+ is_monitor = False
+
+ 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.
+
+ Args:
+ input_file (str): Name of the QChem input file.
+ output_file (str): Name of the QChem output file.
+ rca_gdm_thresh (float): The threshold for the prior scf algorithm.
+ If last deltaE is larger than the threshold try RCA_DIIS
+ first, else, try DIIS_GDM first.
+ scf_max_cycles (int): The max iterations to set to fix SCF failure.
+ """
+ self.input_file = input_file
+ self.output_file = output_file
+ self.scf_max_cycles = scf_max_cycles
+ self.geom_max_cycles = geom_max_cycles
+ self.qcinp = QCInput.from_file(self.input_file)
+ self.outdata = None
+ self.errors = None
+ self.qchem_job = qchem_job
+
+[docs] def check(self):
+ # Checks output file for errors.
+ self.outdata = QCOutput(self.output_file).data
+ self.errors = self.outdata.get("errors")
+ return len(self.errors) > 0
+
+[docs] def correct(self):
+ print("This hasn't been implemented yet!")
+ return {"errors": self.errors, "actions": None}
+
+# coding: utf-8
+
+from __future__ import unicode_literals, division
+import math
+
+# New QChem job module
+
+
+import os
+import shutil
+import copy
+import subprocess
+import numpy as np
+from pymatgen.core import Molecule
+from pymatgen.io.qchem_io.inputs import QCInput
+from pymatgen.io.qchem_io.outputs import QCOutput
+from custodian.custodian import Job
+from pymatgen.analysis.molecule_structure_comparator import MoleculeStructureComparator
+
+__author__ = "Samuel Blau, Brandon Woods, Shyam Dwaraknath"
+__copyright__ = "Copyright 2018, The Materials Project"
+__version__ = "0.1"
+__maintainer__ = "Samuel Blau"
+__email__ = "samblau1@gmail.com"
+__status__ = "Alpha"
+__date__ = "3/20/18"
+__credits__ = "Xiaohui Qu"
+
+
+[docs]class QCJob(Job):
+ """
+ A basic QChem Job.
+ """
+
+ def __init__(self,
+ qchem_command,
+ multimode="openmp",
+ input_file="mol.qin",
+ output_file="mol.qout",
+ max_cores=32,
+ qclog_file="mol.qclog",
+ suffix="",
+ scratch_dir="/dev/shm/qcscratch/",
+ save_scratch=False,
+ save_name="default_save_name"):
+ """
+ Args:
+ qchem_command (str): Command to run QChem.
+ multimode (str): Parallelization scheme, either openmp or mpi.
+ input_file (str): Name of the QChem input file.
+ output_file (str): Name of the QChem output file.
+ max_cores (int): Maximum number of cores to parallelize over.
+ Defaults to 32.
+ qclog_file (str): Name of the file to redirect the standard output
+ to. None means not to record the standard output. Defaults to
+ None.
+ suffix (str): String to append to the file in postprocess.
+ scratch_dir (str): QCSCRATCH directory. Defaults to "/dev/shm/qcscratch/".
+ save_scratch (bool): Whether to save scratch directory contents.
+ Defaults to False.
+ save_name (str): Name of the saved scratch directory. Defaults to
+ to "default_save_name".
+ """
+ self.qchem_command = qchem_command.split(" ")
+ self.multimode = multimode
+ self.input_file = input_file
+ self.output_file = output_file
+ self.max_cores = max_cores
+ self.qclog_file = qclog_file
+ self.suffix = suffix
+ self.scratch_dir = scratch_dir
+ self.save_scratch = save_scratch
+ self.save_name = save_name
+
+ @property
+ def current_command(self):
+ multimode_index = 0
+ if self.save_scratch:
+ command = [
+ "-save", "",
+ str(self.max_cores), self.input_file, self.output_file,
+ self.save_name
+ ]
+ multimode_index = 1
+ else:
+ command = [
+ "", str(self.max_cores), self.input_file, self.output_file
+ ]
+ if self.multimode == 'openmp':
+ command[multimode_index] = "-nt"
+ elif self.multimode == 'mpi':
+ command[multimode_index] = "-np"
+ else:
+ print("ERROR: Multimode should only be set to openmp or mpi")
+ command = self.qchem_command + command
+ return command
+
+[docs] def setup(self):
+ os.putenv("QCSCRATCH", self.scratch_dir)
+ if self.multimode == 'openmp':
+ os.putenv('QCTHREADS', str(self.max_cores))
+ os.putenv('OMP_NUM_THREADS', str(self.max_cores))
+
+[docs] def postprocess(self):
+ if self.save_scratch:
+ shutil.copytree(
+ os.path.join(self.scratch_dir, self.save_name),
+ os.path.join(os.path.dirname(self.input_file), self.save_name))
+ if self.suffix != "":
+ shutil.move(self.input_file, self.input_file + self.suffix)
+ shutil.move(self.output_file, self.output_file + self.suffix)
+ shutil.move(self.qclog_file, self.qclog_file + self.suffix)
+
+[docs] def run(self):
+ """
+ Perform the actual QChem run.
+
+ Returns:
+ (subprocess.Popen) Used for monitoring.
+ """
+ qclog = open(self.qclog_file, 'w')
+ p = subprocess.Popen(self.current_command, stdout=qclog)
+ 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,
+ reversed_direction=False,
+ ignore_connectivity=False,
+ **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
+ perturbe the geometry, optimize, and recalculate frequencies until all are
+ positive, aka a true minima has been found.
+
+ Args:
+ qchem_command (str): Command to run QChem.
+ multimode (str): Parallelization scheme, either openmp or mpi.
+ input_file (str): Name of the QChem input file.
+ output_file (str): Name of the QChem output file
+ max_iterations (int): Number of perturbation -> optimization -> frequency
+ iterations to perform. Defaults to 10.
+ max_molecule_perturb_scale (float): The maximum scaled perturbation that
+ can be applied to the molecule. Defaults to 0.3.
+ reversed_direction (bool): Whether to reverse the direction of the
+ vibrational frequency vectors. Defaults to False.
+ ignore_connectivity (bool): Whether to ignore differences in connectivity
+ introduced by structural perturbation. Defaults to False.
+ **QCJob_kwargs: Passthrough kwargs to QCJob. See
+ :class:`custodian.qchem.new_jobs.QCJob`.
+ """
+
+ min_molecule_perturb_scale = 0.1
+ scale_grid = 10
+ perturb_scale_grid = (
+ max_molecule_perturb_scale - min_molecule_perturb_scale
+ ) / scale_grid
+ msc = MoleculeStructureComparator()
+
+ if not os.path.exists(input_file):
+ 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)
+ orig_freq_rem["job_type"] = "freq"
+
+ 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),
+ **QCJob_kwargs))
+ opt_outdata = QCOutput(output_file + ".opt_" + str(ii)).data
+ freq_QCInput = QCInput(
+ molecule=opt_outdata.get("molecule_from_optimized_geometry"),
+ rem=orig_freq_rem,
+ opt=orig_opt_input.opt,
+ pcm=orig_opt_input.pcm,
+ solvent=orig_opt_input.solvent)
+ 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),
+ **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:
+ print("All frequencies positive!")
+ break
+ else:
+ negative_freq_vecs = outdata.get("frequency_mode_vectors")[0]
+ old_coords = outdata.get("initial_geometry")
+ old_molecule = outdata.get("initial_molecule")
+ structure_successfully_perturbed = False
+
+ for molecule_perturb_scale in np.arange(
+ max_molecule_perturb_scale, min_molecule_perturb_scale,
+ -perturb_scale_grid):
+ new_coords = perturb_coordinates(
+ old_coords=old_coords,
+ negative_freq_vecs=negative_freq_vecs,
+ molecule_perturb_scale=molecule_perturb_scale,
+ reversed_direction=reversed_direction)
+ new_molecule = Molecule(
+ species=outdata.get('species'),
+ coords=new_coords,
+ charge=outdata.get('charge'),
+ spin_multiplicity=outdata.get('multiplicity'))
+ if msc.are_equal(old_molecule, new_molecule) or ignore_connectivity:
+ structure_successfully_perturbed = True
+ break
+ if not structure_successfully_perturbed:
+ raise Exception(
+ "Unable to perturb coordinates to remove negative frequency without changing the bonding structure"
+ )
+
+ new_opt_QCInput = QCInput(
+ molecule=new_molecule,
+ rem=orig_opt_rem,
+ opt=orig_opt_input.opt,
+ pcm=orig_opt_input.pcm,
+ solvent=orig_opt_input.solvent)
+ 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])
+ 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)]
+
logging.info("Backing up run to {}.".format(filename))
with tarfile.open(filename, "w:gz") as tar:
for fname in filenames:
- for f in glob(fname):
import socket
host = host or socket.gethostname()
except:
- pass
# e-density (brmix error)
if err == "brmix" and 'NELECT' in incar:
continue
- self.errors.add(err)
actions.append({"dict": "INCAR",
"action": {"_set": {"SYMPREC": 1e-6}}})
- VaspModder(vi=vi).apply_actions(actions)
for err, msgs in LrfCommutatorHandler.error_msgs.items():
for msg in msgs:
if l.find(msg) != -1:
- self.errors.add(err)
actions.append({"dict": "INCAR",
"action": {"_set": {"LPEAD": True}}})
- VaspModder(vi=vi).apply_actions(actions)
for err, msgs in StdErrHandler.error_msgs.items():
for msg in msgs:
if l.find(msg) != -1:
- self.errors.add(err)
actions.append({"dict": "INCAR",
"action": {"_set": {"KPAR": reduced_kpar}}})
- VaspModder(vi=vi).apply_actions(actions)
# density (brmix error)
if err == "brmix" and 'NELECT' in incar:
continue
- self.errors.add(err)
"action": {
"_file_delete": {'mode': "actual"}}}])
- VaspModder(vi=vi).apply_actions(actions)
return False
else:
curr_drift = outcar.data.get("drift", [])[::-1][:self.to_average]
- curr_drift = np.average([np.linalg.norm(d) for d in curr_drift])
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)
for line in f:
l = line.strip()
if l.find(msg) != -1:
- return True
m += m % 2
actions = [{"dict": "KPOINTS",
"action": {"_set": {"kpoints": [[m] * 3]}}}]
- VaspModder(vi=vi).apply_actions(actions)
if not v.converged:
return True
except:
- pass
if not v.converged_ionic:
actions.append({"dict": "INCAR",
"action": {"_set": {"IBRION": 1}}})
- VaspModder().apply_actions(actions)
if max_force > self.max_force_threshold and v.converged is True:
return True
except:
- pass
{"dict": "INCAR",
"action": {"_set": {"EDIFFG": ediffg * 0.5}}}]
VaspModder(vi=vi).apply_actions(actions)
-
max_dE = max([s['dE'] for s in oszicar.ionic_steps[1:]]) / n
if max_dE > self.dE_threshold:
return True
- except:
actions = [{"dict": "INCAR",
"action": {"_set": {"POTIM": potim * 0.5}}}]
- VaspModder(vi=vi).apply_actions(actions)
[docs] def check(self):
st = os.stat(self.output_filename)
- if time.time() - st.st_mtime > self.timeout:
- return True
+ if time.time() - st.st_mtime > self.timeout:
+ return True
"action": {"_set": {"ALGO": "Normal"}}})
VaspModder(vi=vi).apply_actions(actions)
-
return all([len(e) == nelm
for e in esteps[-(self.nionic_steps + 1):-1]])
except:
- pass
return {"errors": ["Non-converging job"], "actions": actions}
# Unfixable error. Just return None for actions.
- else:
time_left = self.wall_time - total_secs
if time_left < max(time_per_step * 3, self.buffer_time):
return True
-
m = Modder(actions=[FileActions])
for a in actions:
- m.modify(a["action"], a["file"])
run_time = datetime.datetime.now() - self.start_time
total_secs = run_time.seconds + run_time.days * 3600 * 24
if total_secs > self.interval:
- return True
# Reset the clock.
self.start_time = datetime.datetime.now()
-
def __init__(self):
pass
-
- return os.path.exists("chkpt.yaml")
+
[docs] def correct(self):
d = loadfn("chkpt.yaml")
@@ -1289,8 +1289,8 @@ Source code for custodian.vasp.handlers
actions.append({"Checkpoint": name})
- return {"errors": ["Stopped run."],
- "actions": actions}
+ return {"errors": ["Stopped run."],
+ "actions": actions}
if oszicar.final_energy > 0:
return True
except:
- pass
VaspModder(vi=vi).apply_actions(actions)
return {"errors": ["Positive energy"], "actions": actions}
# Unfixable error. Just return None for actions.
- else:
self.modify(a["action"], a["file"])
else:
raise ValueError("Unrecognized format: {}".format(a))
- for f in modified:
import shutil
import math
import logging
-import itertools
import numpy as np
@@ -237,8 +236,8 @@ Source code for custodian.vasp.jobs
actions = self.auto_continue
dumpfn({"actions": actions}, "continue.json")
- if self.settings_override is not None:
- VaspModder().apply_actions(self.settings_override)
+ if self.settings_override is not None:
+ VaspModder().apply_actions(self.settings_override)
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)
# Remove continuation so if a subsequent job is run in
# the same directory, will not restart this job.
- if os.path.exists("continue.json"):
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,
if jobs[1].settings_override:
post_opt_settings = jobs[1].settings_override + post_opt_settings
jobs[1].settings_override = post_opt_settings
-
"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,
with open("EOS.txt", "wt") as f:
f.write("# %s energy\n" % lattice_direction)
- for k in sorted(energies.keys()):
poscar = os.path.join(path, "POSCAR")
shutil.copy(contcar, poscar)
- if self.settings_override is not None:
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)
if os.path.exists(f):
if self.final and self.suffix != "":
shutil.move(f, "{}{}".format(f, self.suffix))
- elif self.suffix != "":
self.contcar_only = contcar_only
self.kwargs = kwargs
-
- pass
+
[docs] def run(self):
if os.path.exists("CONTCAR"):
@@ -864,11 +871,11 @@ Source code for custodian.vasp.jobs
raise RuntimeError("No CONTCAR/POSCAR detected to generate input!")
modname, classname = self.input_set.rsplit(".", 1)
mod = __import__(modname, globals(), locals(), [classname], 0)
- vis = getattr(mod, classname)(structure, **self.kwargs)
- vis.write_input(".")
+ vis = getattr(mod, classname)(structure, **self.kwargs)
+ vis.write_input(".")
-
try:
Vasprun("vasprun.xml")
except:
- return True
[docs] def check(self):
for vfile in ["CONTCAR", "OSZICAR", "OUTCAR"]:
if not os.path.exists(vfile):
- return True
outcar.read_pattern(patterns=patterns)
if outcar.data["MDALGO"] == [['3']]:
return False
- else:
add_to_set
(settings)[source]¶add_to_set
(input_dict, settings)[source]¶
inc
(settings)[source]¶inc
(input_dict, settings)[source]¶
pop
(settings)[source]¶pop
(input_dict, settings)[source]¶
pull
(settings)[source]¶pull
(input_dict, settings)[source]¶
pull_all
(settings)[source]¶pull_all
(input_dict, settings)[source]¶
push
(settings)[source]¶push
(input_dict, settings)[source]¶
push_all
(settings)[source]¶push_all
(input_dict, settings)[source]¶
rename
(settings)[source]¶rename
(input_dict, settings)[source]¶
set
(settings)[source]¶set
(input_dict, settings)[source]¶
unset
(settings)[source]¶unset
(input_dict, settings)[source]¶
file_copy
(settings)[source]¶file_copy
(filename, settings)[source]¶
Copies a file. {‘_file_copy’: {‘dest’: ‘new_file_name’}}
+ |
QRSS |
|
+
NavigationNavigationcustodianModule contents +NavigationPython Module Index |
custodian.qchem.jobs | |
+ |
+ custodian.qchem.new_handlers | + |
+ |
+ custodian.qchem.new_jobs | + |
diff --git a/docs/search.html b/docs/search.html
index 5129d4b8..f0e8a0eb 100644
--- a/docs/search.html
+++ b/docs/search.html
@@ -6,10 +6,10 @@
- Navigation |