Пример #1
0
    def tag_structure(
        self,
        structure: Structure,
        calculate_valences: bool = True,
        guesstimate_spin: bool = False,
        op_threshold: float = 0.1,
    ) -> Structure:
        """
        Convenience method, uses get_analysis_and_structure method.
        Add a "possible_jt_active" site property on Structure.

        Args:
            structure: input structure
            calculate_valences: whether to attempt to calculate valences or not, structure
                should have oxidation states to perform analysis (Default value = True)
            guesstimate_spin: whether to guesstimate spin state from magnetic moments
                or not, use with caution (Default value = False)
            op_threshold: threshold for order parameter above which to consider site
                to match an octahedral or tetrahedral motif, since Jahn-Teller structures
                can often be
                quite distorted, this threshold is smaller than one might expect

        Returns:
            Decorated Structure, will be in primitive setting.

        """
        try:
            analysis, structure = self.get_analysis_and_structure(
                structure,
                calculate_valences=calculate_valences,
                guesstimate_spin=guesstimate_spin,
                op_threshold=op_threshold,
            )
            jt_sites = [False] * len(structure)
            if analysis["active"]:
                for site in analysis["sites"]:
                    for index in site["site_indices"]:
                        jt_sites[index] = True
                        structure.add_site_property("possible_jt_active", jt_sites)
            return structure
        except Exception as e:
            warnings.warn(
                "Error analyzing {}: {}".format(
                    structure.composition.reduced_formula, e
                )
            )
            return structure
Пример #2
0
def analyze(topology: Structure, skin: float = 5e-3) -> List[Molecule]:
    # containers for the output
    fragments = []
    # we iterate over non-dummies
    dmat = topology.distance_matrix
    dummies = numpy.array(topology.indices_from_symbol("X"))
    not_dummies = numpy.array(
        [i for i in range(len(topology)) if i not in dummies])
    # initialize and set tags
    tags = numpy.zeros(len(topology), dtype=int)
    tags[dummies] = dummies + 1
    topology.add_site_property(property_name="tags", values=tags)
    # TODO : site properties
    # get the distances between centers and connections
    distances = dmat[not_dummies][:, dummies]
    coordinations = numpy.array(topology.atomic_numbers)[not_dummies]
    partitions = numpy.argsort(distances, axis=1)
    for center_idx, best_dummies in enumerate(partitions):
        coordination = coordinations[center_idx]
        if coordination < len(best_dummies):
            best_dummies = best_dummies[:coordination]
        cutoff = distances[center_idx][best_dummies].max() + skin
        # now extract the corresponding fragment
        fragment_center = topology.sites[not_dummies[center_idx]]
        fragment_sites = topology.get_neighbors(fragment_center, r=cutoff)
        # some topologies in the RCSR have a crazy size
        # we ignore them with the heat of a thousand suns
        assert len(
            fragment_sites) <= 12, "Fragment size larger than limit of 12."
        # store as molecule to use the point group analysis
        fragment = Molecule.from_sites(
            fragment_sites)  #, charge=1, spin_multiplicity=1)
        # this is needed because X have no mass, leading to a
        # symmetrization error.
        fragment.replace_species({"X": "He"})
        pg = PointGroupAnalyzer(fragment, tolerance=0.1)
        # from pymatgen.io.ase import AseAtomsAdaptor
        # view(AseAtomsAdaptor.get_atoms(fragment))
        if pg.sch_symbol == "C1":
            raise ("NoSymm")
        fragment.replace_species({"He": "X"})
        fragments.append(Fragment(atoms=fragment, symmetry=pg, name="slot"))
    return fragments
