def test_rounding_errors(self):
     # It used to be that a rounding issue would result in this structure
     # showing that Cu3Te2 satisfies an ordering of this structure.
     # This has been fixed by multiplying the base by 100.
     struct = Structure.from_file(os.path.join(test_dir, "Cu7Te5.cif"))
     adaptor = EnumlibAdaptor(struct, 1, 2)
     self.assertRaises(EnumError, adaptor.run)
     adaptor = EnumlibAdaptor(struct, 1, 5)
     adaptor.run()
     self.assertEqual(len(adaptor.structures), 197)
    def test_partial_disorder(self):
        s = Structure.from_file(filename=os.path.join(test_dir, "garnet.cif"))
        a = SpacegroupAnalyzer(s, 0.1)
        prim = a.find_primitive()
        s = prim.copy()
        s["Al3+"] = {"Al3+": 0.5, "Ga3+": 0.5}
        adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 7)
        for s in structures:
            self.assertEqual(s.formula, 'Ca12 Al4 Ga4 Si12 O48')
        s = prim.copy()
        s["Ca2+"] = {"Ca2+": 1/3, "Mg2+": 2/3}
        adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 20)
        for s in structures:
            self.assertEqual(s.formula, 'Ca4 Mg8 Al8 Si12 O48')

        s = prim.copy()
        s["Si4+"] = {"Si4+": 1/3, "Ge4+": 2/3}
        adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 18)
        for s in structures:
            self.assertEqual(s.formula, 'Ca12 Al8 Si4 Ge8 O48')
 def test_timeout(self):
     s = Structure.from_file(filename=os.path.join(test_dir, "garnet.cif"))
     a = SpacegroupAnalyzer(s, 0.1)
     s["Al3+"] = {"Al3+": 0.5, "Ga3+": 0.5}
     adaptor = EnumlibAdaptor(s,
                              1,
                              1,
                              enum_precision_parameter=0.01,
                              timeout=0.0000000000001)
     self.assertRaises(TimeoutError, adaptor._run_multienum)
 def test_rounding_errors(self):
     # It used to be that a rounding issue would result in this structure
     # showing that Cu3Te2 satisfies an ordering of this structure.
     # This has been fixed by multiplying the base by 100.
     struct = Structure.from_file(os.path.join(PymatgenTest.TEST_FILES_DIR, "Cu7Te5.cif"))
     adaptor = EnumlibAdaptor(struct, 1, 2)
     self.assertRaises(EnumError, adaptor.run)
     adaptor = EnumlibAdaptor(struct, 1, 5)
     adaptor.run()
     self.assertEqual(len(adaptor.structures), 197)
    def test_partial_disorder(self):
        s = Structure.from_file(filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "garnet.cif"))
        a = SpacegroupAnalyzer(s, 0.1)
        prim = a.find_primitive()
        s = prim.copy()
        s["Al3+"] = {"Al3+": 0.5, "Ga3+": 0.5}
        adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 7)
        for s in structures:
            self.assertEqual(s.formula, "Ca12 Al4 Ga4 Si12 O48")
        s = prim.copy()
        s["Ca2+"] = {"Ca2+": 1 / 3, "Mg2+": 2 / 3}
        adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 20)
        for s in structures:
            self.assertEqual(s.formula, "Ca4 Mg8 Al8 Si12 O48")

        s = prim.copy()
        s["Si4+"] = {"Si4+": 1 / 3, "Ge4+": 2 / 3}
        adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 18)
        for s in structures:
            self.assertEqual(s.formula, "Ca12 Al8 Si4 Ge8 O48")
    def test_init(self):
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            struct = self.get_structure("LiFePO4")
            subtrans = SubstitutionTransformation({"Li": {"Li": 0.5}})
            adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 2)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 86)
            for s in structures:
                self.assertAlmostEqual(s.composition.get_atomic_fraction(Element("Li")), 0.5 / 6.5)
            adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 2, refine_structure=True)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 52)

            subtrans = SubstitutionTransformation({"Li": {"Li": 0.25}})
            adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 1, refine_structure=True)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 1)
            for s in structures:
                self.assertAlmostEqual(s.composition.get_atomic_fraction(Element("Li")), 0.25 / 6.25)

            # Make sure it works for completely disordered structures.
            struct = Structure([[10, 0, 0], [0, 10, 0], [0, 0, 10]], [{"Fe": 0.5}], [[0, 0, 0]])
            adaptor = EnumlibAdaptor(struct, 1, 2)
            adaptor.run()
            self.assertEqual(len(adaptor.structures), 3)

            # Make sure it works properly when symmetry is broken by ordered sites.
            struct = self.get_structure("LiFePO4")
            subtrans = SubstitutionTransformation({"Li": {"Li": 0.25}})
            s = subtrans.apply_transformation(struct)
            # REmove some ordered sites to break symmetry.
            removetrans = RemoveSitesTransformation([4, 7])
            s = removetrans.apply_transformation(s)
            adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 4)

            struct = Structure(
                [[3, 0, 0], [0, 3, 0], [0, 0, 3]],
                [{"Si": 0.5}] * 2,
                [[0, 0, 0], [0.5, 0.5, 0.5]],
            )
            adaptor = EnumlibAdaptor(struct, 1, 3, enum_precision_parameter=0.01)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 10)

            struct = Structure.from_file(os.path.join(PymatgenTest.TEST_FILES_DIR, "EnumerateTest.json"))
            adaptor = EnumlibAdaptor(struct, 1, 1)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 2)
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        Return either a single ordered structure or a sequence of all ordered
        structures.

        Args:
            structure: Structure to order.
            return_ranked_list (bool): Whether or not multiple structures are
                returned. If return_ranked_list is a number, that number of
                structures is returned.

        Returns:
            Depending on returned_ranked list, either a transformed structure
            or a list of dictionaries, where each dictionary is of the form
            {"structure" = .... , "other_arguments"}

            The list of ordered structures is ranked by ewald energy / atom, if
            the input structure is an oxidation state decorated structure.
            Otherwise, it is ranked by number of sites, with smallest number of
            sites first.
        """
        try:
            num_to_return = int(return_ranked_list)
        except ValueError:
            num_to_return = 1

        if structure.is_ordered:
            raise ValueError("Enumeration can be carried out only on "
                             "disordered structures!")

        if self.refine_structure:
            finder = SymmetryFinder(structure, self.symm_prec)
            structure = finder.get_refined_structure()

        contains_oxidation_state = False
        for sp in structure.composition.elements:
            if hasattr(sp, "oxi_state") and sp.oxi_state != 0:
                contains_oxidation_state = True
                break

        adaptor = EnumlibAdaptor(structure, min_cell_size=self.min_cell_size,
                                 max_cell_size=self.max_cell_size,
                                 symm_prec=self.symm_prec,
                                 refine_structure=False)
        adaptor.run()
        structures = adaptor.structures
        original_latt = structure.lattice
        inv_latt = np.linalg.inv(original_latt.matrix)
        ewald_matrices = {}
        all_structures = []
        for s in structures:
            new_latt = s.lattice
            transformation = np.dot(new_latt.matrix, inv_latt)
            transformation = tuple([tuple([int(round(cell)) for cell in row])
                                    for row in transformation])
            if contains_oxidation_state:
                if transformation not in ewald_matrices:
                    s_supercell = Structure.from_sites(structure.sites)
                    s_supercell.make_supercell(transformation)
                    ewald = EwaldSummation(s_supercell)
                    ewald_matrices[transformation] = ewald
                else:
                    ewald = ewald_matrices[transformation]
                energy = ewald.compute_sub_structure(s)
                all_structures.append({"num_sites": len(s), "energy": energy,
                                       "structure": s})
            else:
                all_structures.append({"num_sites": len(s), "structure": s})

        def sort_func(s):
            return s["energy"] / s["num_sites"] if contains_oxidation_state \
                else s["num_sites"]

        self._all_structures = sorted(all_structures, key=sort_func)

        if return_ranked_list:
            return self._all_structures[0:num_to_return]
        else:
            return self._all_structures[0]["structure"]
    def test_init(self):
        test_dir = os.path.join(os.path.dirname(__file__), "..", "..", "..",
                                'test_files')
        parser = CifParser(os.path.join(test_dir, "LiFePO4.cif"))
        struct = parser.get_structures(False)[0]
        subtrans = SubstitutionTransformation({'Li': {'Li': 0.5}})
        adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 2)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 86)
        for s in structures:
            self.assertAlmostEqual(
                s.composition.get_atomic_fraction(Element("Li")), 0.5 / 6.5)
        adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 2,
                                 refine_structure=True)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 52)

        subtrans = SubstitutionTransformation({'Li': {'Li': 0.25}})
        adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 1,
                                 refine_structure=True)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 1)
        for s in structures:
            self.assertAlmostEqual(s.composition
                                   .get_atomic_fraction(Element("Li")),
                                   0.25 / 6.25)

        #Make sure it works for completely disordered structures.
        struct = Structure([[10, 0, 0], [0, 10, 0], [0, 0, 10]], [{'Fe': 0.5}],
                           [[0, 0, 0]])
        adaptor = EnumlibAdaptor(struct, 1, 2)
        adaptor.run()
        self.assertEqual(len(adaptor.structures), 3)

        #Make sure it works properly when symmetry is broken by ordered sites.
        parser = CifParser(os.path.join(test_dir, "LiFePO4.cif"))
        struct = parser.get_structures(False)[0]
        subtrans = SubstitutionTransformation({'Li': {'Li': 0.25}})
        s = subtrans.apply_transformation(struct)
        #REmove some ordered sites to break symmetry.
        removetrans = RemoveSitesTransformation([4, 7])
        s = removetrans.apply_transformation(s)
        adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 4)

        struct = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]],
                           [{"Si": 0.5}] * 2, [[0, 0, 0], [0.5, 0.5, 0.5]])
        adaptor = EnumlibAdaptor(struct, 1, 3, enum_precision_parameter=0.01)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 10)

        struct = CifParser(os.path.join(test_dir, "EnumerateTest.cif"))\
            .get_structures()[0]
        adaptor = EnumlibAdaptor(struct, 1, 1)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 2)
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        Return either a single ordered structure or a sequence of all ordered
        structures.

        Args:
            structure: Structure to order.
            return_ranked_list (bool): Whether or not multiple structures are
                returned. If return_ranked_list is a number, that number of
                structures is returned.

        Returns:
            Depending on returned_ranked list, either a transformed structure
            or a list of dictionaries, where each dictionary is of the form
            {"structure" = .... , "other_arguments"}

            The list of ordered structures is ranked by ewald energy / atom, if
            the input structure is an oxidation state decorated structure.
            Otherwise, it is ranked by number of sites, with smallest number of
            sites first.
        """
        try:
            num_to_return = int(return_ranked_list)
        except ValueError:
            num_to_return = 1

        if self.occu_tol:
            species = [dict(d) for d in structure.species_and_occu]
            # Here, we rescale all occupancies such that they meet the frac
            # limit.
            for sp in species:
                for k, v in sp.items():
                    sp[k] = float(Fraction(v).limit_denominator(self.occu_tol))
            structure = Structure(structure.lattice, species,
                                  structure.frac_coords)

        if self.refine_structure:
            finder = SpacegroupAnalyzer(structure, self.symm_prec)
            structure = finder.get_refined_structure()

        contains_oxidation_state = all(
            [hasattr(sp, "oxi_state") and sp.oxi_state != 0 for sp in
             structure.composition.elements]
        )

        if structure.is_ordered:
            warn("Enumeration skipped for structure with composition {} "
                 "because it is ordered".format(structure.composition))
            structures = [structure.copy()]
        else:
            adaptor = EnumlibAdaptor(
                structure, min_cell_size=self.min_cell_size,
                max_cell_size=self.max_cell_size,
                symm_prec=self.symm_prec, refine_structure=False,
                enum_precision_parameter=self.enum_precision_parameter,
                check_ordered_symmetry=self.check_ordered_symmetry)
            adaptor.run()
            structures = adaptor.structures

        original_latt = structure.lattice
        inv_latt = np.linalg.inv(original_latt.matrix)
        ewald_matrices = {}
        all_structures = []
        for s in structures:
            new_latt = s.lattice
            transformation = np.dot(new_latt.matrix, inv_latt)
            transformation = tuple([tuple([int(round(cell)) for cell in row])
                                    for row in transformation])
            if contains_oxidation_state:
                if transformation not in ewald_matrices:
                    s_supercell = structure * transformation
                    ewald = EwaldSummation(s_supercell)
                    ewald_matrices[transformation] = ewald
                else:
                    ewald = ewald_matrices[transformation]
                energy = ewald.compute_sub_structure(s)
                all_structures.append({"num_sites": len(s), "energy": energy,
                                       "structure": s})
            else:
                all_structures.append({"num_sites": len(s), "structure": s})

        def sort_func(s):
            return s["energy"] / s["num_sites"] if contains_oxidation_state \
                else s["num_sites"]

        self._all_structures = sorted(all_structures, key=sort_func)

        if return_ranked_list:
            return self._all_structures[0:num_to_return]
        else:
            return self._all_structures[0]["structure"]
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        Return either a single ordered structure or a sequence of all ordered
        structures.

        Args:
            structure: Structure to order.
            return_ranked_list (bool): Whether or not multiple structures are
                returned. If return_ranked_list is a number, that number of
                structures is returned.

        Returns:
            Depending on returned_ranked list, either a transformed structure
            or a list of dictionaries, where each dictionary is of the form
            {"structure" = .... , "other_arguments"}

            The list of ordered structures is ranked by ewald energy / atom, if
            the input structure is an oxidation state decorated structure.
            Otherwise, it is ranked by number of sites, with smallest number of
            sites first.
        """
        try:
            num_to_return = int(return_ranked_list)
        except ValueError:
            num_to_return = 1

        if self.refine_structure:
            finder = SpacegroupAnalyzer(structure, self.symm_prec)
            structure = finder.get_refined_structure()

        contains_oxidation_state = all([
            hasattr(sp, "oxi_state") and sp.oxi_state != 0
            for sp in structure.composition.elements
        ])

        structures = None

        if structure.is_ordered:
            warn("Enumeration skipped for structure with composition {} "
                 "because it is ordered".format(structure.composition))
            structures = [structure.copy()]

        if self.max_disordered_sites:
            ndisordered = sum([1 for site in structure if not site.is_ordered])
            if ndisordered > self.max_disordered_sites:
                raise ValueError("Too many disordered sites! ({} > {})".format(
                    ndisordered, self.max_disordered_sites))
            max_cell_sizes = range(
                self.min_cell_size,
                int(math.floor(self.max_disordered_sites / ndisordered)) + 1)

        else:
            max_cell_sizes = [self.max_cell_size]

        for max_cell_size in max_cell_sizes:
            adaptor = EnumlibAdaptor(
                structure,
                min_cell_size=self.min_cell_size,
                max_cell_size=max_cell_size,
                symm_prec=self.symm_prec,
                refine_structure=False,
                enum_precision_parameter=self.enum_precision_parameter,
                check_ordered_symmetry=self.check_ordered_symmetry,
                timeout=self.timeout)
            try:
                adaptor.run()
            except EnumError:
                warn("Unable to enumerate for max_cell_size = %d".format(
                    max_cell_size))
            structures = adaptor.structures
            if structures:
                break

        if structures is None:
            raise ValueError("Unable to enumerate")

        original_latt = structure.lattice
        inv_latt = np.linalg.inv(original_latt.matrix)
        ewald_matrices = {}
        all_structures = []
        for s in structures:
            new_latt = s.lattice
            transformation = np.dot(new_latt.matrix, inv_latt)
            transformation = tuple([
                tuple([int(round(cell)) for cell in row])
                for row in transformation
            ])
            if contains_oxidation_state and self.sort_criteria == "ewald":
                if transformation not in ewald_matrices:
                    s_supercell = structure * transformation
                    ewald = EwaldSummation(s_supercell)
                    ewald_matrices[transformation] = ewald
                else:
                    ewald = ewald_matrices[transformation]
                energy = ewald.compute_sub_structure(s)
                all_structures.append({
                    "num_sites": len(s),
                    "energy": energy,
                    "structure": s
                })
            else:
                all_structures.append({"num_sites": len(s), "structure": s})

        def sort_func(s):
            return s["energy"] / s["num_sites"] \
                if contains_oxidation_state and self.sort_criteria == "ewald" \
                else s["num_sites"]

        self._all_structures = sorted(all_structures, key=sort_func)

        if return_ranked_list:
            return self._all_structures[0:num_to_return]
        else:
            return self._all_structures[0]["structure"]
    def test_init(self):
        test_dir = os.path.join(os.path.dirname(__file__), "..", "..", "..",
                                'test_files')
        struct = self.get_structure("LiFePO4")
        subtrans = SubstitutionTransformation({'Li': {'Li': 0.5}})
        adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 2)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 86)
        for s in structures:
            self.assertAlmostEqual(
                s.composition.get_atomic_fraction(Element("Li")), 0.5 / 6.5)
        adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct),
                                 1,
                                 2,
                                 refine_structure=True)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 52)

        subtrans = SubstitutionTransformation({'Li': {'Li': 0.25}})
        adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct),
                                 1,
                                 1,
                                 refine_structure=True)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 1)
        for s in structures:
            self.assertAlmostEqual(
                s.composition.get_atomic_fraction(Element("Li")), 0.25 / 6.25)

        #Make sure it works for completely disordered structures.
        struct = Structure([[10, 0, 0], [0, 10, 0], [0, 0, 10]], [{
            'Fe': 0.5
        }], [[0, 0, 0]])
        adaptor = EnumlibAdaptor(struct, 1, 2)
        adaptor.run()
        self.assertEqual(len(adaptor.structures), 3)

        #Make sure it works properly when symmetry is broken by ordered sites.
        struct = self.get_structure("LiFePO4")
        subtrans = SubstitutionTransformation({'Li': {'Li': 0.25}})
        s = subtrans.apply_transformation(struct)
        #REmove some ordered sites to break symmetry.
        removetrans = RemoveSitesTransformation([4, 7])
        s = removetrans.apply_transformation(s)
        adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 4)

        struct = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]], [{
            "Si": 0.5
        }] * 2, [[0, 0, 0], [0.5, 0.5, 0.5]])
        adaptor = EnumlibAdaptor(struct, 1, 3, enum_precision_parameter=0.01)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 10)

        struct = Structure.from_file(
            os.path.join(test_dir, "EnumerateTest.json"))
        adaptor = EnumlibAdaptor(struct, 1, 1)
        adaptor.run()
        structures = adaptor.structures
        self.assertEqual(len(structures), 2)
Exemple #12
0
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        Return either a single ordered structure or a sequence of all ordered
        structures.

        Args:
            structure: Structure to order.
            return_ranked_list (bool): Whether or not multiple structures are
                returned. If return_ranked_list is a number, that number of
                structures is returned.

        Returns:
            Depending on returned_ranked list, either a transformed structure
            or a list of dictionaries, where each dictionary is of the form
            {"structure" = .... , "other_arguments"}

            The list of ordered structures is ranked by ewald energy / atom, if
            the input structure is an oxidation state decorated structure.
            Otherwise, it is ranked by number of sites, with smallest number of
            sites first.
        """
        try:
            num_to_return = int(return_ranked_list)
        except ValueError:
            num_to_return = 1

        if structure.is_ordered:
            raise ValueError("Enumeration can be carried out only on "
                             "disordered structures!")

        if self.refine_structure:
            finder = SpacegroupAnalyzer(structure, self.symm_prec)
            structure = finder.get_refined_structure()

        contains_oxidation_state = all([
            hasattr(sp, "oxi_state") and sp.oxi_state != 0
            for sp in structure.composition.elements
        ])

        adaptor = EnumlibAdaptor(
            structure,
            min_cell_size=self.min_cell_size,
            max_cell_size=self.max_cell_size,
            symm_prec=self.symm_prec,
            refine_structure=False,
            enum_precision_parameter=self.enum_precision_parameter,
            check_ordered_symmetry=self.check_ordered_symmetry)
        adaptor.run()
        structures = adaptor.structures
        original_latt = structure.lattice
        inv_latt = np.linalg.inv(original_latt.matrix)
        ewald_matrices = {}
        all_structures = []
        for s in structures:
            new_latt = s.lattice
            transformation = np.dot(new_latt.matrix, inv_latt)
            transformation = tuple([
                tuple([int(round(cell)) for cell in row])
                for row in transformation
            ])
            if contains_oxidation_state:
                if transformation not in ewald_matrices:
                    s_supercell = Structure.from_sites(structure.sites)
                    s_supercell.make_supercell(transformation)
                    ewald = EwaldSummation(s_supercell)
                    ewald_matrices[transformation] = ewald
                else:
                    ewald = ewald_matrices[transformation]
                energy = ewald.compute_sub_structure(s)
                all_structures.append({
                    "num_sites": len(s),
                    "energy": energy,
                    "structure": s
                })
            else:
                all_structures.append({"num_sites": len(s), "structure": s})

        def sort_func(s):
            return s["energy"] / s["num_sites"] if contains_oxidation_state \
                else s["num_sites"]

        self._all_structures = sorted(all_structures, key=sort_func)

        if return_ranked_list:
            return self._all_structures[0:num_to_return]
        else:
            return self._all_structures[0]["structure"]
