def test_get_composition(self):
     comp = self.transformed_entry.composition
     expected_comp = Composition({
         DummySpecies("Xa"): 1,
         DummySpecies("Xb"): 1
     })
     self.assertEqual(comp, expected_comp, "Wrong composition!")
Exemple #2
0
 def test_init(self):
     self.specie1 = DummySpecies("X")
     self.assertRaises(ValueError, DummySpecies, "Xe")
     self.assertRaises(ValueError, DummySpecies, "Xec")
     self.assertRaises(ValueError, DummySpecies, "Vac")
     self.specie2 = DummySpecies("X", 2, {"spin": 3})
     self.assertEqual(self.specie2.spin, 3)
 def test_get_composition(self):
     comp = self.transformed_entry.composition
     expected_comp = Composition({
         DummySpecies("Xf"): 14 / 30,
         DummySpecies("Xg"): 1.0,
         DummySpecies("Xh"): 2 / 30
     })
     self.assertEqual(comp, expected_comp, "Wrong composition!")
Exemple #4
0
 def test_from_string(self):
     sp = DummySpecies.from_string("X")
     self.assertEqual(sp.oxi_state, 0)
     sp = DummySpecies.from_string("X2+")
     self.assertEqual(sp.oxi_state, 2)
     sp = DummySpecies.from_string("X2+spin=5")
     self.assertEqual(sp.oxi_state, 2)
     self.assertEqual(sp.spin, 5)
 def setUp(self):
     comp = Composition("LiFeO2")
     entry = PDEntry(comp, 53)
     self.transformed_entry = TransformedPDEntry(
         {
             DummySpecies("Xa"): 1,
             DummySpecies("Xb"): 1
         }, entry)
 def test_normalize(self):
     norm_entry = self.transformed_entry.normalize(mode="atom")
     expected_comp = Composition({
         DummySpecies("Xf"): 7 / 23,
         DummySpecies("Xg"): 15 / 23,
         DummySpecies("Xh"): 1 / 23
     })
     self.assertEqual(norm_entry.composition, expected_comp,
                      "Wrong composition!")
Exemple #7
0
 def test_from_string(self):
     sp = DummySpecies.from_string("X")
     self.assertEqual(sp.oxi_state, 0)
     sp = DummySpecies.from_string("X2+")
     self.assertEqual(sp.oxi_state, 2)
     self.assertEqual(sp.to_latex_string(), "X$^{2+}$")
     sp = DummySpecies.from_string("X2+spin=5")
     self.assertEqual(sp.oxi_state, 2)
     self.assertEqual(sp.spin, 5)
     self.assertEqual(sp.to_latex_string(), "X$^{2+}$")
     self.assertEqual(sp.to_html_string(), "X<sup>2+</sup>")
     self.assertEqual(sp.to_unicode_string(), "X²⁺")
Exemple #8
0
    def from_dict(cls, d, lattice=None):
        """
        Create PeriodicSite from dict representation.

        Args:
            d (dict): dict representation of PeriodicSite
            lattice: Optional lattice to override lattice specified in d.
                Useful for ensuring all sites in a structure share the same
                lattice.

        Returns:
            PeriodicSite
        """
        species = {}
        for sp_occu in d["species"]:
            if "oxidation_state" in sp_occu and Element.is_valid_symbol(
                    sp_occu["element"]):
                sp = Species.from_dict(sp_occu)
            elif "oxidation_state" in sp_occu:
                sp = DummySpecies.from_dict(sp_occu)
            else:
                sp = Element(sp_occu["element"])
            species[sp] = sp_occu["occu"]
        props = d.get("properties", None)
        if props is not None:
            for key in props.keys():
                props[key] = json.loads(json.dumps(props[key],
                                                   cls=MontyEncoder),
                                        cls=MontyDecoder)
        lattice = lattice if lattice else Lattice.from_dict(d["lattice"])
        return cls(species, d["abc"], lattice, properties=props)
Exemple #9
0
 def test_get_el_sp(self):
     self.assertEqual(get_el_sp("Fe2+"), Species("Fe", 2))
     self.assertEqual(get_el_sp("3"), Element.Li)
     self.assertEqual(get_el_sp("3.0"), Element.Li)
     self.assertEqual(get_el_sp("U"), Element.U)
     self.assertEqual(get_el_sp("X2+"), DummySpecies("X", 2))
     self.assertEqual(get_el_sp("Mn3+"), Species("Mn", 3))