Пример #3
0
class StructureTest(PymatgenTest):

    def setUp(self):
        coords = list()
        coords.append([0, 0, 0])
        coords.append([0.75, 0.5, 0.75])
        lattice = Lattice([[3.8401979337, 0.00, 0.00],
                           [1.9200989668, 3.3257101909, 0.00],
                           [0.00, -2.2171384943, 3.1355090603]])
        self.structure = Structure(lattice, ["Si", "Si"], coords)

    def test_mutable_sequence_methods(self):
        s = self.structure
        s[0] = "Fe"
        self.assertEqual(s.formula, "Fe1 Si1")
        s[0] = "Fe", [0.5, 0.5, 0.5]
        self.assertEqual(s.formula, "Fe1 Si1")
        self.assertArrayAlmostEqual(s[0].frac_coords, [0.5, 0.5, 0.5])
        s.reverse()
        self.assertEqual(s[0].specie, Element("Si"))
        self.assertArrayAlmostEqual(s[0].frac_coords, [0.75, 0.5, 0.75])
        s[0] = {"Mn": 0.5}
        self.assertEqual(s.formula, "Mn0.5 Fe1")
        del s[1]
        self.assertEqual(s.formula, "Mn0.5")
        s[0] = "Fe", [0.9, 0.9, 0.9], {"magmom": 5}
        self.assertEqual(s.formula, "Fe1")
        self.assertEqual(s[0].magmom, 5)

    def test_non_hash(self):
        self.assertRaises(TypeError, dict, [(self.structure, 1)])

    def test_sort(self):
        s = self.structure
        s[0] = "F"
        s.sort()
        self.assertEqual(s[0].species_string, "Si")
        self.assertEqual(s[1].species_string, "F")
        s.sort(key=lambda site: site.species_string)
        self.assertEqual(s[0].species_string, "F")
        self.assertEqual(s[1].species_string, "Si")
        s.sort(key=lambda site: site.species_string, reverse=True)
        self.assertEqual(s[0].species_string, "Si")
        self.assertEqual(s[1].species_string, "F")

    def test_append_insert_remove_replace(self):
        s = self.structure
        s.insert(1, "O", [0.5, 0.5, 0.5])
        self.assertEqual(s.formula, "Si2 O1")
        self.assertTrue(s.ntypesp == 2)
        self.assertTrue(s.symbol_set == ("Si", "O"))
        self.assertTrue(s.indices_from_symbol("Si") == (0,2))
        self.assertTrue(s.indices_from_symbol("O") == (1,))
        del s[2]
        self.assertEqual(s.formula, "Si1 O1")
        self.assertTrue(s.indices_from_symbol("Si") == (0,))
        self.assertTrue(s.indices_from_symbol("O") == (1,))
        s.append("N", [0.25, 0.25, 0.25])
        self.assertEqual(s.formula, "Si1 N1 O1")
        self.assertTrue(s.ntypesp == 3)
        self.assertTrue(s.symbol_set == ("Si", "O", "N"))
        self.assertTrue(s.indices_from_symbol("Si") == (0,))
        self.assertTrue(s.indices_from_symbol("O") == (1,))
        self.assertTrue(s.indices_from_symbol("N") == (2,))
        s[0] = "Ge"
        self.assertEqual(s.formula, "Ge1 N1 O1")
        self.assertTrue(s.symbol_set == ("Ge", "O", "N"))
        s.replace_species({"Ge": "Si"})
        self.assertEqual(s.formula, "Si1 N1 O1")
        self.assertTrue(s.ntypesp == 3)

        s.replace_species({"Si": {"Ge": 0.5, "Si": 0.5}})
        self.assertEqual(s.formula, "Si0.5 Ge0.5 N1 O1")
        #this should change the .5Si .5Ge sites to .75Si .25Ge
        s.replace_species({"Ge": {"Ge": 0.5, "Si": 0.5}})
        self.assertEqual(s.formula, "Si0.75 Ge0.25 N1 O1")

        # In this case, s.ntypesp is ambiguous.
        # for the time being, we raise AttributeError.
        with self.assertRaises(AttributeError):
            s.ntypesp

        s.remove_species(["Si"])
        self.assertEqual(s.formula, "Ge0.25 N1 O1")

        s.remove_sites([1, 2])
        self.assertEqual(s.formula, "Ge0.25")

    def test_add_site_property(self):
        s = self.structure
        s.add_site_property("charge", [4.1, -5])
        self.assertEqual(s[0].charge, 4.1)
        self.assertEqual(s[1].charge, -5)
        s.add_site_property("magmom", [3, 2])
        self.assertEqual(s[0].charge, 4.1)
        self.assertEqual(s[0].magmom, 3)

    def test_propertied_structure(self):
        #Make sure that site properties are set to None for missing values.
        s = self.structure
        s.add_site_property("charge", [4.1, -5])
        s.append("Li", [0.3, 0.3 ,0.3])
        self.assertEqual(len(s.site_properties["charge"]), 3)

    def test_perturb(self):
        d = 0.1
        pre_perturbation_sites = self.structure.sites[:]
        self.structure.perturb(distance=d)
        post_perturbation_sites = self.structure.sites

        for i, x in enumerate(pre_perturbation_sites):
            self.assertAlmostEqual(x.distance(post_perturbation_sites[i]), d,
                                   3, "Bad perturbation distance")

    def test_add_oxidation_states(self):
        oxidation_states = {"Si": -4}
        self.structure.add_oxidation_state_by_element(oxidation_states)
        for site in self.structure:
            for k in site.species_and_occu.keys():
                self.assertEqual(k.oxi_state, oxidation_states[k.symbol],
                                 "Wrong oxidation state assigned!")
        oxidation_states = {"Fe": 2}
        self.assertRaises(ValueError,
                          self.structure.add_oxidation_state_by_element,
                          oxidation_states)
        self.structure.add_oxidation_state_by_site([2, -4])
        self.assertEqual(self.structure[0].specie.oxi_state, 2)
        self.assertRaises(ValueError,
                          self.structure.add_oxidation_state_by_site,
                          [1])

    def test_remove_oxidation_states(self):
        co_elem = Element("Co")
        o_elem = Element("O")
        co_specie = Specie("Co", 2)
        o_specie = Specie("O", -2)
        coords = list()
        coords.append([0, 0, 0])
        coords.append([0.75, 0.5, 0.75])
        lattice = Lattice.cubic(10)
        s_elem = Structure(lattice, [co_elem, o_elem], coords)
        s_specie = Structure(lattice, [co_specie, o_specie], coords)
        s_specie.remove_oxidation_states()
        self.assertEqual(s_elem, s_specie, "Oxidation state remover "
                                           "failed")

    def test_apply_operation(self):
        op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90)
        s = self.structure.copy()
        s.apply_operation(op)
        self.assertArrayAlmostEqual(
            s.lattice.matrix,
            [[0.000000, 3.840198, 0.000000],
             [-3.325710, 1.920099, 0.000000],
             [2.217138, -0.000000, 3.135509]], 5)

        op = SymmOp([[1, 1, 0, 0.5], [1, 0, 0, 0.5], [0, 0, 1, 0.5],
                     [0, 0, 0, 1]])
        s = self.structure.copy()
        s.apply_operation(op, fractional=True)
        self.assertArrayAlmostEqual(
            s.lattice.matrix,
            [[5.760297, 3.325710, 0.000000],
             [3.840198, 0.000000, 0.000000],
             [0.000000, -2.217138, 3.135509]], 5)

    def test_apply_strain(self):
        s = self.structure
        initial_coord = s[1].coords
        s.apply_strain(0.01)
        self.assertAlmostEqual(
            s.lattice.abc,
            (3.8785999130369997, 3.878600984287687, 3.8785999130549516))
        self.assertArrayAlmostEqual(s[1].coords, initial_coord * 1.01)
        a1, b1, c1 = s.lattice.abc
        s.apply_strain([0.1, 0.2, 0.3])
        a2, b2, c2 = s.lattice.abc
        self.assertAlmostEqual(a2 / a1, 1.1)
        self.assertAlmostEqual(b2 / b1, 1.2)
        self.assertAlmostEqual(c2 / c1, 1.3)

    def test_scale_lattice(self):
        initial_coord = self.structure[1].coords
        self.structure.scale_lattice(self.structure.volume * 1.01 ** 3)
        self.assertArrayAlmostEqual(
            self.structure.lattice.abc,
            (3.8785999130369997, 3.878600984287687, 3.8785999130549516))
        self.assertArrayAlmostEqual(self.structure[1].coords,
            initial_coord * 1.01)

    def test_translate_sites(self):
        self.structure.translate_sites([0, 1], [0.5, 0.5, 0.5],
                                       frac_coords=True)
        self.assertArrayEqual(self.structure.frac_coords[0],
                              [0.5, 0.5, 0.5])

        self.structure.translate_sites([0], [0.5, 0.5, 0.5],
                                       frac_coords=False)
        self.assertArrayAlmostEqual(self.structure.cart_coords[0],
                                    [3.38014845, 1.05428585, 2.06775453])

        self.structure.translate_sites([0], [0.5, 0.5, 0.5],
                                       frac_coords=True, to_unit_cell=False)
        self.assertArrayAlmostEqual(self.structure.frac_coords[0],
                                    [1.00187517, 1.25665291, 1.15946374])

    def test_mul(self):
        self.structure *= [2, 1, 1]
        self.assertEqual(self.structure.formula, "Si4")
        s = [2, 1, 1] * self.structure
        self.assertEqual(s.formula, "Si8")
        self.assertIsInstance(s, Structure)
        s = self.structure * [[1, 0, 0], [2, 1, 0], [0, 0, 2]]
        self.assertEqual(s.formula, "Si8")
        self.assertArrayAlmostEqual(s.lattice.abc,
                                    [7.6803959, 17.5979979, 7.6803959])

    def test_make_supercell(self):
        self.structure.make_supercell([2, 1, 1])
        self.assertEqual(self.structure.formula, "Si4")
        self.structure.make_supercell([[1, 0, 0], [2, 1, 0], [0, 0, 1]])
        self.assertEqual(self.structure.formula, "Si4")
        self.structure.make_supercell(2)
        self.assertEqual(self.structure.formula, "Si32")
        self.assertArrayAlmostEqual(self.structure.lattice.abc,
                                    [15.360792, 35.195996, 7.680396], 5)

    def test_disordered_supercell_primitive_cell(self):
        l = Lattice.cubic(2)
        f = [[0.5, 0.5, 0.5]]
        sp = [{'Si': 0.54738}]
        s = Structure(l, sp, f)
        #this supercell often breaks things
        s.make_supercell([[0,-1,1],[-1,1,0],[1,1,1]])
        self.assertEqual(len(s.get_primitive_structure()), 1)

    def test_another_supercell(self):
        #this is included b/c for some reason the old algo was failing on it
        s = self.structure.copy()
        s.make_supercell([[0, 2, 2], [2, 0, 2], [2, 2, 0]])
        self.assertEqual(s.formula, "Si32")
        s = self.structure.copy()
        s.make_supercell([[0, 2, 0], [1, 0, 0], [0, 0, 1]])
        self.assertEqual(s.formula, "Si4")

    def test_to_from_dict(self):
        d = self.structure.as_dict()
        s2 = Structure.from_dict(d)
        self.assertEqual(type(s2), Structure)

    def test_to_from_file_string(self):
        for fmt in ["cif", "json", "poscar", "cssr", "yaml", "xsf"]:
            s = self.structure.to(fmt=fmt)
            self.assertIsNotNone(s)
            ss = Structure.from_str(s, fmt=fmt)
            self.assertArrayAlmostEqual(
                ss.lattice.lengths_and_angles,
                self.structure.lattice.lengths_and_angles, decimal=5)
            self.assertArrayAlmostEqual(ss.frac_coords,
                                        self.structure.frac_coords)
            self.assertIsInstance(ss, Structure)

        self.structure.to(filename="POSCAR.testing")
        self.assertTrue(os.path.exists("POSCAR.testing"))
        os.remove("POSCAR.testing")

        self.structure.to(filename="structure_testing.json")
        self.assertTrue(os.path.exists("structure_testing.json"))
        s = Structure.from_file("structure_testing.json")
        self.assertEqual(s, self.structure)
        os.remove("structure_testing.json")

    def test_from_spacegroup(self):
        s1 = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Li", "O"],
                                       [[0.25, 0.25, 0.25], [0, 0, 0]])
        self.assertEqual(s1.formula, "Li8 O4")
        s2 = Structure.from_spacegroup(225, Lattice.cubic(3), ["Li", "O"],
                                       [[0.25, 0.25, 0.25], [0, 0, 0]])
        self.assertEqual(s1, s2)

        s2 = Structure.from_spacegroup(225, Lattice.cubic(3), ["Li", "O"],
                                       [[0.25, 0.25, 0.25], [0, 0, 0]],
                                       site_properties={"charge": [1, -2]})
        self.assertEqual(sum(s2.site_properties["charge"]), 0)

        s = Structure.from_spacegroup("Pm-3m", Lattice.cubic(3), ["Cs", "Cl"],
                                      [[0, 0, 0], [0.5, 0.5, 0.5]])
        self.assertEqual(s.formula, "Cs1 Cl1")

        self.assertRaises(ValueError, Structure.from_spacegroup,
                          "Pm-3m", Lattice.tetragonal(1, 3), ["Cs", "Cl"],
                          [[0, 0, 0], [0.5, 0.5, 0.5]])

    def test_merge_sites(self):
        species = [{'Ag': 0.5}, {'Cl': 0.25}, {'Cl': 0.1},
                   {'Ag': 0.5}, {'F': 0.15}, {'F': 0.1}]
        coords = [[0, 0, 0], [0.5, 0.5, 0.5], [0.5, 0.5, 0.5],
                  [0, 0, 0], [0.5, 0.5, 1.501], [0.5, 0.5, 1.501]]
        s = Structure(Lattice.cubic(1), species, coords)
        s.merge_sites()
        self.assertEqual(s[0].specie.symbol, 'Ag')
        self.assertEqual(s[1].species_and_occu,
                         Composition({'Cl': 0.35, 'F': 0.25}))
        self.assertArrayAlmostEqual(s[1].frac_coords, [.5, .5, .5005])

    def test_properties(self):
        self.assertEqual(self.structure.num_sites, len(self.structure))
        self.structure.make_supercell(2)
        self.structure[1] = "C"
        sites = list(self.structure.group_by_types())
        self.assertEqual(sites[-1].specie.symbol, "C")
        self.structure.add_oxidation_state_by_element({"Si": 4, "C": 2})
        self.assertEqual(self.structure.charge, 62)

    def test_init_error(self):
        self.assertRaises(StructureError, Structure, Lattice.cubic(3), ["Si"], [[0, 0, 0], [0.5, 0.5, 0.5]])

    def test_from_sites(self):
        self.structure.add_site_property("hello", [1, 2])
        s = Structure.from_sites(self.structure, to_unit_cell=True)
        self.assertEqual(s.site_properties["hello"][1], 2)

    def test_magic(self):
        s = Structure.from_sites(self.structure)
        self.assertEqual(s, self.structure)
        self.assertNotEqual(s, None)
        s.apply_strain(0.5)
        self.assertNotEqual(s, self.structure)
        self.assertNotEqual(self.structure * 2, self.structure)
