def operate_magmom(self, magmom): """ Apply time reversal operator on the magnetic moment. Note that magnetic moments transform as axial vectors, not polar vectors. See 'Symmetry and magnetic structures', Rodríguez-Carvajal and Bourée for a good discussion. DOI: 10.1051/epjconf/20122200010 Args: magmom: Magnetic moment as electronic_structure.core.Magmom class or as list or np array-like Returns: Magnetic moment after operator applied as Magmom class """ magmom = Magmom(magmom) # type casting to handle lists as input transformed_moment = (self.apply_rotation_only(magmom.global_moment) * np.linalg.det(self.rotation_matrix) * self.time_reversal) # retains input spin axis if different from default return Magmom.from_global_moment_and_saxis(transformed_moment, magmom.saxis)
def _unique_coords(self, coords_in, magmoms_in=None): """ Generate unique coordinates using coord and symmetry positions and also their corresponding magnetic moments, if supplied. """ coords = [] if magmoms_in: magmoms = [] magmoms_in = [Magmom(magmom) for magmom in magmoms_in] if len(magmoms_in) != len(coords_in): raise ValueError for tmp_coord, tmp_magmom in zip(coords_in, magmoms_in): for op in self.symmetry_operations: coord = op.operate(tmp_coord) coord = np.array([i - math.floor(i) for i in coord]) if isinstance(op, MagSymmOp): magmom = Magmom(op.operate_magmom(tmp_magmom.moment)) else: magmom = tmp_magmom if not in_coord_list_pbc( coords, coord, atol=self._site_tolerance): coords.append(coord) magmoms.append(magmom) return coords, magmoms else: for tmp_coord in coords_in: for op in self.symmetry_operations: coord = op.operate(tmp_coord) coord = np.array([i - math.floor(i) for i in coord]) if not in_coord_list_pbc( coords, coord, atol=self._site_tolerance): coords.append(coord) return coords, [Magmom(0)] * len(coords) # return dummy magmoms
def test_get_moments(self): # simple cases magmom_along_x = Magmom([1, 0, 0]) self.assertTrue(np.allclose(magmom_along_x.get_moment(saxis=[1, 0, 0]), [0, 0, 1])) magmom_along_y = Magmom([0, 1, 0]) self.assertTrue(np.allclose(magmom_along_y.get_moment(saxis=[0, 1, 0]), [0, 0, 1])) # test transformations magmoms = [[0, 0, 0], [0, 0, 1], [0, 0, -1], [1, 2, 3], [-1, 2, 3], [-1, -2, -3]] for magmom in magmoms: magmom1 = Magmom(magmom) # transform to non-default saxis magmom2 = magmom1.get_00t_magmom_with_xyz_saxis() # and back to default saxis magmom3 = magmom2.get_xyz_magmom_with_001_saxis() self.assertTrue(np.allclose(magmom1.moment, magmom)) self.assertTrue(np.allclose(magmom1.saxis, [0, 0, 1])) self.assertTrue(np.allclose(magmom1.get_moment(saxis=magmom1.saxis), magmom1.moment)) self.assertTrue(np.allclose(magmom1.get_moment(saxis=magmom2.saxis), magmom2.moment)) self.assertTrue(np.allclose(magmom2.get_moment(saxis=[0, 0, 1]), magmom1.moment)) self.assertTrue(np.allclose(magmom2.get_moment(saxis=magmom2.saxis), magmom2.moment)) self.assertTrue(np.allclose(magmom3.moment, magmom1.moment))
def test_as_dict_and_from_dict(self): d = self.incar.as_dict() incar2 = Incar.from_dict(d) self.assertEqual(self.incar, incar2) d["MAGMOM"] = [Magmom([1, 2, 3]).as_dict()] incar3 = Incar.from_dict(d) self.assertEqual(incar3["MAGMOM"], [Magmom([1, 2, 3])])
def test_have_consistent_saxis(self): magmom1 = Magmom([1, 2, 3]) magmom2 = Magmom([1, 2, 3]) magmom3 = Magmom([1, 2, 3], saxis=[0, 0, -1]) magmom4 = Magmom([1, 2, 3], saxis=[1, 2, 3]) self.assertTrue(Magmom.have_consistent_saxis([magmom1, magmom2])) self.assertFalse(Magmom.have_consistent_saxis([magmom1, magmom3])) self.assertFalse(Magmom.have_consistent_saxis([magmom1, magmom4]))
def test_get_consistent_set_and_saxis(self): magmoms = [1, 1, 2, 2, 0, 0, 2] magmoms, saxis = Magmom.get_consistent_set_and_saxis(magmoms) self.assertTrue(np.allclose(saxis, [0, 0, 1])) magmoms = [[0, 0, 0], [1, 1, 1], [2, 2, 2]] magmoms, saxis = Magmom.get_consistent_set_and_saxis(magmoms) self.assertTrue(np.allclose(saxis, [np.sqrt(1 / 3.0)] * 3))
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_consistent_set_and_saxis(self): magmoms = [1, 1, 2, 2, 0, 0, 2] magmoms, saxis = Magmom.get_consistent_set_and_saxis(magmoms) self.assertTrue(np.allclose(saxis, [0, 0, 1])) magmoms = [[0, 0, 0], [1, 1, 1], [2, 2, 2]] magmoms, saxis = Magmom.get_consistent_set_and_saxis(magmoms) self.assertTrue(np.allclose(saxis, [np.sqrt(1/3.)]*3))
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 from_dict(cls, d: dict) -> "ViseIncar": if d.get("MAGMOM") and isinstance(d["MAGMOM"][0], dict): d["MAGMOM"] = [Magmom.from_dict(m) for m in d["MAGMOM"]] return cls( {k: v for k, v in d.items() if k not in ("@module", "@class")})
def get_orbit(self, p, m, tol=1e-5): """ Returns the orbit for a point and its associated magnetic moment. Args: p: Point as a 3x1 array. m: A magnetic moment, compatible with :class:`pymatgen.electronic_structure.core.Magmom` tol: Tolerance for determining if sites are the same. 1e-5 should be sufficient for most purposes. Set to 0 for exact matching (and also needed for symbolic orbits). Returns: (([array], [array])) Tuple of orbit for point and magnetic moments for orbit. """ orbit = [] orbit_magmoms = [] m = Magmom(m) for o in self.symmetry_ops: pp = o.operate(p) pp = np.mod(np.round(pp, decimals=10), 1) mm = o.operate_magmom(m) if not in_array_list(orbit, pp, tol=tol): orbit.append(pp) orbit_magmoms.append(mm) return orbit, orbit_magmoms
def test_operate_magmom(self): # all test magmoms are the same magmoms = [ Magmom([1, 2, 3]), # as Magmom [1, 2, 3], # as list Magmom([-3, 2, 1], saxis=[1, 0, 0]), ] # as Magmom with non-default saxis xyzt_strings = ["x, y, z, +1", "x, y, z, -1", "x, -y, z, -1", "-x, -y, z, -1"] transformed_magmoms = [[1, 2, 3], [-1, -2, -3], [1, -2, 3], [1, 2, -3]] for xyzt_string, transformed_magmom in zip(xyzt_strings, transformed_magmoms): for magmom in magmoms: op = MagSymmOp.from_xyzt_string(xyzt_string) self.assertTrue(np.allclose(transformed_magmom, op.operate_magmom(magmom).global_moment))
def from_dict(cls, d: Dict[str, Any]) -> "ViseIncar": kwargs = deepcopy(d) if kwargs.get("MAGMOM") and isinstance(kwargs["MAGMOM"][0], dict): kwargs["MAGMOM"] = [Magmom.from_dict(m) for m in kwargs["MAGMOM"]] return cls( {k: v for k, v in d.items() if k not in ("@module", "@class")})
def test_init(self): # backwards compatibility for scalar-like magmoms magmom = Magmom(2.0) self.assertEqual(float(magmom), 2.0) # backwards compatibility for list-like magmoms magmom2 = Magmom([1, 2, 3]) self.assertEqual(list(magmom2), [1, 2, 3]) self.assertEqual(magmom2.global_moment.tolist(), [1, 2, 3]) # non-default saxis, normalized internally magmom3 = Magmom([1, 2, 3], saxis=[1, 1, 1]) self.assertTrue(np.allclose(magmom3.saxis, [np.sqrt(1/3.)]*3)) # test construction from known global moment and desired, non-default saxis magmom4 = Magmom.from_global_moment_and_saxis([1, 2, 3], saxis=[1, 0, 0]) self.assertTrue(np.allclose(magmom4.moment, [-3, 2, 1])) # test global moments with non-default saxis magmom5 = Magmom([-3, 2, 1], saxis=[1, 0, 0]) self.assertTrue(np.allclose(magmom5.global_moment, [1, 2, 3]))
def _unique_coords(self, coords_in, magmoms_in=None, lattice=None): """ Generate unique coordinates using coord and symmetry positions and also their corresponding magnetic moments, if supplied. """ coords = [] coords_num = [] if magmoms_in: magmoms = [] if len(magmoms_in) != len(coords_in): raise ValueError for tmp_coord, tmp_magmom in zip(coords_in, magmoms_in): count = 0 for op in self.symmetry_operations: coord = op.operate(tmp_coord) coord = np.array([i - math.floor(i) for i in coord]) if isinstance(op, MagSymmOp): # Up to this point, magmoms have been defined relative # to crystal axis. Now convert to Cartesian and into # a Magmom object. magmom = Magmom.from_moment_relative_to_crystal_axes( op.operate_magmom(tmp_magmom), lattice=lattice) else: magmom = Magmom(tmp_magmom) if not in_coord_list_pbc( coords, coord, atol=self._site_tolerance): coords.append(coord) magmoms.append(magmom) count = count + 1 coords_num.append(count) return coords, magmoms, coords_num else: for tmp_coord in coords_in: count = 0 for op in self.symmetry_operations: coord = op.operate(tmp_coord) coord = np.array([i - math.floor(i) for i in coord]) if not in_coord_list_pbc( coords, coord, atol=self._site_tolerance): coords.append(coord) count = count + 1 coords_num.append(count) return coords, [Magmom(0) ] * len(coords), coords_num # return dummy magmoms
def test_relative_to_crystal_axes(self): lattice = Lattice.from_parameters(5, 10, 5, 90, 110, 90) moment = [1, 0, 2] magmom = Magmom.from_moment_relative_to_crystal_axes(moment, lattice) self.assertTrue( np.allclose(magmom.moment, [0.93969262, 0.0, 1.65797986])) self.assertTrue( np.allclose(magmom.get_moment_relative_to_crystal_axes(lattice), moment))
def setUp(self): self.ordered_site = Site("Fe", [0.25, 0.35, 0.45]) self.disordered_site = Site({"Fe": 0.5, "Mn": 0.5}, [0.25, 0.35, 0.45]) self.propertied_site = Site("Fe2+", [0.25, 0.35, 0.45], {'magmom': 5.1, 'charge': 4.2}) self.propertied_magmomvector_site = Site("Fe2+", [0.25, 0.35, 0.45], {'magmom': Magmom([2.6, 2.6, 3.5]), 'charge': 4.2}) self.dummy_site = Site("X", [0, 0, 0])
def test_lsorbit_magmom(self): magmom1 = [[0.0, 0.0, 3.0], [0, 1, 0], [2, 1, 2]] magmom2 = [-1, -1, -1, 0, 0, 0, 0, 0] magmom4 = [Magmom([1.0, 2.0, 2.0])] ans_string1 = ( "LANGEVIN_GAMMA = 10 10 10\nLSORBIT = True\n" "MAGMOM = 0.0 0.0 3.0 0 1 0 2 1 2\n" ) ans_string2 = "LANGEVIN_GAMMA = 10\nLSORBIT = True\n" "MAGMOM = 3*3*-1 3*5*0\n" ans_string3 = "LSORBIT = False\nMAGMOM = 2*-1 2*9\n" ans_string4_nolsorbit = "LANGEVIN_GAMMA = 10\nLSORBIT = False\nMAGMOM = 1*3.0\n" ans_string4_lsorbit = ( "LANGEVIN_GAMMA = 10\nLSORBIT = True\nMAGMOM = 1.0 2.0 2.0\n" ) incar = Incar({}) incar["MAGMOM"] = magmom1 incar["LSORBIT"] = "T" incar["LANGEVIN_GAMMA"] = [10, 10, 10] self.assertEqual(ans_string1, str(incar)) incar["MAGMOM"] = magmom2 incar["LSORBIT"] = "T" incar["LANGEVIN_GAMMA"] = 10 self.assertEqual(ans_string2, str(incar)) incar["MAGMOM"] = magmom4 incar["LSORBIT"] = "F" self.assertEqual(ans_string4_nolsorbit, str(incar)) incar["LSORBIT"] = "T" self.assertEqual(ans_string4_lsorbit, str(incar)) incar = Incar.from_string(ans_string1) self.assertEqual(incar["MAGMOM"], [[0.0, 0.0, 3.0], [0, 1, 0], [2, 1, 2]]) self.assertEqual(incar["LANGEVIN_GAMMA"], [10, 10, 10]) incar = Incar.from_string(ans_string2) self.assertEqual( incar["MAGMOM"], [ [-1, -1, -1], [-1, -1, -1], [-1, -1, -1], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], ], ) self.assertEqual(incar["LANGEVIN_GAMMA"], [10]) incar = Incar.from_string(ans_string3) self.assertFalse(incar["LSORBIT"]) self.assertEqual(incar["MAGMOM"], [-1, -1, 9, 9])
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 test_to_from_dict(self): d = self.disordered_site.as_dict() site = Site.from_dict(d) self.assertEqual(site, self.disordered_site) self.assertNotEqual(site, self.ordered_site) d = self.propertied_site.as_dict() site = Site.from_dict(d) self.assertEqual(site.properties["magmom"], 5.1) self.assertEqual(site.properties["charge"], 4.2) d = self.propertied_magmomvector_site.as_dict() site = Site.from_dict(d) self.assertEqual(site.properties["magmom"], Magmom([2.6, 2.6, 3.5])) self.assertEqual(site.properties["charge"], 4.2) d = self.dummy_site.as_dict() site = Site.from_dict(d) self.assertEqual(site.species, self.dummy_site.species)
def parse_magmoms(self, data, lattice=None): """ Parse atomic magnetic moments from data dictionary """ if lattice is None: raise Exception( 'Magmoms given in terms of crystal axes in magCIF spec.') try: magmoms = { data["_atom_site_moment_label"][i]: Magmom.from_moment_relative_to_crystal_axes([ str2float(data["_atom_site_moment_crystalaxis_x"][i]), str2float(data["_atom_site_moment_crystalaxis_y"][i]), str2float(data["_atom_site_moment_crystalaxis_z"][i]) ], lattice) for i in range(len(data["_atom_site_moment_label"])) } except (ValueError, KeyError): return None return magmoms
def _pristine_magnetic_moments_and_direction(self): """Magnetic moments of and collinear axis direction and separates it into the directions of each moments and collinear spin axis. Returns ------- moments : numpy.ndarray direction : numpy.ndarray """ structure = self.pristine_structure_supercell magmoms = structure.site_properties['magmom'] moments, direction = Magmom.get_consistent_set_and_saxis(magmoms) # append for muon if self.mu_f==-1: moments.append(np.array([0.,0.,0.])) else: moments.insert(self_mu_f, np.array([0.,0.,0.])) if len(moments) != self.n_atoms: # to check if calculated structure is compatible to supercell size raise EstimateFieldContributionsError('Invalid inputs in Structure magmoms') return moments, direction
def test_from_magnetic_spacegroup(self): # AFM MnF s1 = Structure.from_magnetic_spacegroup( "P4_2'/mnm'", Lattice.tetragonal(4.87, 3.30), ["Mn", "F"], [[0, 0, 0], [0.30, 0.30, 0.00]], {'magmom': [4, 0]}) self.assertEqual(s1.formula, "Mn2 F4") self.assertEqual(sum(map(float, s1.site_properties['magmom'])), 0) self.assertEqual(max(map(float, s1.site_properties['magmom'])), 4) self.assertEqual(min(map(float, s1.site_properties['magmom'])), -4) # AFM LaMnO3, ordered on (001) planes s2 = Structure.from_magnetic_spacegroup( "Pn'ma'", Lattice.orthorhombic(5.75, 7.66, 5.53), ["La", "Mn", "O", "O"], [[0.05, 0.25, 0.99], [0.00, 0.00, 0.50], [0.48, 0.25, 0.08], [0.31, 0.04, 0.72]], {'magmom': [0, Magmom([4, 0, 0]), 0, 0]}) self.assertEqual(s2.formula, "La4 Mn4 O12") self.assertEqual(sum(map(float, s2.site_properties['magmom'])), 0) self.assertEqual(max(map(float, s2.site_properties['magmom'])), 4) self.assertEqual(min(map(float, s2.site_properties['magmom'])), -4)
def operate_magmom(self, magmom): """ Apply time reversal operator on the magnetic moment. Note that magnetic moments transform as axial vectors, not polar vectors. See 'Symmetry and magnetic structures', Rodríguez-Carvajal and Bourée for a good discussion. DOI: 10.1051/epjconf/20122200010 Args: magmom: Magnetic moment as electronic_structure.core.Magmom class or as list or np array-like Returns: Magnetic moment after operator applied as Magmom class """ magmom = Magmom(magmom) # type casting to handle lists as input transformed_moment = self.apply_rotation_only(magmom.global_moment) * \ np.linalg.det(self.rotation_matrix) * self.time_reversal # retains input spin axis if different from default return Magmom.from_global_moment_and_saxis(transformed_moment, magmom.saxis)
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
def test_negative(self): self.assertEqual(-Magmom([1, 2, 3]), Magmom([-1, -2, -3]))
def test_equality(self): self.assertTrue(Magmom([1, 1, 1]) == Magmom([1, 1, 1])) self.assertFalse(Magmom([1, 1, 2]) == Magmom([1, 1, 1])) self.assertTrue(Magmom([0, 0, 10]) == 10)
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 get_site_scene( self, connected_sites: List[ConnectedSite] = None, # connected_site_metadata: None, # connected_sites_to_draw, connected_sites_not_drawn: List[ConnectedSite] = None, hide_incomplete_edges: bool = False, incomplete_edge_length_scale: Optional[float] = 1.0, connected_sites_colors: Optional[List[str]] = None, connected_sites_not_drawn_colors: Optional[List[str]] = None, origin: Optional[List[float]] = None, draw_polyhedra: bool = True, explicitly_calculate_polyhedra_hull: bool = False, bond_radius: float = 0.1, draw_magmoms: bool = True, magmom_scale: float = 1.0, legend: Optional[Legend] = None, ) -> Scene: """ Args: connected_sites: connected_sites_not_drawn: hide_incomplete_edges: incomplete_edge_length_scale: connected_sites_colors: connected_sites_not_drawn_colors: origin: explicitly_calculate_polyhedra_hull: legend: Returns: """ atoms = [] bonds = [] polyhedron = [] magmoms = [] legend = legend or Legend(self) # for disordered structures is_ordered = self.is_ordered phiStart, phiEnd = None, None occu_start = 0.0 position = self.coords.tolist() radii = [legend.get_radius(sp, site=self) for sp in self.species.keys()] max_radius = float(min(radii)) for idx, (sp, occu) in enumerate(self.species.items()): if isinstance(sp, DummySpecie): cube = Cubes( positions=[position], color=legend.get_color(sp, site=self), width=0.4 ) atoms.append(cube) else: color = legend.get_color(sp, site=self) radius = legend.get_radius(sp, site=self) # TODO: make optional/default to None # in disordered structures, we fractionally color-code spheres, # drawing a sphere segment from phi_end to phi_start # (think a sphere pie chart) if not is_ordered: phi_frac_end = occu_start + occu phi_frac_start = occu_start occu_start = phi_frac_end phiStart = phi_frac_start * np.pi * 2 phiEnd = phi_frac_end * np.pi * 2 name = str(sp) if occu != 1.0: name += " ({}% occupancy)".format(occu) name += f" ({position[0]:.3f}, {position[1]:.3f}, {position[2]:.3f})" if self.properties: for k, v in self.properties.items(): name += f" ({k} = {v})" sphere = Spheres( positions=[position], color=color, radius=radius, phiStart=phiStart, phiEnd=phiEnd, clickable=True, tooltip=name, ) atoms.append(sphere) # Add magmoms if draw_magmoms: if magmom := self.properties.get("magmom"): # enforce type magmom = np.array(Magmom(magmom).get_moment()) magmom = 2 * magmom_scale * max_radius * magmom tail = np.array(position) - 0.5 * np.array(magmom) head = np.array(position) + 0.5 * np.array(magmom) arrow = Arrows( positionPairs=[[tail, head]], color="red", radius=0.20, headLength=0.5, headWidth=0.4, clickable=True, ) magmoms.append(arrow)
def _get_structure(self, data, primitive): """ Generate structure from part of the cif. """ def parse_symbol(sym): # Common representations for elements/water in cif files # TODO: fix inconsistent handling of water special = { "D": "D", "Hw": "H", "Ow": "O", "Wat": "O", "wat": "O", "OH": "", "OH2": "" } m = re.findall(r"w?[A-Z][a-z]*", sym) if m and m != "?": if sym in special: v = special[sym] else: v = special.get(m[0], m[0]) if len(m) > 1 or (m[0] in special): warnings.warn("{} parsed as {}".format(sym, v)) return v lattice = self.get_lattice(data) # if magCIF, get magnetic symmetry moments and magmoms # else standard CIF, and use empty magmom dict if self.feature_flags["magcif_incommensurate"]: raise NotImplementedError( "Incommensurate structures not currently supported.") elif self.feature_flags["magcif"]: self.symmetry_operations = self.get_magsymops(data) magmoms = self.parse_magmoms(data, lattice=lattice) else: self.symmetry_operations = self.get_symops(data) magmoms = {} oxi_states = self.parse_oxi_states(data) coord_to_species = OrderedDict() coord_to_magmoms = OrderedDict() def get_matching_coord(coord): keys = list(coord_to_species.keys()) coords = np.array(keys) for op in self.symmetry_operations: c = op.operate(coord) inds = find_in_coord_list_pbc(coords, c, atol=self._site_tolerance) # cant use if inds, because python is dumb and np.array([0]) evaluates # to False if len(inds): return keys[inds[0]] return False for i in range(len(data["_atom_site_label"])): try: # If site type symbol exists, use it. Otherwise, we use the # label. symbol = parse_symbol(data["_atom_site_type_symbol"][i]) except KeyError: symbol = parse_symbol(data["_atom_site_label"][i]) if not symbol: continue if oxi_states is not None: o_s = oxi_states.get(symbol, 0) # use _atom_site_type_symbol if possible for oxidation state if "_atom_site_type_symbol" in data.data.keys(): oxi_symbol = data["_atom_site_type_symbol"][i] o_s = oxi_states.get(oxi_symbol, o_s) try: el = Specie(symbol, o_s) except: el = DummySpecie(symbol, o_s) else: el = get_el_sp(symbol) x = str2float(data["_atom_site_fract_x"][i]) y = str2float(data["_atom_site_fract_y"][i]) z = str2float(data["_atom_site_fract_z"][i]) magmom = magmoms.get(data["_atom_site_label"][i], Magmom(0)) try: occu = str2float(data["_atom_site_occupancy"][i]) except (KeyError, ValueError): occu = 1 if occu > 0: coord = (x, y, z) match = get_matching_coord(coord) if not match: coord_to_species[coord] = Composition({el: occu}) coord_to_magmoms[coord] = magmom else: coord_to_species[match] += {el: occu} coord_to_magmoms[ match] = None # disordered magnetic not currently supported sum_occu = [sum(c.values()) for c in coord_to_species.values()] if any([o > 1 for o in sum_occu]): warnings.warn( "Some occupancies (%s) sum to > 1! If they are within " "the tolerance, they will be rescaled." % str(sum_occu)) allspecies = [] allcoords = [] allmagmoms = [] # check to see if magCIF file is disordered if self.feature_flags["magcif"]: for k, v in coord_to_magmoms.items(): if v is None: # Proposed solution to this is to instead store magnetic moments # as Specie 'spin' property, instead of site property, but this # introduces ambiguities for end user (such as unintended use of # `spin` and Specie will have fictious oxidation state). raise NotImplementedError( 'Disordered magnetic structures not currently supported.' ) if coord_to_species.items(): for species, group in groupby(sorted(list( coord_to_species.items()), key=lambda x: x[1]), key=lambda x: x[1]): tmp_coords = [site[0] for site in group] tmp_magmom = [ coord_to_magmoms[tmp_coord] for tmp_coord in tmp_coords ] if self.feature_flags["magcif"]: coords, magmoms = self._unique_coords( tmp_coords, tmp_magmom) else: coords, magmoms = self._unique_coords(tmp_coords) allcoords.extend(coords) allspecies.extend(len(coords) * [species]) allmagmoms.extend(magmoms) # rescale occupancies if necessary for i, species in enumerate(allspecies): totaloccu = sum(species.values()) if 1 < totaloccu <= self._occupancy_tolerance: allspecies[i] = species / totaloccu if allspecies and len(allspecies) == len(allcoords) and len( allspecies) == len(allmagmoms): if self.feature_flags["magcif"]: struct = Structure(lattice, allspecies, allcoords, site_properties={"magmom": allmagmoms}) else: struct = Structure(lattice, allspecies, allcoords) struct = struct.get_sorted_structure() if primitive: struct = struct.get_primitive_structure() struct = struct.get_reduced_structure() return struct
def test_relative_to_crystal_axes(self): lattice = Lattice.from_parameters(5, 10, 5, 90, 110, 90) moment = [1, 0, 2] magmom = Magmom.from_moment_relative_to_crystal_axes(moment, lattice) self.assertTrue(np.allclose(magmom.moment, [0.93969262, 0.0, 1.65797986])) self.assertTrue(np.allclose(magmom.get_moment_relative_to_crystal_axes(lattice), moment))
def from_dict(cls, d): if d.get("MAGMOM") and isinstance(d["MAGMOM"][0], dict): d["MAGMOM"] = [Magmom.from_dict(m) for m in d["MAGMOM"]] return Incar({k: v for k, v in d.items() if k not in ("@module","@class",'comment')}),\ [v for k, v in d.items() if k in ('comment')][0]
def __init__(self, struct, symprec=None, write_magmoms=False): """ A wrapper around CifFile to write CIF files from pymatgen structures. Args: struct (Structure): structure to write symprec (float): If not none, finds the symmetry of the structure and writes the cif with symmetry information. Passes symprec to the SpacegroupAnalyzer write_magmoms (bool): If True, will write magCIF file. Incompatible with symprec """ if write_magmoms and symprec: warnings.warn( "Magnetic symmetry cannot currently be detected by pymatgen.") symprec = None format_str = "{:.8f}" block = OrderedDict() loops = [] spacegroup = ("P 1", 1) if symprec is not None: sf = SpacegroupAnalyzer(struct, symprec) spacegroup = (sf.get_space_group_symbol(), sf.get_space_group_number()) # Needs the refined struture when using symprec. This converts # primitive to conventional structures, the standard for CIF. struct = sf.get_refined_structure() latt = struct.lattice comp = struct.composition no_oxi_comp = comp.element_composition block["_symmetry_space_group_name_H-M"] = spacegroup[0] for cell_attr in ['a', 'b', 'c']: block["_cell_length_" + cell_attr] = format_str.format( getattr(latt, cell_attr)) for cell_attr in ['alpha', 'beta', 'gamma']: block["_cell_angle_" + cell_attr] = format_str.format( getattr(latt, cell_attr)) block["_symmetry_Int_Tables_number"] = spacegroup[1] block["_chemical_formula_structural"] = no_oxi_comp.reduced_formula block["_chemical_formula_sum"] = no_oxi_comp.formula block["_cell_volume"] = latt.volume.__str__() reduced_comp, fu = no_oxi_comp.get_reduced_composition_and_factor() block["_cell_formula_units_Z"] = str(int(fu)) if symprec is None: block["_symmetry_equiv_pos_site_id"] = ["1"] block["_symmetry_equiv_pos_as_xyz"] = ["x, y, z"] else: sf = SpacegroupAnalyzer(struct, symprec) symmops = [] for op in sf.get_symmetry_operations(): v = op.translation_vector symmops.append( SymmOp.from_rotation_and_translation( op.rotation_matrix, v)) ops = [op.as_xyz_string() for op in symmops] block["_symmetry_equiv_pos_site_id"] = \ ["%d" % i for i in range(1, len(ops) + 1)] block["_symmetry_equiv_pos_as_xyz"] = ops loops.append( ["_symmetry_equiv_pos_site_id", "_symmetry_equiv_pos_as_xyz"]) contains_oxidation = True try: symbol_to_oxinum = OrderedDict([(el.__str__(), float(el.oxi_state)) for el in sorted(comp.elements)]) except AttributeError: symbol_to_oxinum = OrderedDict([(el.symbol, 0) for el in sorted(comp.elements)]) contains_oxidation = False if contains_oxidation: block["_atom_type_symbol"] = symbol_to_oxinum.keys() block["_atom_type_oxidation_number"] = symbol_to_oxinum.values() loops.append(["_atom_type_symbol", "_atom_type_oxidation_number"]) atom_site_type_symbol = [] atom_site_symmetry_multiplicity = [] atom_site_fract_x = [] atom_site_fract_y = [] atom_site_fract_z = [] atom_site_label = [] atom_site_occupancy = [] atom_site_moment_label = [] atom_site_moment_crystalaxis_x = [] atom_site_moment_crystalaxis_y = [] atom_site_moment_crystalaxis_z = [] count = 1 if symprec is None: for site in struct: for sp, occu in sorted(site.species_and_occu.items()): atom_site_type_symbol.append(sp.__str__()) atom_site_symmetry_multiplicity.append("1") atom_site_fract_x.append("{0:f}".format(site.a)) atom_site_fract_y.append("{0:f}".format(site.b)) atom_site_fract_z.append("{0:f}".format(site.c)) atom_site_label.append("{}{}".format(sp.symbol, count)) atom_site_occupancy.append(occu.__str__()) magmom = site.properties.get('magmom', Magmom(0)) moment = Magmom.get_moment_relative_to_crystal_axes( magmom, latt) if write_magmoms and abs(magmom) > 0: atom_site_moment_label.append("{}{}".format( sp.symbol, count)) atom_site_moment_crystalaxis_x.append(moment[0]) atom_site_moment_crystalaxis_y.append(moment[1]) atom_site_moment_crystalaxis_z.append(moment[2]) count += 1 else: # The following just presents a deterministic ordering. unique_sites = [ (sorted(sites, key=lambda s: tuple([abs(x) for x in s.frac_coords]))[0], len(sites)) for sites in sf.get_symmetrized_structure().equivalent_sites ] for site, mult in sorted(unique_sites, key=lambda t: (t[0].species_and_occu.average_electroneg, -t[1], t[0].a, t[0].b, t[0].c)): for sp, occu in site.species_and_occu.items(): atom_site_type_symbol.append(sp.__str__()) atom_site_symmetry_multiplicity.append("%d" % mult) atom_site_fract_x.append("{0:f}".format(site.a)) atom_site_fract_y.append("{0:f}".format(site.b)) atom_site_fract_z.append("{0:f}".format(site.c)) atom_site_label.append("{}{}".format(sp.symbol, count)) atom_site_occupancy.append(occu.__str__()) count += 1 block["_atom_site_type_symbol"] = atom_site_type_symbol block["_atom_site_label"] = atom_site_label block["_atom_site_symmetry_multiplicity"] = \ atom_site_symmetry_multiplicity block["_atom_site_fract_x"] = atom_site_fract_x block["_atom_site_fract_y"] = atom_site_fract_y block["_atom_site_fract_z"] = atom_site_fract_z block["_atom_site_occupancy"] = atom_site_occupancy loops.append([ "_atom_site_type_symbol", "_atom_site_label", "_atom_site_symmetry_multiplicity", "_atom_site_fract_x", "_atom_site_fract_y", "_atom_site_fract_z", "_atom_site_occupancy" ]) if write_magmoms: block["_atom_site_moment_label"] = atom_site_moment_label block[ "_atom_site_moment_crystalaxis_x"] = atom_site_moment_crystalaxis_x block[ "_atom_site_moment_crystalaxis_y"] = atom_site_moment_crystalaxis_y block[ "_atom_site_moment_crystalaxis_z"] = atom_site_moment_crystalaxis_z loops.append([ "_atom_site_moment_label", "_atom_site_moment_crystalaxis_x", "_atom_site_moment_crystalaxis_y", "_atom_site_moment_crystalaxis_z" ]) d = OrderedDict() d[comp.reduced_formula] = CifBlock(block, loops, comp.reduced_formula) self._cf = CifFile(d)
def test_init(self): for f in ['OUTCAR', 'OUTCAR.gz']: filepath = os.path.join(test_dir, f) outcar = Outcar(filepath) expected_mag = ({ 'd': 0.0, 'p': 0.003, 's': 0.002, 'tot': 0.005 }, { 'd': 0.798, 'p': 0.008, 's': 0.007, 'tot': 0.813 }, { 'd': 0.798, 'p': 0.008, 's': 0.007, 'tot': 0.813 }, { 'd': 0.0, 'p': -0.117, 's': 0.005, 'tot': -0.112 }, { 'd': 0.0, 'p': -0.165, 's': 0.004, 'tot': -0.162 }, { 'd': 0.0, 'p': -0.117, 's': 0.005, 'tot': -0.112 }, { 'd': 0.0, 'p': -0.165, 's': 0.004, 'tot': -0.162 }) expected_chg = ({ 'p': 0.154, 's': 0.078, 'd': 0.0, 'tot': 0.232 }, { 'p': 0.707, 's': 0.463, 'd': 8.316, 'tot': 9.486 }, { 'p': 0.707, 's': 0.463, 'd': 8.316, 'tot': 9.486 }, { 'p': 3.388, 's': 1.576, 'd': 0.0, 'tot': 4.964 }, { 'p': 3.365, 's': 1.582, 'd': 0.0, 'tot': 4.947 }, { 'p': 3.388, 's': 1.576, 'd': 0.0, 'tot': 4.964 }, { 'p': 3.365, 's': 1.582, 'd': 0.0, 'tot': 4.947 }) self.assertAlmostEqual(outcar.magnetization, expected_mag, 5, "Wrong magnetization read from Outcar") self.assertAlmostEqual(outcar.charge, expected_chg, 5, "Wrong charge read from Outcar") self.assertFalse(outcar.is_stopped) self.assertEqual( outcar.run_stats, { 'System time (sec)': 0.938, 'Total CPU time used (sec)': 545.142, 'Elapsed time (sec)': 546.709, 'Maximum memory used (kb)': 0.0, 'Average memory used (kb)': 0.0, 'User time (sec)': 544.204, 'cores': '8' }) self.assertAlmostEqual(outcar.efermi, 2.0112) self.assertAlmostEqual(outcar.nelect, 44.9999991) self.assertAlmostEqual(outcar.total_mag, 0.9999998) self.assertIsNotNone(outcar.as_dict()) self.assertFalse(outcar.lepsilon) filepath = os.path.join(test_dir, 'OUTCAR.stopped') outcar = Outcar(filepath) self.assertTrue(outcar.is_stopped) for f in ['OUTCAR.lepsilon', 'OUTCAR.lepsilon.gz']: filepath = os.path.join(test_dir, f) outcar = Outcar(filepath) self.assertTrue(outcar.lepsilon) self.assertAlmostEqual(outcar.dielectric_tensor[0][0], 3.716432) self.assertAlmostEqual(outcar.dielectric_tensor[0][1], -0.20464) self.assertAlmostEqual(outcar.dielectric_tensor[1][2], -0.20464) self.assertAlmostEqual(outcar.dielectric_ionic_tensor[0][0], 0.001419) self.assertAlmostEqual(outcar.dielectric_ionic_tensor[0][2], 0.001419) self.assertAlmostEqual(outcar.dielectric_ionic_tensor[2][2], 0.001419) self.assertAlmostEqual(outcar.piezo_tensor[0][0], 0.52799) self.assertAlmostEqual(outcar.piezo_tensor[1][3], 0.35998) self.assertAlmostEqual(outcar.piezo_tensor[2][5], 0.35997) self.assertAlmostEqual(outcar.piezo_ionic_tensor[0][0], 0.05868) self.assertAlmostEqual(outcar.piezo_ionic_tensor[1][3], 0.06241) self.assertAlmostEqual(outcar.piezo_ionic_tensor[2][5], 0.06242) self.assertAlmostEqual(outcar.born[0][1][2], -0.385) self.assertAlmostEqual(outcar.born[1][2][0], 0.36465) filepath = os.path.join(test_dir, 'OUTCAR.NiO_SOC.gz') outcar = Outcar(filepath) expected_mag = ({ 's': Magmom([0.0, 0.0, -0.001]), 'p': Magmom([0.0, 0.0, -0.003]), 'd': Magmom([0.0, 0.0, 1.674]), 'tot': Magmom([0.0, 0.0, 1.671]) }, { 's': Magmom([0.0, 0.0, 0.001]), 'p': Magmom([0.0, 0.0, 0.003]), 'd': Magmom([0.0, 0.0, -1.674]), 'tot': Magmom([0.0, 0.0, -1.671]) }, { 's': Magmom([0.0, 0.0, 0.0]), 'p': Magmom([0.0, 0.0, 0.0]), 'd': Magmom([0.0, 0.0, 0.0]), 'tot': Magmom([0.0, 0.0, 0.0]) }, { 's': Magmom([0.0, 0.0, 0.0]), 'p': Magmom([0.0, 0.0, 0.0]), 'd': Magmom([0.0, 0.0, 0.0]), 'tot': Magmom([0.0, 0.0, 0.0]) }) # test note: Magmom class uses np.allclose() when testing for equality # so fine to use assertEqual here self.assertEqual( outcar.magnetization, expected_mag, "Wrong vector magnetization read from Outcar for SOC calculation")