Exemple #10
0
    def test_specie_cifwriter(self):
        si4 = Species("Si", 4)
        si3 = Species("Si", 3)
        n = DummySpecies("X", -3)
        coords = list()
        coords.append(np.array([0.5, 0.5, 0.5]))
        coords.append(np.array([0.75, 0.5, 0.75]))
        coords.append(np.array([0, 0, 0]))
        lattice = Lattice(
            np.array([
                [3.8401979337, 0.00, 0.00],
                [1.9200989668, 3.3257101909, 0.00],
                [0.00, -2.2171384943, 3.1355090603],
            ]))
        struct = Structure(lattice, [n, {si3: 0.5, n: 0.5}, si4], coords)
        writer = CifWriter(struct)
        ans = """# generated using pymatgen
data_X1.5Si1.5
_symmetry_space_group_name_H-M   'P 1'
_cell_length_a   3.84019793
_cell_length_b   3.84019899
_cell_length_c   3.84019793
_cell_angle_alpha   119.99999086
_cell_angle_beta   90.00000000
_cell_angle_gamma   60.00000914
_symmetry_Int_Tables_number   1
_chemical_formula_structural   X1.5Si1.5
_chemical_formula_sum   'X1.5 Si1.5'
_cell_volume   40.04479464
_cell_formula_units_Z   1
loop_
 _symmetry_equiv_pos_site_id
 _symmetry_equiv_pos_as_xyz
  1  'x, y, z'
loop_
 _atom_type_symbol
 _atom_type_oxidation_number
  X3-  -3.0
  Si3+  3.0
  Si4+  4.0
loop_
 _atom_site_type_symbol
 _atom_site_label
 _atom_site_symmetry_multiplicity
 _atom_site_fract_x
 _atom_site_fract_y
 _atom_site_fract_z
 _atom_site_occupancy
  X3-  X0  1  0.50000000  0.50000000  0.50000000  1
  X3-  X1  1  0.75000000  0.50000000  0.75000000  0.5
  Si3+  Si2  1  0.75000000  0.50000000  0.75000000  0.5
  Si4+  Si3  1  0.00000000  0.00000000  0.00000000  1
"""
        for l1, l2 in zip(str(writer).split("\n"), ans.split("\n")):
            self.assertEqual(l1.strip(), l2.strip())

        # test that mixed valence works properly
        s2 = Structure.from_str(ans, "cif")
        self.assertEqual(struct.composition, s2.composition)
    def setUp(self):
        comp = Composition("LiFeO2")
        entry = PDEntry(comp, 53)

        terminal_compositions = ["Li2O", "FeO", "LiO8"]
        terminal_compositions = [Composition(c) for c in terminal_compositions]

        sp_mapping = OrderedDict()
        for i, comp in enumerate(terminal_compositions):
            sp_mapping[comp] = DummySpecies("X" + chr(102 + i))

        self.transformed_entry = TransformedPDEntry(entry, sp_mapping)
Exemple #12
0
 def from_dict(cls, d: dict) -> "Site":
     """
     Create Site from dict representation
     """
     atoms_n_occu = {}
     for sp_occu in d["species"]:
         if "oxidation_state" in sp_occu and Element.is_valid_symbol(sp_occu["element"]):
             sp = Species.from_dict(sp_occu)
         elif "oxidation_state" in sp_occu:
             sp = DummySpecies.from_dict(sp_occu)
         else:
             sp = Element(sp_occu["element"])  # type: ignore
         atoms_n_occu[sp] = sp_occu["occu"]
     props = d.get("properties", None)
     if props is not None:
         for key in props.keys():
             props[key] = json.loads(json.dumps(props[key], cls=MontyEncoder), cls=MontyDecoder)
     return cls(atoms_n_occu, d["xyz"], properties=props)
Exemple #13
0
 def _sanitize_symbol(symbol):
     if symbol == "vacancy":
         symbol = DummySpecies("X_vacancy", oxidation_state=None)
     elif symbol == "X":
         symbol = DummySpecies("X", oxidation_state=None)
     return symbol
Exemple #14
0
 def test_sort(self):
     r = sorted([Element.Fe, DummySpecies("X")])
     self.assertEqual(r, [DummySpecies("X"), Element.Fe])
     self.assertTrue(DummySpecies("X", 3) < DummySpecies("X", 4))
Exemple #15
0
 def test_pickle(self):
     el1 = DummySpecies("X", 3)
     o = pickle.dumps(el1)
     self.assertEqual(el1, pickle.loads(o))
