예제 #1
0
    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)
예제 #2
0
def list_ordering(list_struc):
    return [
        CollinearMagneticStructureAnalyzer(structure).ordering
        for structure in list_struc
    ]
예제 #3
0
    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
예제 #4
0
def list_magmoms(list_struc):
    return [
        CollinearMagneticStructureAnalyzer(structure).magmoms
        for structure in list_struc
    ]
예제 #5
0
def list_types_of_magnetic_specie(list_struc):
    return [
        CollinearMagneticStructureAnalyzer(structure).types_of_magnetic_specie
        for structure in list_struc
    ]
예제 #6
0
def list_number_of_unique_magnetic_sites(list_struc):
    return [
        CollinearMagneticStructureAnalyzer(
            structure).number_of_unique_magnetic_sites()
        for structure in list_struc
    ]
예제 #7
0
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
예제 #8
0
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 )