def test_magnetic_kpath_generation(self): struct_file_path = os.path.join(test_dir_structs, "LaMnO3_magnetic.mcif") struct = Structure.from_file(struct_file_path) mga = CollinearMagneticStructureAnalyzer(struct) col_spin_orig = mga.get_structure_with_spin() col_spin_orig.add_spin_by_site([0.0] * 20) col_spin_sym = SpacegroupAnalyzer(col_spin_orig) col_spin_prim = col_spin_sym.get_primitive_standard_structure( international_monoclinic=False) magmom_vec_list = [np.zeros(3) for site in col_spin_prim] magmom_vec_list[4:8] = [ np.array([3.87, 3.87, 0.0]), np.array([3.87, 3.87, 0.0]), np.array([-3.87, -3.87, 0.0]), np.array([-3.87, -3.87, 0.0]), ] col_spin_prim.add_site_property("magmom", magmom_vec_list) kpath = KPathLatimerMunro(col_spin_prim, has_magmoms=True) kpoints = kpath._kpath["kpoints"] labels = list(kpoints.keys()) self.assertEqual( sorted(labels), sorted(["a", "b", "c", "d", "d_{1}", "e", "f", "g", "g_{1}", "Γ"]), ) self.assertAlmostEqual(kpoints["e"][0], -0.4999999999999998) self.assertAlmostEqual(kpoints["e"][1], 0.0) self.assertAlmostEqual(kpoints["e"][2], 0.5000000000000002) self.assertAlmostEqual(kpoints["g"][0], -0.4999999999999999) self.assertAlmostEqual(kpoints["g"][1], -0.49999999999999994) self.assertAlmostEqual(kpoints["g"][2], 0.5000000000000002) self.assertAlmostEqual(kpoints["a"][0], -0.4999999999999999) self.assertAlmostEqual(kpoints["a"][1], 0.0) self.assertAlmostEqual(kpoints["a"][2], 0.0) self.assertAlmostEqual(kpoints["g_{1}"][0], 0.4999999999999999) self.assertAlmostEqual(kpoints["g_{1}"][1], -0.5) self.assertAlmostEqual(kpoints["g_{1}"][2], 0.5000000000000001) self.assertAlmostEqual(kpoints["f"][0], 0.0) self.assertAlmostEqual(kpoints["f"][1], -0.5) self.assertAlmostEqual(kpoints["f"][2], 0.5000000000000002) self.assertAlmostEqual(kpoints["c"][0], 0.0) self.assertAlmostEqual(kpoints["c"][1], 0.0) self.assertAlmostEqual(kpoints["c"][2], 0.5000000000000001) self.assertAlmostEqual(kpoints["b"][0], 0.0) self.assertAlmostEqual(kpoints["b"][1], -0.5) self.assertAlmostEqual(kpoints["b"][2], 0.0) self.assertAlmostEqual(kpoints["Γ"][0], 0) self.assertAlmostEqual(kpoints["Γ"][1], 0) self.assertAlmostEqual(kpoints["Γ"][2], 0) d = False if np.allclose(kpoints["d_{1}"], [-0.5, -0.5, 0.0], atol=1e-5) or np.allclose( kpoints["d"], [-0.5, -0.5, 0.0], atol=1e-5): d = True self.assertTrue(d) g = False if np.allclose(kpoints["g_{1}"], [-0.5, -0.5, 0.5], atol=1e-5) or np.allclose( kpoints["g"], [-0.5, -0.5, 0.5], atol=1e-5): g = True self.assertTrue(g)
def list_ordering(list_struc): return [ CollinearMagneticStructureAnalyzer(structure).ordering for structure in list_struc ]
def get_wf(self, scan=False, perform_bader=True, num_orderings_hard_limit=16, c=None): """ Retrieve the FireWorks workflow. Args: scan: if True, use the SCAN functional instead of GGA+U, since the SCAN functional has shown to have improved performance for magnetic systems in some cases perform_bader: if True, make sure the "bader" binary is in your path, will use Bader analysis to calculate atom-projected magnetic moments num_orderings_hard_limit: will make sure total number of magnetic orderings does not exceed this number even if there are extra orderings of equivalent symmetry c: additional config dict (as used elsewhere in atomate) Returns: FireWorks Workflow """ c = c or {"VASP_CMD": VASP_CMD, "DB_FILE": DB_FILE} fws = [] analysis_parents = [] # trim total number of orderings (useful in high-throughput context) # this is somewhat course, better to reduce num_orderings kwarg and/or # change enumeration strategies ordered_structures = self.ordered_structures ordered_structure_origins = self.ordered_structure_origins def _add_metadata(structure): """ For book-keeping, store useful metadata with the Structure object for later database ingestion including workflow version and a UUID for easier querying of all tasks generated from the workflow. Args: structure: Structure Returns: TransformedStructure """ # this could be further improved by storing full transformation # history, but would require an improved transformation pipeline return TransformedStructure( structure, other_parameters={"wf_meta": self.wf_meta}) ordered_structures = [ _add_metadata(struct) for struct in ordered_structures ] if (num_orderings_hard_limit and len(self.ordered_structures) > num_orderings_hard_limit): ordered_structures = self.ordered_structures[ 0:num_orderings_hard_limit] ordered_structure_origins = self.ordered_structure_origins[ 0:num_orderings_hard_limit] logger.warning("Number of ordered structures exceeds hard limit, " "removing last {} structures.".format( len(self.ordered_structures) - len(ordered_structures))) # always make sure input structure is included if self.input_index and self.input_index > num_orderings_hard_limit: ordered_structures.append( self.ordered_structures[self.input_index]) ordered_structure_origins.append( self.ordered_structure_origins[self.input_index]) # default incar settings user_incar_settings = {"ISYM": 0, "LASPH": True, "EDIFFG": -0.05} if scan: # currently, using SCAN relaxation as a static calculation also # since it is typically high quality enough, but want to make # sure we are also writing the AECCAR* files user_incar_settings.update({"LAECHG": True}) user_incar_settings.update(c.get("user_incar_settings", {})) c["user_incar_settings"] = user_incar_settings for idx, ordered_structure in enumerate(ordered_structures): analyzer = CollinearMagneticStructureAnalyzer(ordered_structure) name = " ordering {} {} -".format(idx, analyzer.ordering.value) if not scan: vis = MPRelaxSet(ordered_structure, user_incar_settings=user_incar_settings) # relax fws.append( OptimizeFW( ordered_structure, vasp_input_set=vis, vasp_cmd=c["VASP_CMD"], db_file=c["DB_FILE"], max_force_threshold=0.05, half_kpts_first_relax=False, name=name + " optimize", )) # static fws.append( StaticFW( ordered_structure, vasp_cmd=c["VASP_CMD"], db_file=c["DB_FILE"], name=name + " static", prev_calc_loc=True, parents=fws[-1], )) else: # wf_scan_opt is just a single FireWork so can append it directly scan_fws = wf_scan_opt(ordered_structure, c=c).fws # change name for consistency with non-SCAN new_name = scan_fws[0].name.replace("structure optimization", name + " optimize") scan_fws[0].name = new_name scan_fws[0].tasks[-1]["additional_fields"][ "task_label"] = new_name fws += scan_fws analysis_parents.append(fws[-1]) fw_analysis = Firework( MagneticOrderingsToDB( db_file=c["DB_FILE"], wf_uuid=self.uuid, auto_generated=False, name="MagneticOrderingsToDB", parent_structure=self.sanitized_structure, origins=ordered_structure_origins, input_index=self.input_index, perform_bader=perform_bader, scan=scan, ), name="Magnetic Orderings Analysis", parents=analysis_parents, spec={"_allow_fizzled_parents": True}, ) fws.append(fw_analysis) formula = self.sanitized_structure.composition.reduced_formula wf_name = "{} - magnetic orderings".format(formula) if scan: wf_name += " - SCAN" wf = Workflow(fws, name=wf_name) wf = add_additional_fields_to_taskdocs(wf, {"wf_meta": self.wf_meta}) tag = "magnetic_orderings group: >>{}<<".format(self.uuid) wf = add_tags(wf, [tag, ordered_structure_origins]) return wf
def list_magmoms(list_struc): return [ CollinearMagneticStructureAnalyzer(structure).magmoms for structure in list_struc ]
def list_types_of_magnetic_specie(list_struc): return [ CollinearMagneticStructureAnalyzer(structure).types_of_magnetic_specie for structure in list_struc ]
def list_number_of_unique_magnetic_sites(list_struc): return [ CollinearMagneticStructureAnalyzer( structure).number_of_unique_magnetic_sites() for structure in list_struc ]
def get_commensurate_orderings(magnetic_structures, energies): """Generate supercells for static calculations. From the ground state magnetic ordering and first excited state, generate supercells with magnetic orderings. If the gs is FM, match supercells to the 1st es. If the gs is AFM, FM is included by default, 1st es may not be. If the gs is FiM, 1st es may not be captured. Args: magnetic_structures (list): Structures. energies (list): Energies per atom. Returns: matched_structures (list): Commensurate supercells for static calculations. TODO: * Only consider orderings with |S_i| = ground state * Constrain noncollinear magmoms """ # Sort by energies ordered_structures = [ s for _, s in sorted(zip(energies, magnetic_structures), reverse=False) ] ordered_energies = sorted(energies, reverse=False) # Ground state and 1st excited state gs_struct = ordered_structures[0] es_struct = ordered_structures[1] cmsa = CollinearMagneticStructureAnalyzer(es_struct, threshold=0.0, threshold_nonmag=1.0, make_primitive=False) es_ordering = cmsa.ordering.value es_struct = cmsa.structure cmsa = CollinearMagneticStructureAnalyzer(gs_struct, threshold=0.0, threshold_nonmag=1.0, make_primitive=False) gs_ordering = cmsa.ordering.value gs_struct = cmsa.structure # FM gs will always be commensurate so we match to the 1st es if gs_ordering == "FM": enum_struct = es_struct fm_moments = np.array(gs_struct.site_properties["magmom"]) fm_moment = np.mean(fm_moments[np.nonzero(fm_moments)]) gs_magmoms = [fm_moment for m in es_struct.site_properties["magmom"]] elif gs_ordering in ["FiM", "AFM"]: enum_struct = gs_struct gs_magmoms = [abs(m) for m in gs_struct.site_properties["magmom"]] mse = MagneticStructureEnumerator( enum_struct, strategies=("ferromagnetic", "antiferromagnetic"), automatic=False, transformation_kwargs={ "min_cell_size": 1, "max_cell_size": 2, "check_ordered_symmetry": False, }, ) # Enumerator bookkeeping input_index = mse.input_index ordered_structure_origins = mse.ordered_structure_origins matched_structures = [] sm = StructureMatcher(primitive_cell=False, attempt_supercell=True, comparator=ElementComparator()) # Get commensurate supercells for s in mse.ordered_structures: try: s2 = sm.get_s2_like_s1(enum_struct, s) except: s2 = None if s2 is not None: # Standardize magnetic structure cmsa = CollinearMagneticStructureAnalyzer(s2, threshold=0.0, make_primitive=False) s2 = cmsa.structure matched_structures.append(s2) # Find the gs ordering in the enumerated supercells cmsa = CollinearMagneticStructureAnalyzer(gs_struct, threshold=0.0, make_primitive=False) enum_index = [cmsa.matches_ordering(s) for s in matched_structures].index(True) # Enforce all magmom magnitudes to match the gs for s in matched_structures: ms = s.site_properties["magmom"] magmoms = [np.sign(m1) * m2 for m1, m2 in zip(ms, gs_magmoms)] s.add_site_property("magmom", magmoms) return matched_structures, input_index, ordered_structure_origins
def analyze_pymatgen_structure( pymatgen_structure, mark_muon=True, moment_to_polarization=True ): """ Convert pymatgen structure to aiida usable, correctly setting magnetic structure and atoms name. Optionally labels muon as 'No'. returns: structure with kind names according to magnetic labels. spin type: 1, non magnetic; 2, collinear; 4 non-collinear; dictionary with magnetic_elements_kind: for collinear case: {'Cr': {'Cr1': 1.0, 'Cr2': -1.0}} for non-collinear care, like this: {'Cr': {'Cr1': array([0. , 1.73205081, 2. ]), 'Cr2': array([-1.5 , -0.8660254, 2. ]), 'Cr3': array([ 1.5 , -0.8660254, 2. ]), 'Cr4': array([-1.5 , 0.8660254, 2. ]), 'Cr5': array([ 0. , -1.73205081, 2. ]), 'Cr6': array([1.5 , 0.8660254, 2. ])} } Parameters ---------- pymatgen_structure : Structure object A (magnetic or not) structure containing impurity in form of Pymatgen Structure object mark_muon : bool Specify the interstitial impurity properties. Default=True moment_to_polarization : bool If True to scale moment to maximum spin polarizations. Default=True Returns : tuple ------- pymatgen_structure : Structure object A pymatgen structure with modified type of species/elements has_spin : int spin type that represent the magnetic ordering type magnetic_elements_kind : dict A dictionary of type magnetic ions and their values """ # analyze magnetism magnetic_structure = CollinearMagneticStructureAnalyzer(pymatgen_structure, make_primitive=False) has_spin = 0 if magnetic_structure.is_magnetic: if magnetic_structure.is_collinear: has_spin=2 else: has_spin=4 else: has_spin=1 # check and force a collinear structure if has_spin == 4: ncol_to_col = check_and_force_collinear(pymatgen_structure) if ncol_to_col == True: has_spin = 2 # dont care if it is noncollinear #if brute_force_to_collinear: # has_spin = 2 # generate collinear spin structure if (has_spin in (1,2)): structure_with_spin = magnetic_structure.get_structure_with_spin() else: structure_with_spin = pymatgen_structure # collect magnetic elements by name. For each of them create kinds kind_values = [] magnetic_elements_kinds = {} n_sites = len(structure_with_spin) for s_idx, site in enumerate(structure_with_spin): # check spin and element name. Information is in slightly different places spin = site.specie.spin if has_spin in (1,2) else site.magmom.moment element = site.specie.element.symbol if has_spin in (1,2) else site.specie.symbol # scale moment according to max spin polarization if moment_to_polarization: if site.specie.block == 'p': spin /= 3. elif site.specie.block == 'd': spin /= 5. elif site.specie.block == 'f': spin /= 7. kind_name = None if not np.allclose(np.abs(spin), 0.0): # checks if element was already found to be magnetic in a previous site # otherwise return an empty dictionary to be filled with the information # of this site kinds_for_element = magnetic_elements_kinds.get(element, {}) # If the spin of this site is for this element is the same we found # previously, just give it the same kind, otherwise add it as # a new kind for this element type. for kind, kind_spin in kinds_for_element.items(): if np.allclose (spin, kind_spin): kind_name = kind break else: kind_name = '{}{}'.format(element, len(kinds_for_element)+1) kinds_for_element[kind_name] = spin # store the updated list of kinds for this element in the full dictionary. magnetic_elements_kinds[element] = kinds_for_element kind_values.append(kind_name) # last value of element checked from for loop...yeah, no namespaces in python if mark_muon and element == 'H': kind_values[-1] = 'No' # Finally return structre, type of magnetic order # and a dictionary for magnetic elements, where all the kinds are reported together # with the value of the spin. # just to make non magnetic calculations #if non_magnetic_calculation: # has_spin = 1; #magnetic_elements_kinds = {} return (pymatgen_structure.copy(site_properties={'kind_name': kind_values}), has_spin, magnetic_elements_kinds )