diff --git a/README.md b/README.md index 5b8f3dc..537319e 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,8 @@ The CASM Python packages The `casm-python` Python packages provide a Python interface to the CASM libraries, implement wrappers to fitting methods and DFT software, and provide other tools for plotting and analysis. -This version of `casm-python` is compatible with [CASM](https://prisms-center.github.io/CASMcode_docs/) >=1.2. - -A basic online reference is located [here](https://prisms-center.github.io/CASMcode_pydocs/latest/index.html) +This version of `casm-python` is compatible with [CASM](https://prisms-center.github.io/CASMcode_docs/) >=1.2. +A basic online reference is located [here](https://prisms-center.github.io/CASMcode_pydocs/casm/overview/latest) For a summary of changes, see [CHANGELOG.md](https://github.com/prisms-center/CASMpython/blob/1.X/CHANGELOG.md). diff --git a/casm/casm/scripts/vasp_relax_report.py b/casm/casm/scripts/vasp_relax_report.py index 5bf655a..5f1b17a 100755 --- a/casm/casm/scripts/vasp_relax_report.py +++ b/casm/casm/scripts/vasp_relax_report.py @@ -5,6 +5,8 @@ import json import sys +import six + from casm.misc import compat, noindent import casm.vaspwrapper @@ -26,13 +28,12 @@ def main(): % configdir)) raise - compat.dump(json, - output, - 'properties.calc.json', - 'w', - cls=noindent.NoIndentEncoder, - indent=4, - sort_keys=True) + with open('properties.calc.json', 'w') as f: + f.write(six.u(json.dumps( + output, + cls=noindent.NoIndentEncoder, + indent=4, + sort_keys=True))) print("Finish vasp.relax.report\n\n") diff --git a/casm/casm/vasp/error.py b/casm/casm/vasp/error.py index 6795d91..ae8a579 100644 --- a/casm/casm/vasp/error.py +++ b/casm/casm/vasp/error.py @@ -724,32 +724,34 @@ def fix(err_jobdir, new_jobdir, settings): def error_check(jobdir, stdoutfile, err_types): """ Check vasp stdout for errors """ + if err_types is None: return None err = dict() err_objs = {} + check_once = ['FreezeError'] for i_err in _RunError.__subclasses__(): err_objs[i_err.__name__] = i_err() - if err_types is None: - possible = [SubSpaceMatrixError()] - else: - # err_objs = {'IbzkptError' : IbzkptError(), 'SubSpaceMatrixError' : SubSpaceMatrixError(), 'NbandsError' : NbandsError()} - for s in err_types: - if s not in err_objs.keys(): - raise VaspError('Invalid err_type: %s' % s) - possible = [err_objs[s] for s in err_types] + for i_err in _FreezeError.__subclasses__(): + err_objs[i_err.__name__] = i_err() + # err_objs = {'IbzkptError' : IbzkptError(), 'SubSpaceMatrixError' : SubSpaceMatrixError(), + # 'NbandsError' : NbandsError(), 'FreezeError' : FreezeError()} + for s in err_types: + if s not in err_objs.keys(): + raise VaspError('Invalid err_type: %s' % s) + possible = [err_objs[s] for s in err_types] # Error to check line by line, only look for first of each type sout = open(stdoutfile, 'r') for line in sout: for p in possible: - if not p.__class__.__name__ in err: + if not p.__class__.__name__ in err and not p.__class__.__name__ in check_once: if p.error(line=line, jobdir=jobdir): err[p.__class__.__name__] = p # Error to check for once - possible = [i_err() for i_err in _FreezeError.__subclasses__()] for p in possible: - if p.error(line=None, jobdir=jobdir): - err[p.__class__.__name__] = p + if not p.__class__.__name__ in err and p.__class__.__name__ in check_once: + if p.error(line=None, jobdir=jobdir): + err[p.__class__.__name__] = p sout.close() if len(err) == 0: diff --git a/casm/casm/vasp/relax.py b/casm/casm/vasp/relax.py index 734a5ce..12c2919 100644 --- a/casm/casm/vasp/relax.py +++ b/casm/casm/vasp/relax.py @@ -195,12 +195,17 @@ def complete(self): def converged(self): """Check if configuration is relaxed. - This is called when self.rundir[-1] is complete and not a constant volume job. + This is called when self.rundir[-1] is complete. Convergence criteria is: at least 2 relaxation jobs are complete, and: 1) the last job completed with <= 3 ionic steps or 2) the last two jobs had final E0 differ by less than self.settings["nrg_convergence"] + OR + at least 1 relaxation job is complete, and + the last job completed with 1 ionic step, and + the volume is fixed (ISIF = 0, 1, 2) + (ISIF must be set explicitly in the INCAR) """ if len(self.rundir) >= 2: if io.ionic_steps(self.rundir[-1]) <= 3: @@ -213,13 +218,18 @@ def converged(self): if abs(o1.E[-1] - o2.E[-1]) < self.settings["nrg_convergence"]: return True + # note: this will not work if ISIF was not explicitly set in the INCAR + elif len(self.rundir) == 1: + if io.ionic_steps(self.rundir[-1]) == 1: + if io.get_incar_tag("ISIF", self.rundir[-1]) in [0, 1, 2]: + return True return False def not_converging(self): """Check if configuration is not converging. - This is called when self.rundir[-1] is complete and not a constant volume job and self.converged() == False. + This is called when self.rundir[-1] is complete and self.converged() == False. Not converging criteria: >= 10 runs without completion """ @@ -416,16 +426,6 @@ def status(self): # io.get_incar_tag("ISMEAR", self.rundir[-1]) == -5: return ("complete", None) - # elif constant volume run (but not the final one) - if io.get_incar_tag("ISIF", self.rundir[-1]) in [0, 1, 2]: - if io.get_incar_tag("NSW", self.rundir[-1]) == len( - io.Oszicar(os.path.join(self.rundir[-1], - "OSZICAR")).E): - return ("incomplete", "relax" - ) # static run hit NSW limit and so isn't "done" - else: - return ("incomplete", "constant") - # elif convergence criteria met if self.converged(): return ("incomplete", "constant") diff --git a/casm/casm/vaspwrapper/relax.py b/casm/casm/vaspwrapper/relax.py index 4330dfb..bf89a5d 100644 --- a/casm/casm/vaspwrapper/relax.py +++ b/casm/casm/vaspwrapper/relax.py @@ -136,8 +136,9 @@ def finalize(self, config_data): super(Relax, self).finalize(config_data) sys.stdout.flush() - def properties(self, calcdir, super_poscarfile=None, speciesfile=None): + @staticmethod + def properties(calcdir, super_poscarfile=None, speciesfile=None): """Make properties output as a list of dict of each image properties""" - output = super(Relax, self).properties(calcdir, super_poscarfile, - speciesfile) + output = super(Relax, Relax).properties( + calcdir, super_poscarfile, speciesfile) return output diff --git a/casm/casm/vaspwrapper/vasp_calculator_base.py b/casm/casm/vaspwrapper/vasp_calculator_base.py index b2e8699..26d7856 100644 --- a/casm/casm/vaspwrapper/vasp_calculator_base.py +++ b/casm/casm/vaspwrapper/vasp_calculator_base.py @@ -421,6 +421,9 @@ def submit(self): message=settings["message"], email=settings["email"], priority=settings["priority"], + constraint=settings["constraint"], + exclude=settings["exclude"], + gpus=settings["gpus"], command=cmd, auto=self.auto) @@ -671,7 +674,6 @@ def finalize(self, config_data): @staticmethod def properties(vaspdir, initial_structurefile=None, speciesfile=None): """ return a dict of output form a vasp directory""" - structure_info = structure.StructureInfo(initial_structurefile) output = dict() # load the OSZICAR and OUTCAR zcar = vasp.io.Oszicar(os.path.join(vaspdir, "OSZICAR")) @@ -697,7 +699,7 @@ def properties(vaspdir, initial_structurefile=None, speciesfile=None): # unsorted_dict[sorted_index] == orig_index # For example: # 'unsort_dict[0]' returns the index into the unsorted POSCAR of the first atom in the sorted POSCAR - output["atom_type"] = initial_structure.atom_type + output["atom_type"] = [i.occ_alias for i in initial_structure.basis] #output["atoms_per_type"] = initial_structure.num_atoms output["coordinate_mode"] = contcar.coordinate_mode @@ -723,32 +725,34 @@ def properties(vaspdir, initial_structurefile=None, speciesfile=None): output["global_properties"]["energy"] = {} output["global_properties"]["energy"]["value"] = zcar.E[-1] - if structure_info.atom_properties is not None: - if "Cmagspin" in list(structure_info.atom_properties.keys()): - output["global_properties"]["Cmagspin"] = {} - cmagspin_specific_output = attribute_classes.CmagspinAttr( - structure_info).vasp_output_dictionary(ocar) - output["atom_properties"].update(cmagspin_specific_output) - #TODO: Need a better way to write global magmom. I don't like what I did here - output["global_properties"]["Cmagspin"]["value"] = zcar.mag[-1] - - #TODO: When you don't have Cmagspin but have magnetic calculations. This part can be removed if you runall magnetic calculations as Cmagspin calculations. - #TODO: Need a better way of doing this. Some code duplication here. - else: - if ocar.ispin == 2: + if initial_structurefile is not None: + structure_info = structure.StructureInfo(initial_structurefile) + if structure_info.atom_properties is not None: + if "Cmagspin" in list(structure_info.atom_properties.keys()): output["global_properties"]["Cmagspin"] = {} + cmagspin_specific_output = attribute_classes.CmagspinAttr( + structure_info).vasp_output_dictionary(ocar) + output["atom_properties"].update(cmagspin_specific_output) + #TODO: Need a better way to write global magmom. I don't like what I did here output["global_properties"]["Cmagspin"]["value"] = zcar.mag[-1] - if ocar.lorbit in [1, 2, 11, 12]: - output["atom_properties"]["Cmagspin"] = {} - output["atom_properties"]["Cmagspin"]["value"] = [ - None for i in range(len(contcar.basis)) - ] - - for i, v in enumerate(contcar.basis): - output["atom_properties"]["Cmagspin"]["value"][ - unsort_dict[i]] = [ - noindent.NoIndent(ocar.mag[i]) - ] + + #TODO: When you don't have Cmagspin but have magnetic calculations. This part can be removed if you runall magnetic calculations as Cmagspin calculations. + #TODO: Need a better way of doing this. Some code duplication here. + else: + if ocar.ispin == 2: + output["global_properties"]["Cmagspin"] = {} + output["global_properties"]["Cmagspin"]["value"] = zcar.mag[-1] + if ocar.lorbit in [1, 2, 11, 12]: + output["atom_properties"]["Cmagspin"] = {} + output["atom_properties"]["Cmagspin"]["value"] = [ + None for i in range(len(contcar.basis)) + ] + + for i, v in enumerate(contcar.basis): + output["atom_properties"]["Cmagspin"]["value"][ + unsort_dict[i]] = [ + noindent.NoIndent(ocar.mag[i]) + ] #TODO: Code duplication here. If you have a magnetic calculation without dofs, you still need to write magmom values. This can be removed if you run all the magnetic calculations as Cmagspin dof calculations. #TODO: If you still want to have this particular functionality, wrap it up in a helper function to avoid code duplication. diff --git a/casm/casm/vaspwrapper/vaspwrapper.py b/casm/casm/vaspwrapper/vaspwrapper.py index 41ecca2..1bb1019 100644 --- a/casm/casm/vaspwrapper/vaspwrapper.py +++ b/casm/casm/vaspwrapper/vaspwrapper.py @@ -36,6 +36,8 @@ def read_settings(filename): "pmem": string for requested memory (default None) "priority": requested job priority (default "0") "constraint": constraint. ex: ``"haswell"`` (default None) + "exclude": nodes to exclude (slurm only). ex: ``"node01,node02,node03"`` (default None) + "gpus": how many gpus to request (slurm only). ex: 4 (default None) "message": when to send messages about jobs (ex. "abe", default "a") "email": where to send messages (ex. "me@fake.com", default None) "qos": quality of service, 'qos' option (ex. "fluxoe") @@ -78,7 +80,7 @@ def read_settings(filename): select_one = [["nodes", "atom_per_proc", "nodes_per_image"]] optional = [ - "account", "pmem", "priority", "constraint", "message", "email", "qos", + "account", "pmem", "priority", "constraint", "exclude", "gpus", "message", "email", "qos", "npar", "ncore", "kpar", "ncpus", "vasp_cmd", "run_limit", "nrg_convergence", "encut", "kpoints", "extra_input_files", "move", "copy", "remove", "compress", "backup", "initial", "final", @@ -88,6 +90,11 @@ def read_settings(filename): "endstate_calctype", "initial_deformation" ] + if "gpus" in settings: + # user must supply their own run command + if not "vasp_cmd" in settings or len(settings["vasp_cmd"]) == 0: + raise VaspWrapperError("gpu setting requires you to set vasp_cmd manually") + for key in required: if not key in settings: raise VaspWrapperError(key + "' missing from: '" + filename + "'") diff --git a/casm/requirements.txt b/casm/requirements.txt index 65c7c63..7d4d18d 100644 --- a/casm/requirements.txt +++ b/casm/requirements.txt @@ -4,5 +4,5 @@ pandas prisms-jobs scikit-learn scipy -sh +sh==1.14.2 six diff --git a/casm/setup.py b/casm/setup.py index fe4fa23..7ab866f 100644 --- a/casm/setup.py +++ b/casm/setup.py @@ -36,7 +36,7 @@ def script_str(file): entry_points={'console_scripts': console_scripts}, install_requires=[ 'deap', 'mock', 'pandas', 'prisms-jobs', 'scikit-learn', - 'scipy', 'sh' + 'scipy', 'sh==1.14.2' ], classifiers=[ 'Development Status :: 5 - Production/Stable',