def test_is_collinear(self): magmoms_list = [[0, 0, 0], [1, 1, 1], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 1], [0, 0, 1], [0, 0, 1]], [[0, 0, -1], [0, 0, 1], [0, 0, 1]], [[2, 2, 2], [-2, -2, -2], [2, 2, 2]]] for magmoms in magmoms_list: self.assertEqual(Magmom.are_collinear(magmoms), True) ncl_magmoms = [[[0, 0, 1], [0, 0, 1], [1, 2, 3]]] self.assertEqual(Magmom.are_collinear(ncl_magmoms), False)
def test_is_collinear(self): magmoms_list = [[0, 0, 0], [1, 1, 1], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 1], [0, 0, 1], [0, 0, 1]], [[0, 0, -1], [0, 0, 1], [0, 0, 1]], [[2, 2, 2], [-2, -2, -2], [2, 2, 2]]] for magmoms in magmoms_list: self.assertEqual(Magmom.are_collinear(magmoms), True) ncl_magmoms = [[[0, 0, 1], [0, 0, 1], [1, 2, 3]]] self.assertEqual(Magmom.are_collinear(ncl_magmoms), False)
def test_get_structures(self): # incommensurate structures not currently supported self.assertRaises(NotImplementedError, self.mcif_incom.get_structures) # disordered magnetic structures not currently supported self.assertRaises(NotImplementedError, self.mcif_disord.get_structures) # taken from self.mcif_ncl, removing explicit magnetic symmops # so that MagneticSymmetryGroup() has to be invoked magcifstr = """ data_5yOhtAoR _space_group.magn_name_BNS "P 4/m' b' m' " _cell_length_a 7.1316 _cell_length_b 7.1316 _cell_length_c 4.0505 _cell_angle_alpha 90.00 _cell_angle_beta 90.00 _cell_angle_gamma 90.00 loop_ _atom_site_label _atom_site_type_symbol _atom_site_fract_x _atom_site_fract_y _atom_site_fract_z _atom_site_occupancy Gd1 Gd 0.31746 0.81746 0.00000 1 B1 B 0.00000 0.00000 0.20290 1 B2 B 0.17590 0.03800 0.50000 1 B3 B 0.08670 0.58670 0.50000 1 loop_ _atom_site_moment_label _atom_site_moment_crystalaxis_x _atom_site_moment_crystalaxis_y _atom_site_moment_crystalaxis_z Gd1 5.05 5.05 0.0""" s = self.mcif.get_structures(primitive=False)[0] self.assertEqual(s.formula, "Ni32 O32") self.assertTrue(Magmom.are_collinear(s.site_properties['magmom'])) # example with non-collinear spin s_ncl = self.mcif_ncl.get_structures(primitive=False)[0] s_ncl_from_msg = CifParser.from_string(magcifstr).get_structures( primitive=False)[0] self.assertEqual(s_ncl.formula, "Gd4 B16") self.assertFalse(Magmom.are_collinear(s_ncl.site_properties['magmom'])) self.assertTrue(s_ncl.matches(s_ncl_from_msg))
def test_get_structures(self): # incommensurate structures not currently supported self.assertRaises(NotImplementedError, self.mcif_incom.get_structures) # disordered magnetic structures not currently supported self.assertRaises(NotImplementedError, self.mcif_disord.get_structures) # taken from self.mcif_ncl, removing explicit magnetic symmops # so that MagneticSymmetryGroup() has to be invoked magcifstr = """ data_5yOhtAoR _space_group.magn_name_BNS "P 4/m' b' m' " _cell_length_a 7.1316 _cell_length_b 7.1316 _cell_length_c 4.0505 _cell_angle_alpha 90.00 _cell_angle_beta 90.00 _cell_angle_gamma 90.00 loop_ _atom_site_label _atom_site_type_symbol _atom_site_fract_x _atom_site_fract_y _atom_site_fract_z _atom_site_occupancy Gd1 Gd 0.31746 0.81746 0.00000 1 B1 B 0.00000 0.00000 0.20290 1 B2 B 0.17590 0.03800 0.50000 1 B3 B 0.08670 0.58670 0.50000 1 loop_ _atom_site_moment_label _atom_site_moment_crystalaxis_x _atom_site_moment_crystalaxis_y _atom_site_moment_crystalaxis_z Gd1 5.05 5.05 0.0""" s = self.mcif.get_structures(primitive=False)[0] self.assertEqual(s.formula, "Ni32 O32") self.assertTrue(Magmom.are_collinear(s.site_properties['magmom'])) # example with non-collinear spin s_ncl = self.mcif_ncl.get_structures(primitive=False)[0] s_ncl_from_msg = CifParser.from_string(magcifstr).get_structures(primitive=False)[0] self.assertEqual(s_ncl.formula, "Gd4 B16") self.assertFalse(Magmom.are_collinear(s_ncl.site_properties['magmom'])) self.assertTrue(s_ncl.matches(s_ncl_from_msg))
def check_and_force_collinear(mypatstructure, angle_tolerance=10.0): """Make sure the list of magmoms use the same spin axis by taking the largest magnetic moments as a reference axis for collinear calculations Parameters: ----------- mypatstructure: Structure structure object angle_tolerance: float the minimum angle to consider between the reference spin axis and magnetic moments vector. Default=10. Returns: -------- all_true : boolean, if True the angle is within the threshold """ from pymatgen.electronic_structure.core import Magmom from pymatgen.util.coord import get_angle magmoms = mypatstructure.site_properties['magmom'] cif_moments, direction = Magmom.get_consistent_set_and_saxis(magmoms) #print("direction", direction) magmoms_ = np.empty([0,3]) for comp in magmoms: magmoms_ = np.vstack([magmoms_, [comp[0], comp[1], comp[2]]]) #print("comp", comp[0], comp[1], comp[2]) # filter non zero moments magmoms_nzr = [m for m in magmoms_ if abs(np.any(m))] true_or_false = Magmom.are_collinear(magmoms) #if true_or_false == False: angles = np.empty([0, 1]) for m in magmoms_nzr: angles = np.vstack([angles, get_angle(unit_vector(m), direction, units="degrees")]) #print("angles", angles) store_check = [] for angle in angles: store_check.append(check_angle_bool(angle, angle_tolerance)) all_true = np.allclose(True, store_check) return all_true
def __init__( self, structure: Structure, overwrite_magmom_mode: Union[OverwriteMagmomMode, str] = "none", round_magmoms: bool = False, detect_valences: bool = False, make_primitive: bool = True, default_magmoms: bool = None, set_net_positive: bool = True, threshold: float = 0.1, ): """ A class which provides a few helpful methods to analyze collinear magnetic structures. If magnetic moments are not defined, moments will be taken either from default_magmoms.yaml (similar to the default magmoms in MPRelaxSet, with a few extra definitions) or from a specie:magmom dict provided by the default_magmoms kwarg. Input magmoms can be replaced using the 'overwrite_magmom_mode' kwarg. This can be: * "none" to do nothing, * "respect_sign" which will overwrite existing magmoms with those from default_magmoms but will keep sites with positive magmoms positive, negative magmoms negative and zero magmoms zero, * "respect_zeros", which will give a ferromagnetic structure (all positive magmoms from default_magmoms) but still keep sites with zero magmoms as zero, * "replace_all" which will try to guess initial magmoms for all sites in the structure irrespective of input structure (this is most suitable for an initial DFT calculation), * "replace_all_if_undefined" is the same as "replace_all" but only if no magmoms are defined in input structure, otherwise it will respect existing magmoms. * "normalize" will normalize magmoms to unity, but will respect sign (used for comparing orderings), magmoms < theshold will be set to zero :param structure: Structure object :param overwrite_magmom_mode (str): default "none" :param round_magmoms (int or bool): will round input magmoms to specified number of decimal places if integer is supplied, if set to a float will try and group magmoms together using a kernel density estimator of provided width, and extracting peaks of the estimator :param detect_valences (bool): if True, will attempt to assign valences to input structure :param make_primitive (bool): if True, will transform to primitive magnetic cell :param default_magmoms (dict): (optional) dict specifying default magmoms :param set_net_positive (bool): if True, will change sign of magnetic moments such that the net magnetization is positive. Argument will be ignored if mode "respect_sign" is used. :param threshold (float): number (in Bohr magnetons) below which magmoms will be rounded to zero, default of 0.1 can probably be increased for many magnetic systems, depending on your application """ if default_magmoms: self.default_magmoms = default_magmoms else: self.default_magmoms = DEFAULT_MAGMOMS structure = structure.copy() # check for disorder if not structure.is_ordered: raise NotImplementedError( "Not implemented for disordered structures, " "make ordered approximation first.") if detect_valences: trans = AutoOxiStateDecorationTransformation() bva = BVAnalyzer() try: structure = trans.apply_transformation(structure) except ValueError: warnings.warn("Could not assign valences " "for {}".format( structure.composition.reduced_formula)) # check to see if structure has magnetic moments # on site properties or species spin properties, # prioritize site properties has_magmoms = bool(structure.site_properties.get("magmom", False)) has_spin = False for comp in structure.species_and_occu: for sp, occu in comp.items(): if getattr(sp, "spin", False): has_spin = True # perform input sanitation ... # rest of class will assume magnetic moments # are stored on site properties: # this is somewhat arbitrary, arguments can # be made for both approaches if has_magmoms and has_spin: raise ValueError("Structure contains magnetic moments on both " "magmom site properties and spin species " "properties. This is ambiguous. Remove one or " "the other.") elif has_magmoms: if None in structure.site_properties["magmom"]: warnings.warn("Be careful with mixing types in your magmom " "site properties. Any 'None' magmoms have been " "replaced with zero.") magmoms = [ m if m else 0 for m in structure.site_properties["magmom"] ] elif has_spin: magmoms = [getattr(sp, "spin", 0) for sp in structure.species] structure.remove_spin() else: # no magmoms present, add zero magmoms for now magmoms = [0] * len(structure) # and overwrite magmoms with default magmoms later unless otherwise stated if overwrite_magmom_mode == "replace_all_if_undefined": overwrite_magmom_mode = "replace_all" # test to see if input structure has collinear magmoms self.is_collinear = Magmom.are_collinear(magmoms) if not self.is_collinear: warnings.warn( "This class is not designed to be used with " "non-collinear structures. If your structure is " "only slightly non-collinear (e.g. canted) may still " "give useful results, but use with caution.") # this is for collinear structures only, make sure magmoms # are all floats magmoms = list(map(float, magmoms)) # set properties that should be done /before/ we process input magmoms self.total_magmoms = sum(magmoms) self.magnetization = sum(magmoms) / structure.volume # round magmoms below threshold to zero magmoms = [m if abs(m) > threshold else 0 for m in magmoms] # overwrite existing magmoms with default_magmoms if overwrite_magmom_mode not in ( "none", "respect_sign", "respect_zeros", "replace_all", "replace_all_if_undefined", "normalize", ): raise ValueError("Unsupported mode.") for idx, site in enumerate(structure): if site.species_string in self.default_magmoms: # look for species first, e.g. Fe2+ default_magmom = self.default_magmoms[site.species_string] elif (isinstance(site.specie, Specie) and str(site.specie.element) in self.default_magmoms): # look for element, e.g. Fe default_magmom = self.default_magmoms[str(site.specie.element)] else: default_magmom = 0 # overwrite_magmom_mode = "respect_sign" will change magnitude of # existing moments only, and keep zero magmoms as # zero: it will keep the magnetic ordering intact if overwrite_magmom_mode == "respect_sign": set_net_positive = False if magmoms[idx] > 0: magmoms[idx] = default_magmom elif magmoms[idx] < 0: magmoms[idx] = -default_magmom # overwrite_magmom_mode = "respect_zeros" will give a ferromagnetic # structure but will keep zero magmoms as zero elif overwrite_magmom_mode == "respect_zeros": if magmoms[idx] != 0: magmoms[idx] = default_magmom # overwrite_magmom_mode = "replace_all" will ignore input magmoms # and give a ferromagnetic structure with magnetic # moments on *all* atoms it thinks could be magnetic elif overwrite_magmom_mode == "replace_all": magmoms[idx] = default_magmom # overwrite_magmom_mode = "normalize" set magmoms magnitude to 1 elif overwrite_magmom_mode == "normalize": if magmoms[idx] != 0: magmoms[idx] = int(magmoms[idx] / abs(magmoms[idx])) # round magmoms, used to smooth out computational data magmoms = (self._round_magmoms(magmoms, round_magmoms) if round_magmoms else magmoms) if set_net_positive: sign = np.sum(magmoms) if sign < 0: magmoms = -np.array(magmoms) structure.add_site_property("magmom", magmoms) if make_primitive: structure = structure.get_primitive_structure(use_site_props=True) self.structure = structure
def __init__(self, structure, overwrite_magmom_mode="none", round_magmoms=False, detect_valences=False, make_primitive=True, default_magmoms=None, threshold=0.1): """ A class which provides a few helpful methods to analyze collinear magnetic structures. If magnetic moments are not defined, moments will be taken either from default_magmoms.yaml (similar to the default magmoms in MPRelaxSet, with a few extra definitions) or from a specie:magmom dict provided by the default_magmoms kwarg. Input magmoms can be replaced using the 'overwrite_magmom_mode' kwarg. This can be: * "none" to do nothing, * "respect_sign" which will overwrite existing magmoms with those from default_magmoms but will keep sites with positive magmoms positive, negative magmoms negative and zero magmoms zero, * "respect_zeros", which will give a ferromagnetic structure (all positive magmoms from default_magmoms) but still keep sites with zero magmoms as zero, * "replace_all" which will try to guess initial magmoms for all sites in the structure irrespective of input structure (this is most suitable for an initial DFT calculation), * "replace_all_if_undefined" is the same as "replace_all" but only if no magmoms are defined in input structure, otherwise it will respect existing magmoms. :param structure: Structure object :param overwrite_magmom_mode (str): default "none" :param round_magmoms (int): will round input magmoms to specified number of decimal places, suggest value of 1 or False for typical DFT calculations depending on application :param detect_valences (bool): if True, will attempt to assign valences to input structure :param make_primitive (bool): if True, will transform to primitive magnetic cell :param default_magmoms (dict): (optional) dict specifying default magmoms :param threshold (float): number (in Bohr magnetons) below which magmoms will be rounded to zero, default of 0.1 can probably be increased for many magnetic systems, depending on your application """ if default_magmoms: self.default_magmoms = default_magmoms else: self.default_magmoms = DEFAULT_MAGMOMS structure = structure.copy() # check for disorder if not structure.is_ordered: raise NotImplementedError("Not implemented for disordered structures, " "make ordered approximation first.") if detect_valences: trans = AutoOxiStateDecorationTransformation() bva = BVAnalyzer() try: structure = trans.apply_transformation(structure) except ValueError: warnings.warn("Could not assign valences " "for {}".format(structure.composition.reduced_formula)) # check to see if structure has magnetic moments # on site properties or species spin properties, # prioritize site properties has_magmoms = bool(structure.site_properties.get('magmom', False)) has_spin = False for comp in structure.species_and_occu: for sp, occu in comp.items(): if getattr(sp, 'spin', False): has_spin = True # perform input sanitation ... # rest of class will assume magnetic moments # are stored on site properties: # this is somewhat arbitrary, arguments can # be made for both approaches if has_magmoms and has_spin: raise ValueError("Structure contains magnetic moments on both " "magmom site properties and spin species " "properties. This is ambiguous. Remove one or " "the other.") elif has_magmoms: if None in structure.site_properties['magmom']: warnings.warn("Be careful with mixing types in your magmom " "site properties. Any 'None' magmoms have been " "replaced with zero.") magmoms = [m if m else 0 for m in structure.site_properties['magmom']] elif has_spin: magmoms = [getattr(sp, 'spin', 0) for sp in structure.species] structure.remove_spin() else: # no magmoms present, add zero magmoms for now magmoms = [0]*len(structure) # and overwrite magmoms with default magmoms later unless otherwise stated if overwrite_magmom_mode == "replace_all_if_undefined": overwrite_magmom_mode = "replace_all" # test to see if input structure has collinear magmoms self.is_collinear = Magmom.are_collinear(magmoms) if not self.is_collinear: warnings.warn("This class is not designed to be used with " "non-collinear structures. If your structure is " "only slightly non-collinear (e.g. canted) may still " "give useful results, but use with caution.") # this is for collinear structures only, make sure magmoms # are all floats magmoms = list(map(float, magmoms)) # set properties that should be done /before/ we process input magmoms self.total_magmoms = sum(magmoms) self.magnetization = sum(magmoms)/structure.volume # round magmoms below threshold to zero magmoms = [m if abs(m) > threshold else 0 for m in magmoms] # overwrite existing magmoms with default_magmoms if overwrite_magmom_mode not in ("none", "respect_sign", "respect_zeros", "replace_all", "replace_all_if_undefined"): raise ValueError("Unsupported mode.") for idx, site in enumerate(structure): if site.species_string in self.default_magmoms: # look for species first, e.g. Fe2+ default_magmom = self.default_magmoms[site.species_string] elif isinstance(site.specie, Specie) and \ str(site.specie.element) in self.default_magmoms: # look for element, e.g. Fe default_magmom = self.default_magmoms[str(site.specie.element)] else: default_magmom = 0 # overwrite_magmom_mode = "respect_sign" will change magnitude of # existing moments only, and keep zero magmoms as # zero: it will keep the magnetic ordering intact if overwrite_magmom_mode == "respect_sign": if magmoms[idx] > 0: magmoms[idx] = default_magmom elif magmoms[idx] < 0: magmoms[idx] = -default_magmom # overwrite_magmom_mode = "respect_zeros" will give a ferromagnetic # structure but will keep zero magmoms as zero elif overwrite_magmom_mode == "respect_zeros": if magmoms[idx] != 0: magmoms[idx] = default_magmom # overwrite_magmom_mode = "replace_all" will ignore input magmoms # and give a ferromagnetic structure with magnetic # moments on *all* atoms it thinks could be magnetic elif overwrite_magmom_mode == "replace_all": magmoms[idx] = default_magmom # round magmoms to specified number of # decimal places, used to smooth out # computational data # TODO: be a bit smarter about rounding magmoms! if round_magmoms: magmoms = np.around(structure.site_properties['magmom'], decimals=round_magmoms) structure.add_site_property(magmoms) structure.add_site_property('magmom', magmoms) if make_primitive: structure = structure.get_primitive_structure(use_site_props=True) self.structure = structure