diff --git a/casm/casm/vasp/io/attribute_classes.py b/casm/casm/vasp/io/attribute_classes.py index a31f250..c6118bd 100644 --- a/casm/casm/vasp/io/attribute_classes.py +++ b/casm/casm/vasp/io/attribute_classes.py @@ -1,29 +1,167 @@ -class DofClassError(Exception): - """Exception handling""" - def __init__(self, message): - self.message = message +import numpy as np - def __str__(self): - """ Writes out the error message + +def get_incar_magmom_from_magmom_values(magmom_values): + """Returns an INCAR magmom string from a + list of magmom values. The magmom values should + be listed in the order of atoms in POSCAR + Parameters + ---------- + magmom_values : np.ndarray + Returns + ------- + str + """ + + magmom = "" + for i, value in enumerate(magmom_values): + if i == 0: + number_of_same_magmoms = 1 + elif np.isclose(value, magmom_values[i - 1]): + number_of_same_magmoms += 1 + else: + magmom += ( + str(number_of_same_magmoms) + "*" + str(magmom_values[i - 1]) + " " + ) + number_of_same_magmoms = 1 + if i == len(magmom_values) - 1: + magmom += str(number_of_same_magmoms) + "*" + str(magmom_values[i]) + " " + + return magmom + + +class NCunitmagspinAttr: + """Class containing information specific to Cmagspin dof. + This object will be constructed from casm.project.structure.StructureInfo class + which digests its information. + + self.atom_props: List[Dict] - Contains the list of atom properites (with atom name and it's value) + Look at the example below for detailed description of object properties + + Consider the example of NaFeO2 with Fe having a +5 magnetic moment and rest all being 0 + self.atom_props: + [ + {"site_index":0, "atom": "Na", "value":0}, + {"site_index":1, "atom":"Fe", "value":5}, + {"site_index":2, "atom":"O","value":0}, + {"site_index":3, "atom":"O","value":0} + ] + """ + + def __init__(self, structure_info): + """Constructs the CmagspinAttr object from StructureInfo object + + Parameters + ---------- + structure_info : casm.project.structure.StructureInfo + + """ + if "NCunitmagspin" not in list(structure_info.atom_properties.keys()): + raise RuntimeError( + "Could not construct NCunitmagspinAttr class. " + "Check if you're dealing with Cmagspin dof calculations." + ) + + self.atom_props = [ + {"site_index": site_index, "atom": atom_type, "value": magmom_value} + for site_index, (atom_type, magmom_value) in enumerate( + zip( + structure_info.atom_type, + structure_info.atom_properties["NCunitmagspin"]["value"], + ) + ) + ] + + def vasp_input_tags(self, sort=True): + """Returns a dictionary of MAGMOM, ISPIN input tags + specific to collinear magnetic VASP calculations. + + Parameters + ---------- + sort: bool, optional + This should match the sort used to write + POSCAR file (whether the basis atoms are sorted)) Returns ------- - string + dict + { + "MAGMOM": magmom_string, + "ISPIN": 2 + } """ - return self.message + if sort is True: + self.atom_props.sort(key=lambda x: x["atom"]) + magmom_values = np.ravel( + np.array([atom_prop["value"] for atom_prop in self.atom_props]) + ) -class CmagspinAttr: + return dict( + MAGMOM=get_incar_magmom_from_magmom_values(magmom_values), + ISPIN=2, + LSORBIT=False, + LNONCOLLINEAR=True, + ) + + @staticmethod + def vasp_output_dictionary(outcar, unsort_dict): + """Returns the attribute specific vasp output + dictionary which can be updated to the whole output + dictionary which will be printed as properties.calc.json. + For Cmagspin, this will be magnetic moment of each individual species + + Parameters + ---------- + outcar : casm.vasp.io.outcar + Outcar containing magmom information + unsort_dict : dict + ``Poscar.unsort_dict()`` useful for reordering + the magmom values + + Returns + ------- + dict + { + "Cmagspin":{ + "value":[] + } + } + + """ + output = {} + # output["Cmagspin"] = {} + # output["Cmagspin"]["value"] = [None] * len(unsort_dict) + # output["Cunitmagspin"] = {} + # output["Cunitmagspin"]["value"] = [None] * len(unsort_dict) + # for i in range(len(outcar.mag)): + # output["Cmagspin"]["value"][unsort_dict[i]] = [outcar.mag[i]] + # output["Cunitmagspin"]["value"][unsort_dict[i]] = [ + # outcar.mag[i] / abs(outcar.mag[i]) + # ] + + raise NotImplementedError("Not implemented this part yet") + + +class SOunitmagspinAttr: """Class containing information specific to Cmagspin dof. - This object will be constructed from casm.project.structure.StructureInfo class - which digests its information. + This object will be constructed from casm.project.structure.StructureInfo class + which digests its information. + + self.atom_props: List[Dict] - Contains the list of atom properites (with atom name and it's value) + Look at the example below for detailed description of object properties - self.atom_props: List[Dict] - Contains the list of atom properites (with atom name and it's value) - Look at the example below for detailed description of object properties + Consider the example of NaFeO2 with Fe having a +5 magnetic moment and rest all being 0 + self.atom_props: + [ + {"site_index":0, "atom": "Na", "value":0}, + {"site_index":1, "atom":"Fe", "value":5}, + {"site_index":2, "atom":"O","value":0}, + {"site_index":3, "atom":"O","value":0} + ] + """ - Consider the example of NaFeO2 with Fe having a +5 magnetic moment and rest all being 0 - self.atom_props: [{"site_index":0, "atom": "Na", "value":0},{"site_index":1,"atom":"Fe", "value":5},{"site_index":2, "atom":"O","value":0},{"site_index":3, "atom":"O","value":0}]""" def __init__(self, structure_info): """Constructs the CmagspinAttr object from StructureInfo object @@ -32,75 +170,304 @@ def __init__(self, structure_info): structure_info : casm.project.structure.StructureInfo """ - try: - self.atom_props = [{ - "site_index": - x, - "atom": - structure_info.atom_type[x], - "value": - structure_info.atom_properties["Cmagspin"]["value"][x] - } for x in range(0, len(structure_info.atom_type))] - except: - raise DofClassError( - "Could not construct CmagspinAttr class!! Check if you're dealing with Cmagspin dof calculations" + if "SOunitmagspin" not in list(structure_info.atom_properties.keys()): + raise RuntimeError( + "Could not construct SOunitmagspinAttr class. " + "Check if you're dealing with Cmagspin dof calculations." + ) + + self.atom_props = [ + {"site_index": site_index, "atom": atom_type, "value": magmom_value} + for site_index, (atom_type, magmom_value) in enumerate( + zip( + structure_info.atom_type, + structure_info.atom_properties["SOunitmagspin"]["value"], + ) ) + ] def vasp_input_tags(self, sort=True): - """Returns a dictionary of VASP input tags specific to collinear magnetic spin calculations. - The collinear magnetic spin specific tags are as follows: + """Returns a dictionary of MAGMOM, ISPIN input tags + specific to collinear magnetic VASP calculations. - MAGMOM, ISPIN + Parameters + ---------- + sort: bool, optional + This should match the sort used to write + POSCAR file (whether the basis atoms are sorted)) + + Returns + ------- + dict + { + "MAGMOM": magmom_string, + "ISPIN": 2 + } + + """ + if sort is True: + self.atom_props.sort(key=lambda x: x["atom"]) + + magmom_values = np.ravel( + np.array([atom_prop["value"] for atom_prop in self.atom_props]) + ) + + return dict( + MAGMOM=get_incar_magmom_from_magmom_values(magmom_values), + ISPIN=2, + LSORBIT=True, + LNONCOLLINEAR=True, + ) + + @staticmethod + def vasp_output_dictionary(outcar, unsort_dict): + """Returns the attribute specific vasp output + dictionary which can be updated to the whole output + dictionary which will be printed as properties.calc.json. + For Cmagspin, this will be magnetic moment of each individual species Parameters ---------- - sort: bool (This should match the sort used to write POSCAR file (whether the basis atoms are sorted)) + outcar : casm.vasp.io.outcar + Outcar containing magmom information + unsort_dict : dict + ``Poscar.unsort_dict()`` useful for reordering + the magmom values Returns ------- - dict{"vasp_input_tag": "value"} + dict + { + "Cmagspin":{ + "value":[] + } + } + + """ + output = {} + # output["Cmagspin"] = {} + # output["Cmagspin"]["value"] = [None] * len(unsort_dict) + # output["Cunitmagspin"] = {} + # output["Cunitmagspin"]["value"] = [None] * len(unsort_dict) + # for i in range(len(outcar.mag)): + # output["Cmagspin"]["value"][unsort_dict[i]] = [outcar.mag[i]] + # output["Cunitmagspin"]["value"][unsort_dict[i]] = [ + # outcar.mag[i] / abs(outcar.mag[i]) + # ] + + raise NotImplementedError("Not implemented this part yet") + + +class CunitmagspinAttr: + """Class containing information specific to Cmagspin dof. + This object will be constructed from casm.project.structure.StructureInfo class + which digests its information. + + self.atom_props: List[Dict] - Contains the list of atom properites (with atom name and it's value) + Look at the example below for detailed description of object properties + + Consider the example of NaFeO2 with Fe having a +5 magnetic moment and rest all being 0 + self.atom_props: + [ + {"site_index":0, "atom": "Na", "value":0}, + {"site_index":1, "atom":"Fe", "value":5}, + {"site_index":2, "atom":"O","value":0}, + {"site_index":3, "atom":"O","value":0} + ] + """ + + def __init__(self, structure_info): + """Constructs the CmagspinAttr object from StructureInfo object + + Parameters + ---------- + structure_info : casm.project.structure.StructureInfo """ - #TODO: Group together atoms of same MAGMOM together - #TODO: Also add ISPIN default tag which is required if missed in INCAR.base + if "Cunitmagspin" not in list(structure_info.atom_properties.keys()): + raise RuntimeError( + "Could not construct CunitmagspinAttr class. " + "Check if you're dealing with Cmagspin dof calculations." + ) + + self.atom_props = [ + {"site_index": site_index, "atom": atom_type, "value": magmom_value} + for site_index, (atom_type, magmom_value) in enumerate( + zip( + structure_info.atom_type, + structure_info.atom_properties["Cunitmagspin"]["value"], + ) + ) + ] + + def vasp_input_tags(self, sort=True): + """Returns a dictionary of MAGMOM, ISPIN input tags + specific to collinear magnetic VASP calculations. + + Parameters + ---------- + sort: bool, optional + This should match the sort used to write + POSCAR file (whether the basis atoms are sorted)) + + Returns + ------- + dict + { + "MAGMOM": magmom_string, + "ISPIN": 2 + } + """ if sort is True: self.atom_props.sort(key=lambda x: x["atom"]) - magmom_value = "" - for atom_props in self.atom_props: - magmom_value = magmom_value + str(atom_props["value"][0]) + " " - - tags = dict() - tags["MAGMOM"] = magmom_value - return tags + magmom_values = np.ravel( + np.array([atom_prop["value"] for atom_prop in self.atom_props]) + ) - def vasp_output_dictionary(self, outcar, sort=True): - """Returns the attribute specific vasp output dictionary which can be updated - to the whole output dictionary which will be printed as properties.calc.json. + return dict(MAGMOM=get_incar_magmom_from_magmom_values(magmom_values), ISPIN=2) + @staticmethod + def vasp_output_dictionary(outcar, unsort_dict): + """Returns the attribute specific vasp output + dictionary which can be updated to the whole output + dictionary which will be printed as properties.calc.json. For Cmagspin, this will be magnetic moment of each individual species Parameters ---------- - outcar : casm.vasp.io.outcar (Class containing information about magmom of individual species from OUTCAR) - sort : bool (This should be the same sort used while writing POSCAR) + outcar : casm.vasp.io.outcar + Outcar containing magmom information + unsort_dict : dict + ``Poscar.unsort_dict()`` useful for reordering + the magmom values Returns ------- - dict{"Cmagspin":{"value":[list]}} + dict + { + "Cmagspin":{ + "value":[] + } + } + + """ + output = {} + output["Cmagspin"] = {} + output["Cmagspin"]["value"] = [None] * len(unsort_dict) + output["Cunitmagspin"] = {} + output["Cunitmagspin"]["value"] = [None] * len(unsort_dict) + for i in range(len(outcar.mag)): + output["Cmagspin"]["value"][unsort_dict[i]] = [outcar.mag[i]] + if abs(outcar.mag[i]) < 0.5: + output["Cunitmagspin"]["value"][unsort_dict[i]] = [0.0] + else: + output["Cunitmagspin"]["value"][unsort_dict[i]] = [ + outcar.mag[i] / abs(outcar.mag[i]) + ] + + return output + + +class CmagspinAttr: + """Class containing information specific to Cmagspin dof. + This object will be constructed from casm.project.structure.StructureInfo class + which digests its information. + + self.atom_props: List[Dict] - Contains the list of atom properites (with atom name and it's value) + Look at the example below for detailed description of object properties + + Consider the example of NaFeO2 with Fe having a +5 magnetic moment and rest all being 0 + self.atom_props: + [ + {"site_index":0, "atom": "Na", "value":0}, + {"site_index":1, "atom":"Fe", "value":5}, + {"site_index":2, "atom":"O","value":0}, + {"site_index":3, "atom":"O","value":0} + ] + """ + + def __init__(self, structure_info): + """Constructs the CmagspinAttr object from StructureInfo object + + Parameters + ---------- + structure_info : casm.project.structure.StructureInfo + + """ + if "Cmagspin" not in list(structure_info.atom_properties.keys()): + raise RuntimeError( + "Could not construct CmagspinAttr class. " + "Check if you're dealing with Cmagspin dof calculations." + ) + self.atom_props = [ + {"site_index": site_index, "atom": atom_type, "value": magmom_value} + for site_index, (atom_type, magmom_value) in enumerate( + zip( + structure_info.atom_type, + structure_info.atom_properties["Cmagspin"]["value"], + ) + ) + ] + + def vasp_input_tags(self, sort=True): + """Returns a dictionary of MAGMOM, ISPIN input tags + specific to collinear magnetic VASP calculations. + + Parameters + ---------- + sort: bool, optional + This should match the sort used to write + POSCAR file (whether the basis atoms are sorted)) + + Returns + ------- + dict + { + "MAGMOM": magmom_string, + "ISPIN": 2 + } """ if sort is True: self.atom_props.sort(key=lambda x: x["atom"]) - permutation_vector_to_unsort = [ - x["site_index"] for x in self.atom_props - ] + magmom_values = np.ravel( + np.array([atom_prop["value"] for atom_prop in self.atom_props]) + ) + + return dict(MAGMOM=get_incar_magmom_from_magmom_values(magmom_values), ISPIN=2) + + @staticmethod + def vasp_output_dictionary(outcar, unsort_dict): + """Returns the attribute specific vasp output + dictionary which can be updated to the whole output + dictionary which will be printed as properties.calc.json. + For Cmagspin, this will be magnetic moment of each individual species + + Parameters + ---------- + outcar : casm.vasp.io.outcar + Outcar containing magmom information + unsort_dict : dict + ``Poscar.unsort_dict()`` useful for reordering + the magmom values + Returns + ------- + dict + { + "Cmagspin":{ + "value":[] + } + } + + """ output = {} output["Cmagspin"] = {} - output["Cmagspin"]["value"] = [[mag] for site_index, mag in sorted( - zip(permutation_vector_to_unsort, outcar.mag))] + output["Cmagspin"]["value"] = [None] * len(unsort_dict) + for i in range(len(outcar.mag)): + output["Cmagspin"]["value"][unsort_dict[i]] = [outcar.mag[i]] return output diff --git a/casm/casm/vasp/io/incar.py b/casm/casm/vasp/io/incar.py index 5e3bab3..aef78eb 100644 --- a/casm/casm/vasp/io/incar.py +++ b/casm/casm/vasp/io/incar.py @@ -22,7 +22,7 @@ VASP_TAG_BOOL_LIST = [ 'lcharg', 'lsorbit', 'lwave', 'lscalapack', 'lscalu', 'lplane', 'lhfcalc', 'shiftred', 'evenonly', 'oddonly', 'addgrid', 'ldau', 'lasph', 'lclimb', - 'ldneb', 'lnebcell', 'ltangentold' + 'ldneb', 'lnebcell', 'ltangentold', 'lnoncollinear' ] # Site-wise list of arrays of FLOAT VASP_TAG_SITEF_LIST = ['magmom', 'rwigs'] @@ -42,6 +42,7 @@ class IncarError(Exception): + def __init__(self, msg): self.msg = msg @@ -56,6 +57,7 @@ class Incar(object): All input tags and associated values are stored as key-value pairs in the dicionary called 'tags'. """ + def __init__(self, filename, species=None, @@ -183,6 +185,20 @@ def update(self, species, poscar, sort=True, structure_info=None): vasp_input_tags_to_append = attribute_classes.CmagspinAttr( structure_info).vasp_input_tags() self.tags.update(vasp_input_tags_to_append) + if "Cunitmagspin" in list(structure_info.atom_properties.keys()): + vasp_input_tags_to_append = attribute_classes.CunitmagspinAttr( + structure_info).vasp_input_tags() + self.tags.update(vasp_input_tags_to_append) + + if "NCunitmagspin" in list(structure_info.atom_properties.keys()): + vasp_input_tags_to_append = attribute_classes.NCunitmagspinAttr( + structure_info).vasp_input_tags() + self.tags.update(vasp_input_tags_to_append) + + if "SOunitmagspin" in list(structure_info.atom_properties.keys()): + vasp_input_tags_to_append = attribute_classes.SOunitmagspinAttr( + structure_info).vasp_input_tags() + self.tags.update(vasp_input_tags_to_append) if sort == False: # for each 'tag' in the IndividualSpecies, create a list in self.tags diff --git a/casm/casm/vaspwrapper/vasp_calculator_base.py b/casm/casm/vaspwrapper/vasp_calculator_base.py index b2e8699..8b66172 100644 --- a/casm/casm/vaspwrapper/vasp_calculator_base.py +++ b/casm/casm/vaspwrapper/vasp_calculator_base.py @@ -28,6 +28,7 @@ def error_job(message): print(str(e)) sys.stdout.flush() + def complete_job(jobid=None): """Complete job by given ID, or detect ID from environment""" import prisms_jobs as jobs @@ -38,6 +39,7 @@ def complete_job(jobid=None): print(str(e)) sys.stdout.flush() + class VaspCalculatorBase(object): """ Base class containing all the basic functions that method classes can inherit @@ -52,6 +54,7 @@ class VaspCalculatorBase(object): sort : bool """ + def __init__(self, selection, calctype=None, auto=True, sort=True): """set up attributes for the base class""" self.selection = selection @@ -411,18 +414,18 @@ def submit(self): sys.stdout.flush() # construct a Job job = jobs.Job(name=jobname(config_data["name"]), - account=settings["account"], - nodes=nodes, - ppn=ppn, - walltime=settings["walltime"], - pmem=settings["pmem"], - qos=settings["qos"], - queue=settings["queue"], - message=settings["message"], - email=settings["email"], - priority=settings["priority"], - command=cmd, - auto=self.auto) + account=settings["account"], + nodes=nodes, + ppn=ppn, + walltime=settings["walltime"], + pmem=settings["pmem"], + qos=settings["qos"], + queue=settings["queue"], + message=settings["message"], + email=settings["email"], + priority=settings["priority"], + command=cmd, + auto=self.auto) print("Submitting") sys.stdout.flush() @@ -698,7 +701,7 @@ def properties(vaspdir, initial_structurefile=None, speciesfile=None): # 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["atoms_per_type"] = initial_structure.num_atoms + # output["atoms_per_type"] = initial_structure.num_atoms output["coordinate_mode"] = contcar.coordinate_mode # as lists @@ -723,47 +726,25 @@ def properties(vaspdir, initial_structurefile=None, speciesfile=None): output["global_properties"]["energy"] = {} output["global_properties"]["energy"]["value"] = zcar.E[-1] + if ocar.ispin == 2: + output["global_properties"]["Cmagspin"] = {} + output["global_properties"]["Cmagspin"]["value"] = zcar.mag[-1] + if ocar.lorbit in [1, 2, 11, 12]: + cmagspin_specific_output = attribute_classes.CmagspinAttr.vasp_output_dictionary( + ocar, unsort_dict) + output["atom_properties"].update(cmagspin_specific_output) + if structure_info.atom_properties is not None: + # if you have cmagspin sitedofs 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) + cmagspin_specific_output = attribute_classes.CmagspinAttr.vasp_output_dictionary( + ocar, unsort_dict) 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 "Cunitmagspin" in list(structure_info.atom_properties.keys()): + cunitmagspin_specific_output = attribute_classes.CunitmagspinAttr.vasp_output_dictionary( + ocar, unsort_dict) + output["atom_properties"].update(cunitmagspin_specific_output) - #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. - else: - if ocar.ispin == 2: - 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])] return output