Пример #4
0
    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
Пример #5
0
class StructureTest(PymatgenTest):
    def setUp(self):
        coords = list()
        coords.append([0, 0, 0])
        coords.append([0.75, 0.5, 0.75])
        lattice = Lattice([[3.8401979337, 0.00, 0.00],
                           [1.9200989668, 3.3257101909, 0.00],
                           [0.00, -2.2171384943, 3.1355090603]])
        self.structure = Structure(lattice, ["Si", "Si"], coords)

    def test_mutable_sequence_methods(self):
        s = self.structure
        s[0] = "Fe"
        self.assertEqual(s.formula, "Fe1 Si1")
        s[0] = "Fe", [0.5, 0.5, 0.5]
        self.assertEqual(s.formula, "Fe1 Si1")
        self.assertArrayAlmostEqual(s[0].frac_coords, [0.5, 0.5, 0.5])
        s.reverse()
        self.assertEqual(s[0].specie, Element("Si"))
        self.assertArrayAlmostEqual(s[0].frac_coords, [0.75, 0.5, 0.75])
        s[0] = {"Mn": 0.5}
        self.assertEqual(s.formula, "Mn0.5 Fe1")
        del s[1]
        self.assertEqual(s.formula, "Mn0.5")
        s[0] = "Fe", [0.9, 0.9, 0.9], {"magmom": 5}
        self.assertEqual(s.formula, "Fe1")
        self.assertEqual(s[0].magmom, 5)

        # Test atomic replacement.
        s["Fe"] = "Mn"
        self.assertEqual(s.formula, "Mn1")

        # Test slice replacement.
        s = PymatgenTest.get_structure("Li2O")
        s[1:3] = "S"
        self.assertEqual(s.formula, "Li1 S2")

    def test_non_hash(self):
        self.assertRaises(TypeError, dict, [(self.structure, 1)])

    def test_sort(self):
        s = self.structure
        s[0] = "F"
        s.sort()
        self.assertEqual(s[0].species_string, "Si")
        self.assertEqual(s[1].species_string, "F")
        s.sort(key=lambda site: site.species_string)
        self.assertEqual(s[0].species_string, "F")
        self.assertEqual(s[1].species_string, "Si")
        s.sort(key=lambda site: site.species_string, reverse=True)
        self.assertEqual(s[0].species_string, "Si")
        self.assertEqual(s[1].species_string, "F")

    def test_append_insert_remove_replace(self):
        s = self.structure
        s.insert(1, "O", [0.5, 0.5, 0.5])
        self.assertEqual(s.formula, "Si2 O1")
        self.assertTrue(s.ntypesp == 2)
        self.assertTrue(s.symbol_set == ("Si", "O"))
        self.assertTrue(s.indices_from_symbol("Si") == (0, 2))
        self.assertTrue(s.indices_from_symbol("O") == (1, ))
        del s[2]
        self.assertEqual(s.formula, "Si1 O1")
        self.assertTrue(s.indices_from_symbol("Si") == (0, ))
        self.assertTrue(s.indices_from_symbol("O") == (1, ))
        s.append("N", [0.25, 0.25, 0.25])
        self.assertEqual(s.formula, "Si1 N1 O1")
        self.assertTrue(s.ntypesp == 3)
        self.assertTrue(s.symbol_set == ("Si", "O", "N"))
        self.assertTrue(s.indices_from_symbol("Si") == (0, ))
        self.assertTrue(s.indices_from_symbol("O") == (1, ))
        self.assertTrue(s.indices_from_symbol("N") == (2, ))
        s[0] = "Ge"
        self.assertEqual(s.formula, "Ge1 N1 O1")
        self.assertTrue(s.symbol_set == ("Ge", "O", "N"))
        s.replace_species({"Ge": "Si"})
        self.assertEqual(s.formula, "Si1 N1 O1")
        self.assertTrue(s.ntypesp == 3)

        s.replace_species({"Si": {"Ge": 0.5, "Si": 0.5}})
        self.assertEqual(s.formula, "Si0.5 Ge0.5 N1 O1")
        #this should change the .5Si .5Ge sites to .75Si .25Ge
        s.replace_species({"Ge": {"Ge": 0.5, "Si": 0.5}})
        self.assertEqual(s.formula, "Si0.75 Ge0.25 N1 O1")

        # In this case, s.ntypesp is ambiguous.
        # for the time being, we raise AttributeError.
        with self.assertRaises(AttributeError):
            s.ntypesp

        s.remove_species(["Si"])
        self.assertEqual(s.formula, "Ge0.25 N1 O1")

        s.remove_sites([1, 2])
        self.assertEqual(s.formula, "Ge0.25")

    def test_add_site_property(self):
        s = self.structure
        s.add_site_property("charge", [4.1, -5])
        self.assertEqual(s[0].charge, 4.1)
        self.assertEqual(s[1].charge, -5)
        s.add_site_property("magmom", [3, 2])
        self.assertEqual(s[0].charge, 4.1)
        self.assertEqual(s[0].magmom, 3)

    def test_propertied_structure(self):
        #Make sure that site properties are set to None for missing values.
        s = self.structure
        s.add_site_property("charge", [4.1, -5])
        s.append("Li", [0.3, 0.3, 0.3])
        self.assertEqual(len(s.site_properties["charge"]), 3)

    def test_perturb(self):
        d = 0.1
        pre_perturbation_sites = self.structure.sites[:]
        self.structure.perturb(distance=d)
        post_perturbation_sites = self.structure.sites

        for i, x in enumerate(pre_perturbation_sites):
            self.assertAlmostEqual(x.distance(post_perturbation_sites[i]), d,
                                   3, "Bad perturbation distance")

    def test_add_oxidation_states(self):
        oxidation_states = {"Si": -4}
        self.structure.add_oxidation_state_by_element(oxidation_states)
        for site in self.structure:
            for k in site.species_and_occu.keys():
                self.assertEqual(k.oxi_state, oxidation_states[k.symbol],
                                 "Wrong oxidation state assigned!")
        oxidation_states = {"Fe": 2}
        self.assertRaises(ValueError,
                          self.structure.add_oxidation_state_by_element,
                          oxidation_states)
        self.structure.add_oxidation_state_by_site([2, -4])
        self.assertEqual(self.structure[0].specie.oxi_state, 2)
        self.assertRaises(ValueError,
                          self.structure.add_oxidation_state_by_site, [1])

    def test_remove_oxidation_states(self):
        co_elem = Element("Co")
        o_elem = Element("O")
        co_specie = Specie("Co", 2)
        o_specie = Specie("O", -2)
        coords = list()
        coords.append([0, 0, 0])
        coords.append([0.75, 0.5, 0.75])
        lattice = Lattice.cubic(10)
        s_elem = Structure(lattice, [co_elem, o_elem], coords)
        s_specie = Structure(lattice, [co_specie, o_specie], coords)
        s_specie.remove_oxidation_states()
        self.assertEqual(s_elem, s_specie, "Oxidation state remover " "failed")

    def test_apply_operation(self):
        op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90)
        s = self.structure.copy()
        s.apply_operation(op)
        self.assertArrayAlmostEqual(
            s.lattice.matrix,
            [[0.000000, 3.840198, 0.000000], [-3.325710, 1.920099, 0.000000],
             [2.217138, -0.000000, 3.135509]], 5)

        op = SymmOp([[1, 1, 0, 0.5], [1, 0, 0, 0.5], [0, 0, 1, 0.5],
                     [0, 0, 0, 1]])
        s = self.structure.copy()
        s.apply_operation(op, fractional=True)
        self.assertArrayAlmostEqual(
            s.lattice.matrix,
            [[5.760297, 3.325710, 0.000000], [3.840198, 0.000000, 0.000000],
             [0.000000, -2.217138, 3.135509]], 5)

    def test_apply_strain(self):
        s = self.structure
        initial_coord = s[1].coords
        s.apply_strain(0.01)
        self.assertAlmostEqual(
            s.lattice.abc,
            (3.8785999130369997, 3.878600984287687, 3.8785999130549516))
        self.assertArrayAlmostEqual(s[1].coords, initial_coord * 1.01)
        a1, b1, c1 = s.lattice.abc
        s.apply_strain([0.1, 0.2, 0.3])
        a2, b2, c2 = s.lattice.abc
        self.assertAlmostEqual(a2 / a1, 1.1)
        self.assertAlmostEqual(b2 / b1, 1.2)
        self.assertAlmostEqual(c2 / c1, 1.3)

    def test_scale_lattice(self):
        initial_coord = self.structure[1].coords
        self.structure.scale_lattice(self.structure.volume * 1.01**3)
        self.assertArrayAlmostEqual(
            self.structure.lattice.abc,
            (3.8785999130369997, 3.878600984287687, 3.8785999130549516))
        self.assertArrayAlmostEqual(self.structure[1].coords,
                                    initial_coord * 1.01)

    def test_translate_sites(self):
        self.structure.translate_sites([0, 1], [0.5, 0.5, 0.5],
                                       frac_coords=True)
        self.assertArrayEqual(self.structure.frac_coords[0], [0.5, 0.5, 0.5])

        self.structure.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=False)
        self.assertArrayAlmostEqual(self.structure.cart_coords[0],
                                    [3.38014845, 1.05428585, 2.06775453])

        self.structure.translate_sites([0], [0.5, 0.5, 0.5],
                                       frac_coords=True,
                                       to_unit_cell=False)
        self.assertArrayAlmostEqual(self.structure.frac_coords[0],
                                    [1.00187517, 1.25665291, 1.15946374])

    def test_mul(self):
        self.structure *= [2, 1, 1]
        self.assertEqual(self.structure.formula, "Si4")
        s = [2, 1, 1] * self.structure
        self.assertEqual(s.formula, "Si8")
        self.assertIsInstance(s, Structure)
        s = self.structure * [[1, 0, 0], [2, 1, 0], [0, 0, 2]]
        self.assertEqual(s.formula, "Si8")
        self.assertArrayAlmostEqual(s.lattice.abc,
                                    [7.6803959, 17.5979979, 7.6803959])

    def test_make_supercell(self):
        self.structure.make_supercell([2, 1, 1])
        self.assertEqual(self.structure.formula, "Si4")
        self.structure.make_supercell([[1, 0, 0], [2, 1, 0], [0, 0, 1]])
        self.assertEqual(self.structure.formula, "Si4")
        self.structure.make_supercell(2)
        self.assertEqual(self.structure.formula, "Si32")
        self.assertArrayAlmostEqual(self.structure.lattice.abc,
                                    [15.360792, 35.195996, 7.680396], 5)

    def test_disordered_supercell_primitive_cell(self):
        l = Lattice.cubic(2)
        f = [[0.5, 0.5, 0.5]]
        sp = [{'Si': 0.54738}]
        s = Structure(l, sp, f)
        #this supercell often breaks things
        s.make_supercell([[0, -1, 1], [-1, 1, 0], [1, 1, 1]])
        self.assertEqual(len(s.get_primitive_structure()), 1)

    def test_another_supercell(self):
        #this is included b/c for some reason the old algo was failing on it
        s = self.structure.copy()
        s.make_supercell([[0, 2, 2], [2, 0, 2], [2, 2, 0]])
        self.assertEqual(s.formula, "Si32")
        s = self.structure.copy()
        s.make_supercell([[0, 2, 0], [1, 0, 0], [0, 0, 1]])
        self.assertEqual(s.formula, "Si4")

    def test_to_from_dict(self):
        d = self.structure.as_dict()
        s2 = Structure.from_dict(d)
        self.assertEqual(type(s2), Structure)

    def test_to_from_abivars(self):
        """Test as_dict, from_dict with fmt == abivars."""
        d = self.structure.as_dict(fmt="abivars")
        s2 = Structure.from_dict(d, fmt="abivars")
        self.assertEqual(s2, self.structure)
        self.assertEqual(type(s2), Structure)

    def test_to_from_file_string(self):
        for fmt in ["cif", "json", "poscar", "cssr", "yaml", "xsf"]:
            s = self.structure.to(fmt=fmt)
            self.assertIsNotNone(s)
            ss = Structure.from_str(s, fmt=fmt)
            self.assertArrayAlmostEqual(
                ss.lattice.lengths_and_angles,
                self.structure.lattice.lengths_and_angles,
                decimal=5)
            self.assertArrayAlmostEqual(ss.frac_coords,
                                        self.structure.frac_coords)
            self.assertIsInstance(ss, Structure)

        self.structure.to(filename="POSCAR.testing")
        self.assertTrue(os.path.exists("POSCAR.testing"))
        os.remove("POSCAR.testing")

        self.structure.to(filename="structure_testing.json")
        self.assertTrue(os.path.exists("structure_testing.json"))
        s = Structure.from_file("structure_testing.json")
        self.assertEqual(s, self.structure)
        os.remove("structure_testing.json")

    def test_from_spacegroup(self):
        s1 = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Li", "O"],
                                       [[0.25, 0.25, 0.25], [0, 0, 0]])
        self.assertEqual(s1.formula, "Li8 O4")
        s2 = Structure.from_spacegroup(225, Lattice.cubic(3), ["Li", "O"],
                                       [[0.25, 0.25, 0.25], [0, 0, 0]])
        self.assertEqual(s1, s2)

        s2 = Structure.from_spacegroup(225,
                                       Lattice.cubic(3), ["Li", "O"],
                                       [[0.25, 0.25, 0.25], [0, 0, 0]],
                                       site_properties={"charge": [1, -2]})
        self.assertEqual(sum(s2.site_properties["charge"]), 0)

        s = Structure.from_spacegroup("Pm-3m", Lattice.cubic(3), ["Cs", "Cl"],
                                      [[0, 0, 0], [0.5, 0.5, 0.5]])
        self.assertEqual(s.formula, "Cs1 Cl1")

        self.assertRaises(ValueError, Structure.from_spacegroup, "Pm-3m",
                          Lattice.tetragonal(1, 3), ["Cs", "Cl"],
                          [[0, 0, 0], [0.5, 0.5, 0.5]])

        self.assertRaises(ValueError, Structure.from_spacegroup, "Pm-3m",
                          Lattice.cubic(3), ["Cs"],
                          [[0, 0, 0], [0.5, 0.5, 0.5]])

    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 test_merge_sites(self):
        species = [{
            'Ag': 0.5
        }, {
            'Cl': 0.25
        }, {
            'Cl': 0.1
        }, {
            'Ag': 0.5
        }, {
            'F': 0.15
        }, {
            'F': 0.1
        }]
        coords = [[0, 0, 0], [0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0, 0, 0],
                  [0.5, 0.5, 1.501], [0.5, 0.5, 1.501]]
        s = Structure(Lattice.cubic(1), species, coords)
        s.merge_sites(mode="s")
        self.assertEqual(s[0].specie.symbol, 'Ag')
        self.assertEqual(s[1].species_and_occu,
                         Composition({
                             'Cl': 0.35,
                             'F': 0.25
                         }))
        self.assertArrayAlmostEqual(s[1].frac_coords, [.5, .5, .5005])

        # Test for TaS2 with spacegroup 166 in 160 setting.
        l = Lattice.from_lengths_and_angles([3.374351, 3.374351, 20.308941],
                                            [90.000000, 90.000000, 120.000000])
        species = ["Ta", "S", "S"]
        coords = [[0.000000, 0.000000,
                   0.944333], [0.333333, 0.666667, 0.353424],
                  [0.666667, 0.333333, 0.535243]]
        tas2 = Structure.from_spacegroup(160, l, species, coords)
        assert len(tas2) == 13
        tas2.merge_sites(mode="d")
        assert len(tas2) == 9

        l = Lattice.from_lengths_and_angles([3.587776, 3.587776, 19.622793],
                                            [90.000000, 90.000000, 120.000000])
        species = ["Na", "V", "S", "S"]
        coords = [[0.333333, 0.666667,
                   0.165000], [0.000000, 0.000000, 0.998333],
                  [0.333333, 0.666667, 0.399394],
                  [0.666667, 0.333333, 0.597273]]
        navs2 = Structure.from_spacegroup(160, l, species, coords)
        assert len(navs2) == 18
        navs2.merge_sites(mode="d")
        assert len(navs2) == 12

    def test_properties(self):
        self.assertEqual(self.structure.num_sites, len(self.structure))
        self.structure.make_supercell(2)
        self.structure[1] = "C"
        sites = list(self.structure.group_by_types())
        self.assertEqual(sites[-1].specie.symbol, "C")
        self.structure.add_oxidation_state_by_element({"Si": 4, "C": 2})
        self.assertEqual(self.structure.charge, 62)

    def test_set_item(self):
        s = self.structure.copy()
        s[0] = "C"
        self.assertEqual(s.formula, "Si1 C1")
        s[(0, 1)] = "Ge"
        self.assertEqual(s.formula, "Ge2")
        s[0:2] = "Sn"
        self.assertEqual(s.formula, "Sn2")

        s = self.structure.copy()
        s["Si"] = "C"
        self.assertEqual(s.formula, "C2")
        s["C"] = "C0.25Si0.5"
        self.assertEqual(s.formula, "Si1 C0.5")
        s["C"] = "C0.25Si0.5"
        self.assertEqual(s.formula, "Si1.25 C0.125")

    def test_init_error(self):
        self.assertRaises(StructureError, Structure, Lattice.cubic(3), ["Si"],
                          [[0, 0, 0], [0.5, 0.5, 0.5]])

    def test_from_sites(self):
        self.structure.add_site_property("hello", [1, 2])
        s = Structure.from_sites(self.structure, to_unit_cell=True)
        self.assertEqual(s.site_properties["hello"][1], 2)

    def test_magic(self):
        s = Structure.from_sites(self.structure)
        self.assertEqual(s, self.structure)
        self.assertNotEqual(s, None)
        s.apply_strain(0.5)
        self.assertNotEqual(s, self.structure)
        self.assertNotEqual(self.structure * 2, self.structure)