Exemple #16
0
    def structure_graph(self, include_critical_points=("bond", "ring", "cage")):
        """
        A StructureGraph object describing bonding information
        in the crystal.
        Args:
            include_critical_points: add DummySpecies for
            the critical points themselves, a list of
            "nucleus", "bond", "ring", "cage", set to None
            to disable

        Returns: a StructureGraph
        """

        structure = self.structure.copy()

        point_idx_to_struct_idx = {}
        if include_critical_points:
            # atoms themselves don't have field information
            # so set to 0
            for prop in ("ellipticity", "laplacian", "field"):
                structure.add_site_property(prop, [0] * len(structure))
            for idx, node in self.nodes.items():
                cp = self.critical_points[node["unique_idx"]]
                if cp.type.value in include_critical_points:
                    specie = DummySpecies(
                        "X{}cp".format(cp.type.value[0]), oxidation_state=None
                    )
                    structure.append(
                        specie,
                        node["frac_coords"],
                        properties={
                            "ellipticity": cp.ellipticity,
                            "laplacian": cp.laplacian,
                            "field": cp.field,
                        },
                    )
                    point_idx_to_struct_idx[idx] = len(structure) - 1

        edge_weight = "bond_length"
        edge_weight_units = "Å"

        sg = StructureGraph.with_empty_graph(
            structure,
            name="bonds",
            edge_weight_name=edge_weight,
            edge_weight_units=edge_weight_units,
        )

        edges = self.edges.copy()
        idx_to_delete = []
        # check for duplicate bonds
        for idx, edge in edges.items():
            unique_idx = self.nodes[idx]["unique_idx"]
            # only check edges representing bonds, not rings
            if self.critical_points[unique_idx].type == CriticalPointType.bond:
                if idx not in idx_to_delete:
                    for idx2, edge2 in edges.items():
                        if idx != idx2 and edge == edge2:
                            idx_to_delete.append(idx2)
                            warnings.warn(
                                "Duplicate edge detected, try re-running "
                                "critic2 with custom parameters to fix this. "
                                "Mostly harmless unless user is also "
                                "interested in rings/cages."
                            )
                            logger.debug(
                                "Duplicate edge between points {} (unique point {})"
                                "and {} ({}).".format(
                                    idx,
                                    self.nodes[idx]["unique_idx"],
                                    idx2,
                                    self.nodes[idx2]["unique_idx"],
                                )
                            )
        # and remove any duplicate bonds present
        for idx in idx_to_delete:
            del edges[idx]

        for idx, edge in edges.items():
            unique_idx = self.nodes[idx]["unique_idx"]
            # only add edges representing bonds, not rings
            if self.critical_points[unique_idx].type == CriticalPointType.bond:

                from_idx = edge["from_idx"]
                to_idx = edge["to_idx"]

                # have to also check bond is between nuclei if non-nuclear
                # attractors not in structure
                skip_bond = False
                if include_critical_points and "nnattr" not in include_critical_points:
                    from_type = self.critical_points[
                        self.nodes[from_idx]["unique_idx"]
                    ].type
                    to_type = self.critical_points[
                        self.nodes[from_idx]["unique_idx"]
                    ].type
                    skip_bond = (from_type != CriticalPointType.nucleus) or (
                        to_type != CriticalPointType.nucleus
                    )

                if not skip_bond:
                    from_lvec = edge["from_lvec"]
                    to_lvec = edge["to_lvec"]

                    relative_lvec = np.subtract(to_lvec, from_lvec)

                    # for edge case of including nnattrs in bonding graph when other critical
                    # points also included, indices may get mixed
                    struct_from_idx = point_idx_to_struct_idx.get(from_idx, from_idx)
                    struct_to_idx = point_idx_to_struct_idx.get(to_idx, to_idx)

                    weight = self.structure.get_distance(
                        struct_from_idx, struct_to_idx, jimage=relative_lvec
                    )

                    crit_point = self.critical_points[unique_idx]

                    edge_properties = {
                        "field": crit_point.field,
                        "laplacian": crit_point.laplacian,
                        "ellipticity": crit_point.ellipticity,
                        "frac_coords": self.nodes[idx]["frac_coords"],
                    }

                    sg.add_edge(
                        struct_from_idx,
                        struct_to_idx,
                        from_jimage=from_lvec,
                        to_jimage=to_lvec,
                        weight=weight,
                        edge_properties=edge_properties,
                    )

        return sg
Exemple #17
0
 def test_eq(self):
     self.assertFalse(DummySpecies("Xg") == DummySpecies("Xh"))
     self.assertFalse(DummySpecies("Xg") == DummySpecies("Xg", 3))
     self.assertTrue(DummySpecies("Xg", 3) == DummySpecies("Xg", 3))