Exemple #13
0
    def test_init(self):
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            struct = self.get_structure("LiFePO4")
            subtrans = SubstitutionTransformation({'Li': {'Li': 0.5}})
            adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 2)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 86)
            for s in structures:
                self.assertAlmostEqual(
                    s.composition.get_atomic_fraction(Element("Li")), 0.5 / 6.5)
            adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 2,
                                     refine_structure=True)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 52)

            subtrans = SubstitutionTransformation({'Li': {'Li': 0.25}})
            adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 1,
                                     refine_structure=True)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 1)
            for s in structures:
                self.assertAlmostEqual(s.composition
                                       .get_atomic_fraction(Element("Li")),
                                       0.25 / 6.25)

            # Make sure it works for completely disordered structures.
            struct = Structure([[10, 0, 0], [0, 10, 0], [0, 0, 10]], [{'Fe': 0.5}],
                               [[0, 0, 0]])
            adaptor = EnumlibAdaptor(struct, 1, 2)
            adaptor.run()
            self.assertEqual(len(adaptor.structures), 3)

            # Make sure it works properly when symmetry is broken by ordered sites.
            struct = self.get_structure("LiFePO4")
            subtrans = SubstitutionTransformation({'Li': {'Li': 0.25}})
            s = subtrans.apply_transformation(struct)
            # REmove some ordered sites to break symmetry.
            removetrans = RemoveSitesTransformation([4, 7])
            s = removetrans.apply_transformation(s)
            adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 4)

            struct = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]],
                               [{"Si": 0.5}] * 2, [[0, 0, 0], [0.5, 0.5, 0.5]])
            adaptor = EnumlibAdaptor(struct, 1, 3, enum_precision_parameter=0.01)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 10)

            struct = Structure.from_file(
                os.path.join(test_dir, "EnumerateTest.json"))
            adaptor = EnumlibAdaptor(struct, 1, 1)
            adaptor.run()
            structures = adaptor.structures
            self.assertEqual(len(structures), 2)
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        Return either a single ordered structure or a sequence of all ordered
        structures.

        Args:
            structure: Structure to order.
            return_ranked_list (bool): Whether or not multiple structures are
                returned. If return_ranked_list is a number, that number of
                structures is returned.

        Returns:
            Depending on returned_ranked list, either a transformed structure
            or a list of dictionaries, where each dictionary is of the form
            {"structure" = .... , "other_arguments"}

            The list of ordered structures is ranked by ewald energy / atom, if
            the input structure is an oxidation state decorated structure.
            Otherwise, it is ranked by number of sites, with smallest number of
            sites first.
        """
        try:
            num_to_return = int(return_ranked_list)
        except ValueError:
            num_to_return = 1

        if self.refine_structure:
            finder = SpacegroupAnalyzer(structure, self.symm_prec)
            structure = finder.get_refined_structure()

        contains_oxidation_state = all(
            [hasattr(sp, "oxi_state") and sp.oxi_state != 0 for sp in
             structure.composition.elements]
        )

        structures = None

        if structure.is_ordered:
            warn("Enumeration skipped for structure with composition {} "
                 "because it is ordered".format(structure.composition))
            structures = [structure.copy()]

        if self.max_disordered_sites:
            ndisordered = sum([1 for site in structure if not site.is_ordered])
            if ndisordered > self.max_disordered_sites:
                raise ValueError(
                    "Too many disordered sites! ({} > {})".format(
                        ndisordered, self.max_disordered_sites))
            max_cell_sizes = range(self.min_cell_size, int(
                    math.floor(self.max_disordered_sites / ndisordered)) + 1)

        else:
            max_cell_sizes = [self.max_cell_size]

        for max_cell_size in max_cell_sizes:
            adaptor = EnumlibAdaptor(
                structure, min_cell_size=self.min_cell_size,
                max_cell_size=max_cell_size,
                symm_prec=self.symm_prec, refine_structure=False,
                enum_precision_parameter=self.enum_precision_parameter,
                check_ordered_symmetry=self.check_ordered_symmetry,
                timeout=self.timeout)
            try:
                adaptor.run()
            except EnumError:
                warn("Unable to enumerate for max_cell_size = %d".format(
                    max_cell_size))
            structures = adaptor.structures
            if structures:
                break

        if structures is None:
            raise ValueError("Unable to enumerate")

        original_latt = structure.lattice
        inv_latt = np.linalg.inv(original_latt.matrix)
        ewald_matrices = {}
        all_structures = []
        for s in structures:
            new_latt = s.lattice
            transformation = np.dot(new_latt.matrix, inv_latt)
            transformation = tuple([tuple([int(round(cell)) for cell in row])
                                    for row in transformation])
            if contains_oxidation_state and self.sort_criteria == "ewald":
                if transformation not in ewald_matrices:
                    s_supercell = structure * transformation
                    ewald = EwaldSummation(s_supercell)
                    ewald_matrices[transformation] = ewald
                else:
                    ewald = ewald_matrices[transformation]
                energy = ewald.compute_sub_structure(s)
                all_structures.append({"num_sites": len(s), "energy": energy,
                                       "structure": s})
            else:
                all_structures.append({"num_sites": len(s), "structure": s})

        def sort_func(s):
            return s["energy"] / s["num_sites"] \
                if contains_oxidation_state and self.sort_criteria == "ewald" \
                else s["num_sites"]

        self._all_structures = sorted(all_structures, key=sort_func)

        if return_ranked_list:
            return self._all_structures[0:num_to_return]
        else:
            return self._all_structures[0]["structure"]