Пример #6
0
    def _generate_transformations(
            self,
            structure: Structure) -> Dict[str, MagOrderingTransformation]:
        """The central problem with trying to enumerate magnetic orderings is
        that we have to enumerate orderings that might plausibly be magnetic
        ground states, while not enumerating orderings that are physically
        implausible. The problem is that it is not always obvious by e.g.
        symmetry arguments alone which orderings to prefer. Here, we use a
        variety of strategies (heuristics) to enumerate plausible orderings,
        and later discard any duplicates that might be found by multiple
        strategies. This approach is not ideal, but has been found to be
        relatively robust over a wide range of magnetic structures.

        Args:
          structure: A sanitized input structure (_sanitize_input_structure)
        Returns: A dict of a transformation class instance (values) and name of
        enumeration strategy (keys)

        Returns: dict of Transformations keyed by strategy

        """

        formula = structure.composition.reduced_formula
        transformations: Dict[str, MagOrderingTransformation] = {}

        # analyzer is used to obtain information on sanitized input
        analyzer = CollinearMagneticStructureAnalyzer(
            structure,
            default_magmoms=self.default_magmoms,
            overwrite_magmom_mode="replace_all",
        )

        if not analyzer.is_magnetic:
            raise ValueError(
                "Not detected as magnetic, add a new default magmom for the element you believe may be magnetic?"
            )

        # now we can begin to generate our magnetic orderings
        self.logger.info(f"Generating magnetic orderings for {formula}")

        mag_species_spin = analyzer.magnetic_species_and_magmoms
        types_mag_species = sorted(
            analyzer.types_of_magnetic_species,
            key=lambda sp: analyzer.default_magmoms.get(str(sp), 0),
            reverse=True,
        )
        num_mag_sites = analyzer.number_of_magnetic_sites
        num_unique_sites = analyzer.number_of_unique_magnetic_sites()

        # enumerations become too slow as number of unique sites (and thus
        # permutations) increase, 8 is a soft limit, this can be increased
        # but do so with care
        if num_unique_sites > self.max_unique_sites:
            raise ValueError(
                "Too many magnetic sites to sensibly perform enumeration.")

        # maximum cell size to consider: as a rule of thumb, if the primitive cell
        # contains a large number of magnetic sites, perhaps we only need to enumerate
        # within one cell, whereas on the other extreme if the primitive cell only
        # contains a single magnetic site, we have to create larger supercells
        if "max_cell_size" not in self.transformation_kwargs:
            # TODO: change to 8 / num_mag_sites ?
            self.transformation_kwargs["max_cell_size"] = max(
                1, int(4 / num_mag_sites))
        self.logger.info("Max cell size set to {}".format(
            self.transformation_kwargs["max_cell_size"]))

        # when enumerating ferrimagnetic structures, it's useful to detect
        # symmetrically distinct magnetic sites, since different
        # local environments can result in different magnetic order
        # (e.g. inverse spinels)
        # initially, this was done by co-ordination number, but is
        # now done by a full symmetry analysis
        sga = SpacegroupAnalyzer(structure)
        structure_sym = sga.get_symmetrized_structure()
        wyckoff = ["n/a"] * len(structure)
        for indices, symbol in zip(structure_sym.equivalent_indices,
                                   structure_sym.wyckoff_symbols):
            for index in indices:
                wyckoff[index] = symbol
        is_magnetic_sites = [
            site.specie in types_mag_species for site in structure
        ]
        # we're not interested in sites that we don't think are magnetic,
        # set these symbols to None to filter them out later
        wyckoff = [
            symbol if is_magnetic_site else "n/a"
            for symbol, is_magnetic_site in zip(wyckoff, is_magnetic_sites)
        ]
        structure.add_site_property("wyckoff", wyckoff)
        wyckoff_symbols = set(wyckoff) - {"n/a"}

        # if user doesn't specifically request ferrimagnetic orderings,
        # we apply a heuristic as to whether to attempt them or not
        if self.automatic:
            if ("ferrimagnetic_by_motif" not in self.strategies
                    and len(wyckoff_symbols) > 1
                    and len(types_mag_species) == 1):
                self.strategies += ["ferrimagnetic_by_motif"]

            if ("antiferromagnetic_by_motif" not in self.strategies
                    and len(wyckoff_symbols) > 1
                    and len(types_mag_species) == 1):
                self.strategies += ["antiferromagnetic_by_motif"]

            if "ferrimagnetic_by_species" not in self.strategies and len(
                    types_mag_species) > 1:
                self.strategies += ["ferrimagnetic_by_species"]

        # we start with a ferromagnetic ordering
        if "ferromagnetic" in self.strategies:
            # TODO: remove 0 spins !

            fm_structure = analyzer.get_ferromagnetic_structure()
            # store magmom as spin property, to be consistent with output from
            # other transformations
            fm_structure.add_spin_by_site(
                fm_structure.site_properties["magmom"])
            fm_structure.remove_site_property("magmom")

            # we now have our first magnetic ordering...
            self.ordered_structures.append(fm_structure)
            self.ordered_structure_origins.append("fm")

        # we store constraint(s) for each strategy first,
        # and then use each to perform a transformation later
        all_constraints: Dict[str, Any] = {}

        # ...to which we can add simple AFM cases first...
        if "antiferromagnetic" in self.strategies:

            constraint = MagOrderParameterConstraint(
                0.5,
                # TODO: update MagOrderParameterConstraint in
                # pymatgen to take types_mag_species directly
                species_constraints=list(map(str, types_mag_species)),
            )
            all_constraints["afm"] = [constraint]

            # allows for non-magnetic sublattices
            if len(types_mag_species) > 1:
                for sp in types_mag_species:
                    constraints = [
                        MagOrderParameterConstraint(
                            0.5, species_constraints=str(sp))
                    ]

                    all_constraints[f"afm_by_{sp}"] = constraints

        # ...and then we also try ferrimagnetic orderings by motif if a
        # single magnetic species is present...
        if "ferrimagnetic_by_motif" in self.strategies and len(
                wyckoff_symbols) > 1:

            # these orderings are AFM on one local environment, and FM on the rest
            for symbol in wyckoff_symbols:
                constraints = [
                    MagOrderParameterConstraint(0.5,
                                                site_constraint_name="wyckoff",
                                                site_constraints=symbol),
                    MagOrderParameterConstraint(
                        1.0,
                        site_constraint_name="wyckoff",
                        site_constraints=list(wyckoff_symbols - {symbol}),
                    ),
                ]

                all_constraints[f"ferri_by_motif_{symbol}"] = constraints

        # and also try ferrimagnetic when there are multiple magnetic species
        if "ferrimagnetic_by_species" in self.strategies:

            sp_list = [str(site.specie) for site in structure]
            num_sp = {sp: sp_list.count(str(sp)) for sp in types_mag_species}
            total_mag_sites = sum(num_sp.values())

            for sp in types_mag_species:
                # attempt via a global order parameter
                all_constraints[
                    f"ferri_by_{sp}"] = num_sp[sp] / total_mag_sites

                # attempt via afm on sp, fm on remaining species

                constraints = [
                    MagOrderParameterConstraint(0.5,
                                                species_constraints=str(sp)),
                    MagOrderParameterConstraint(
                        1.0,
                        species_constraints=list(
                            map(str,
                                set(types_mag_species) - {sp})),
                    ),
                ]

                all_constraints[f"ferri_by_{sp}_afm"] = constraints

        # ...and finally, we can try orderings that are AFM on one local
        # environment, and non-magnetic on the rest -- this is less common
        # but unless explicitly attempted, these states are unlikely to be found
        if "antiferromagnetic_by_motif" in self.strategies:

            for symbol in wyckoff_symbols:
                constraints = [
                    MagOrderParameterConstraint(0.5,
                                                site_constraint_name="wyckoff",
                                                site_constraints=symbol)
                ]

                all_constraints[f"afm_by_motif_{symbol}"] = constraints

        # and now construct all our transformations for each strategy
        transformations = {}
        for name, constraints in all_constraints.items():
            trans = MagOrderingTransformation(mag_species_spin,
                                              order_parameter=constraints,
                                              **self.transformation_kwargs)

            transformations[name] = trans

        return transformations