Exemple #18
0
    def _gen_input_file(self):
        """
        Generate the necessary struct_enum.in file for enumlib. See enumlib
        documentation for details.
        """
        coord_format = "{:.6f} {:.6f} {:.6f}"
        # Using symmetry finder, get the symmetrically distinct sites.
        fitter = SpacegroupAnalyzer(self.structure, self.symm_prec)
        symmetrized_structure = fitter.get_symmetrized_structure()
        logger.debug("Spacegroup {} ({}) with {} distinct sites".format(
            fitter.get_space_group_symbol(),
            fitter.get_space_group_number(),
            len(symmetrized_structure.equivalent_sites),
        ))
        """
        Enumlib doesn"t work when the number of species get too large. To
        simplify matters, we generate the input file only with disordered sites
        and exclude the ordered sites from the enumeration. The fact that
        different disordered sites with the exact same species may belong to
        different equivalent sites is dealt with by having determined the
        spacegroup earlier and labelling the species differently.
        """

        # index_species and index_amounts store mappings between the indices
        # used in the enum input file, and the actual species and amounts.
        index_species = []
        index_amounts = []

        # Stores the ordered sites, which are not enumerated.
        ordered_sites = []
        disordered_sites = []
        coord_str = []
        for sites in symmetrized_structure.equivalent_sites:
            if sites[0].is_ordered:
                ordered_sites.append(sites)
            else:
                sp_label = []
                species = dict(sites[0].species.items())
                if sum(species.values()) < 1 - EnumlibAdaptor.amount_tol:
                    # Let us first make add a dummy element for every single
                    # site whose total occupancies don't sum to 1.
                    species[DummySpecies("X")] = 1 - sum(species.values())
                for sp in species.keys():
                    if sp not in index_species:
                        index_species.append(sp)
                        sp_label.append(len(index_species) - 1)
                        index_amounts.append(species[sp] * len(sites))
                    else:
                        ind = index_species.index(sp)
                        sp_label.append(ind)
                        index_amounts[ind] += species[sp] * len(sites)
                sp_label = "/".join(["{}".format(i) for i in sorted(sp_label)])
                for site in sites:
                    coord_str.append("{} {}".format(
                        coord_format.format(*site.coords), sp_label))
                disordered_sites.append(sites)

        def get_sg_info(ss):
            finder = SpacegroupAnalyzer(Structure.from_sites(ss),
                                        self.symm_prec)
            return finder.get_space_group_number()

        target_sgnum = get_sg_info(symmetrized_structure.sites)
        curr_sites = list(itertools.chain.from_iterable(disordered_sites))
        sgnum = get_sg_info(curr_sites)
        ordered_sites = sorted(ordered_sites, key=lambda sites: len(sites))
        logger.debug("Disordered sites has sg # %d" % (sgnum))
        self.ordered_sites = []

        # progressively add ordered sites to our disordered sites
        # until we match the symmetry of our input structure
        if self.check_ordered_symmetry:
            while sgnum != target_sgnum and len(ordered_sites) > 0:
                sites = ordered_sites.pop(0)
                temp_sites = list(curr_sites) + sites
                new_sgnum = get_sg_info(temp_sites)
                if sgnum != new_sgnum:
                    logger.debug("Adding %s in enum. New sg # %d" %
                                 (sites[0].specie, new_sgnum))
                    index_species.append(sites[0].specie)
                    index_amounts.append(len(sites))
                    sp_label = len(index_species) - 1
                    for site in sites:
                        coord_str.append("{} {}".format(
                            coord_format.format(*site.coords), sp_label))
                    disordered_sites.append(sites)
                    curr_sites = temp_sites
                    sgnum = new_sgnum
                else:
                    self.ordered_sites.extend(sites)

        for sites in ordered_sites:
            self.ordered_sites.extend(sites)

        self.index_species = index_species

        lattice = self.structure.lattice

        output = [self.structure.formula, "bulk"]
        for vec in lattice.matrix:
            output.append(coord_format.format(*vec))
        output.append("%d" % len(index_species))
        output.append("%d" % len(coord_str))
        output.extend(coord_str)

        output.append("{} {}".format(self.min_cell_size, self.max_cell_size))
        output.append(str(self.enum_precision_parameter))
        output.append("full")

        ndisordered = sum([len(s) for s in disordered_sites])
        base = int(ndisordered * lcm(*[
            f.limit_denominator(ndisordered * self.max_cell_size).denominator
            for f in map(fractions.Fraction, index_amounts)
        ]))

        # This multiplicative factor of 10 is to prevent having too small bases
        # which can lead to rounding issues in the next step.
        # An old bug was that a base was set to 8, with a conc of 0.4:0.6. That
        # resulted in a range that overlaps and a conc of 0.5 satisfying this
        # enumeration. See Cu7Te5.cif test file.
        base *= 10

        # base = ndisordered #10 ** int(math.ceil(math.log10(ndisordered)))
        # To get a reasonable number of structures, we fix concentrations to the
        # range expected in the original structure.
        total_amounts = sum(index_amounts)
        for amt in index_amounts:
            conc = amt / total_amounts

            if abs(conc * base - round(conc * base)) < 1e-5:
                output.append("{} {} {}".format(int(round(conc * base)),
                                                int(round(conc * base)), base))
            else:
                min_conc = int(math.floor(conc * base))
                output.append("{} {} {}".format(min_conc - 1, min_conc + 1,
                                                base))
        output.append("")
        logger.debug("Generated input file:\n{}".format("\n".join(output)))
        with open("struct_enum.in", "w") as f:
            f.write("\n".join(output))