Пример #7
0
    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
Пример #8
0
    def _get_structure(self, data, primitive):
        """
        Generate structure from part of the cif.
        """
        def get_num_implicit_hydrogens(sym):
            num_h = {"Wat": 2, "wat": 2, "O-H": 1}
            return num_h.get(sym[:3], 0)

        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

        label_el_dict = {}

        for i in range(len(data["_atom_site_label"])):
            try:
                # If site type symbol exists, use it. Otherwise, we use the
                # label.
                symbol = self._parse_symbol(data["_atom_site_type_symbol"][i])

                label = data["_atom_site_label"][i]

                num_h = get_num_implicit_hydrogens(
                    data["_atom_site_type_symbol"][i])

            except KeyError:
                symbol = self._parse_symbol(data["_atom_site_label"][i])

                label = data["_atom_site_label"][i]

                num_h = get_num_implicit_hydrogens(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],
                                 np.array([0, 0, 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)
                comp_d = {el: occu}

                if num_h > 0:
                    comp_d["H"] = num_h
                comp = Composition(comp_d)
                if not match:
                    coord_to_species[coord] = comp
                    coord_to_magmoms[coord] = magmom
                else:
                    coord_to_species[match] += comp
                    # disordered magnetic not currently supported
                    coord_to_magmoms[match] = None

            label_el_dict[coord] = label

        sum_occu = [
            sum(c.values()) for c in coord_to_species.values() if
            not set(c.elements) == {Element("O"), Element("H")}
        ]
        if any([o > 1 for o in sum_occu]):
            msg = "Some occupancies (%s) sum to > 1! If they are within " \
                    "the tolerance, they will be rescaled." % str(sum_occu)
            warnings.warn(msg)
            self.errors.append(msg)

        allspecies = []
        allcoords = []
        allmagmoms = []
        allhydrogens = []
        alllabels = []

        # 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 comp, 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]

                #print(tmp_coords)
                labels = []
                for i in tmp_coords:
                    labels.append(label_el_dict[i])

                #print(labels)
                tmp_magmom = [
                    coord_to_magmoms[tmp_coord] for tmp_coord in tmp_coords
                ]
                if self.feature_flags["magcif"]:
                    coords, magmoms, coords_num = self._unique_coords(
                        tmp_coords, magmoms_in=tmp_magmom, lattice=lattice)
                else:
                    coords, magmoms, coords_num = self._unique_coords(
                        tmp_coords)

                if set(comp.elements) == {Element("O"), Element("H")}:
                    # O with implicit hydrogens
                    im_h = comp["H"]
                    species = Composition({"O": comp["O"]})
                else:
                    im_h = 0
                    species = comp

                allhydrogens.extend(len(coords) * [im_h])
                allcoords.extend(coords)
                allspecies.extend(len(coords) * [species])
                allmagmoms.extend(magmoms)

                for i in range(len(coords_num)):

                    alllabels.extend(coords_num[i] * [labels[i]])

            # 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):
            site_properties = dict()
            if any(allhydrogens):
                assert len(allhydrogens) == len(allcoords)
                site_properties["implicit_hydrogens"] = allhydrogens

            if self.feature_flags["magcif"]:
                site_properties["magmom"] = allmagmoms

            if len(site_properties) == 0:
                site_properties = None

            struct = Structure(lattice,
                               allspecies,
                               allcoords,
                               site_properties=site_properties)
            #struct = struct.get_sorted_structure()

            if primitive and self.feature_flags['magcif']:
                struct = struct.get_primitive_structure(use_site_props=True)
            elif primitive:
                struct = struct.get_primitive_structure()
                struct = struct.get_reduced_structure()
            struct.add_site_property("_atom_site_label", alllabels)
            return struct