def apply_transformation(self, structure):
     editor = StructureEditor(structure)
     for i, sp in enumerate(self._species):
         editor.insert_site(i, sp, self._coords[i],
                            coords_are_cartesian=self._cartesian,
                            validate_proximity=self._validate_proximity)
     return editor.modified_structure.get_sorted_structure()
예제 #2
0
def _get_host(structure, species_to_remove):
    if species_to_remove:
        editor = StructureEditor(structure)
        editor.remove_species(species_to_remove)
        return editor.modified_structure
    else:
        return structure
예제 #3
0
def _get_host(structure, species_to_remove):
    if species_to_remove:
        editor = StructureEditor(structure)
        editor.remove_species(species_to_remove)
        return editor.modified_structure
    else:
        return structure
    def complete_ordering(self, structure, num_remove_dict):
        self.logger.debug("Performing complete ordering...")
        all_structures = []
        from pymatgen.symmetry.finder import SymmetryFinder
        symprec = 0.2
        s = SymmetryFinder(structure, symprec=symprec)
        self.logger.debug("Symmetry of structure is determined to be {}."
                          .format(s.get_spacegroup_symbol()))
        sg = s.get_spacegroup()
        tested_sites = []
        starttime = time.time()
        self.logger.debug("Performing initial ewald sum...")
        ewaldsum = EwaldSummation(structure)
        self.logger.debug("Ewald sum took {} seconds."
                          .format(time.time() - starttime))
        starttime = time.time()

        allcombis = []
        for ind, num in num_remove_dict.items():
            allcombis.append(itertools.combinations(ind, num))

        count = 0
        for allindices in itertools.product(*allcombis):
            sites_to_remove = []
            indices_list = []
            for indices in allindices:
                sites_to_remove.extend([structure[i] for i in indices])
                indices_list.extend(indices)

            mod = StructureEditor(structure)
            mod.delete_sites(indices_list)
            s_new = mod.modified_structure
            energy = ewaldsum.compute_partial_energy(indices_list)
            already_tested = False
            for i, tsites in enumerate(tested_sites):
                tenergy = all_structures[i]["energy"]
                if abs((energy - tenergy) / len(s_new)) < 1e-5 and \
                        sg.are_symmetrically_equivalent(sites_to_remove, tsites,
                                                        symm_prec=symprec):
                    already_tested = True

            if not already_tested:
                tested_sites.append(sites_to_remove)
                all_structures.append({"structure": s_new, "energy": energy})

            count += 1
            if count % 10 == 0:
                timenow = time.time()
                self.logger.debug("{} structures, {:.2f} seconds."
                                  .format(count, timenow - starttime))
                self.logger.debug("Average time per combi = {} seconds"
                                  .format((timenow - starttime) / count))
                self.logger.debug("{} symmetrically distinct structures found."
                                  .format(len(all_structures)))

        self.logger.debug("Total symmetrically distinct structures found = {}"
                          .format(len(all_structures)))
        all_structures = sorted(all_structures, key=lambda s: s["energy"])
        return all_structures
 def apply_transformation(self, structure):
     species_map = {}
     for k, v in self._species_map.items():
         if isinstance(v, dict):
             value = {smart_element_or_specie(x): y for x, y in v.items()}
         else:
             value = smart_element_or_specie(v)
         species_map[smart_element_or_specie(k)] = value
     editor = StructureEditor(structure)
     editor.replace_species(species_map)
     return editor.modified_structure
예제 #6
0
    def test_add_site_property(self):
        self.modifier.add_site_property("charge", [4.1, 5])
        s = self.modifier.modified_structure
        self.assertEqual(s[0].charge, 4.1)
        self.assertEqual(s[1].charge, 5)

        #test adding multiple properties.
        mod2 = StructureEditor(s)
        mod2.add_site_property("magmom", [3, 2])
        s = mod2.modified_structure
        self.assertEqual(s[0].charge, 4.1)
        self.assertEqual(s[0].magmom, 3)
예제 #7
0
 def setUp(self):
     p = Poscar.from_file(os.path.join(test_dir, 'POSCAR'))
     self.structure = p.struct
     self.sg = SymmetryFinder(self.structure, 0.001)
     parser = CifParser(os.path.join(test_dir, 'Li10GeP2S12.cif'))
     self.disordered_structure = parser.get_structures()[0]
     self.disordered_sg = SymmetryFinder(self.disordered_structure, 0.001)
     s = p.struct
     editor = StructureEditor(p.struct)
     site = s[0]
     editor.delete_site(0)
     editor.append_site(site.species_and_occu, site.frac_coords)
     self.sg3 = SymmetryFinder(editor.modified_structure, 0.001)
    def fast_ordering(self, structure, num_remove_dict, num_to_return=1):
        """
        This method uses the matrix form of ewaldsum to calculate the ewald
        sums of the potential structures. This is on the order of 4 orders of
        magnitude faster when there are large numbers of permutations to
        consider. There are further optimizations possible (doing a smarter
        search of permutations for example), but this wont make a difference
        until the number of permutations is on the order of 30,000.
        """
        self.logger.debug("Performing fast ordering")
        starttime = time.time()
        self.logger.debug("Performing initial ewald sum...")

        ewaldmatrix = EwaldSummation(structure).total_energy_matrix
        self.logger.debug("Ewald sum took {} seconds."
                          .format(time.time() - starttime))
        starttime = time.time()
        m_list = []
        for indices, num in num_remove_dict.items():
            m_list.append([0, num, list(indices), None])

        self.logger.debug("Calling EwaldMinimizer...")
        minimizer = EwaldMinimizer(ewaldmatrix, m_list, num_to_return,
                                   PartialRemoveSitesTransformation.ALGO_FAST)
        self.logger.debug("Minimizing Ewald took {} seconds."
                          .format(time.time() - starttime))

        all_structures = []

        lowest_energy = minimizer.output_lists[0][0]
        num_atoms = sum(structure.composition.values())

        for output in minimizer.output_lists:
            se = StructureEditor(structure)
            del_indices = []

            for manipulation in output[1]:
                if manipulation[1] is None:
                    del_indices.append(manipulation[0])
                else:
                    se.replace_site(manipulation[0], manipulation[1])
            se.delete_sites(del_indices)
            struct = se.modified_structure.get_sorted_structure()
            all_structures.append({"energy": output[0],
                                   "energy_above_minimum": (output[0]
                                                            - lowest_energy)
                                   / num_atoms,
                                   "structure": struct})

        return all_structures
예제 #9
0
    def test_init(self):
        fitter = StructureFitter(self.b, self.a)
        self.assertTrue(fitter.mapping_op != None, "No fit found!")

        #Now to try with rotated structure
        op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, np.array([0, 0, 1]))
        editor = StructureEditor(self.a)
        editor.apply_operation(op)
        fitter = StructureFitter(self.b, editor.modified_structure)

        self.assertTrue(fitter.mapping_op != None, "No fit found!")

        #test with a supercell
        mod = SupercellMaker(self.a, scaling_matrix=[[2, 0, 0], [0, 1, 0], [0, 0, 1]])
        a_super = mod.modified_structure
        fitter = StructureFitter(self.b, a_super)
        self.assertTrue(fitter.mapping_op != None, "No fit found!")

        # Test with a structure with a translated point

        editor = StructureEditor(self.a)
        site = self.a[0]
        editor.delete_site(0)
        trans = np.random.randint(0, 1000, 3)
        editor.insert_site(0, site.species_and_occu, site.frac_coords + trans, False, False)
        fitter = StructureFitter(self.b, editor.modified_structure)
        self.assertTrue(fitter.mapping_op != None, "No fit found for translation {}!".format(trans))

        parser = CifParser(os.path.join(test_dir, "FePO4a.cif"))
        a = parser.get_structures()[0]
        parser = CifParser(os.path.join(test_dir, "FePO4b.cif"))
        b = parser.get_structures()[0]
        fitter = StructureFitter(b, a)
        self.assertTrue(fitter.mapping_op != None, "No fit found!")
 def enumerate_ordering(self, structure):
     # Generate the disordered structure first.
     editor = StructureEditor(structure)
     for indices, fraction in zip(self._indices, self._fractions):
         for ind in indices:
             new_sp = {sp: occu * fraction
                       for sp, occu
                       in structure[ind].species_and_occu.items()}
             editor.replace_site(ind, new_sp)
     mod_s = editor.modified_structure
     # Perform enumeration
     from pymatgen.transformations.advanced_transformations import \
         EnumerateStructureTransformation
     trans = EnumerateStructureTransformation()
     return trans.apply_transformation(mod_s, 10000)
예제 #11
0
 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)
     mod = StructureEditor(s_specie)
     mod.remove_oxidation_states()
     mod_s = mod.modified_structure
     self.assertEqual(s_elem, mod_s, "Oxidation state remover failed")
    def test_fit(self):
        """
        Take two known matched structures
            1) Ensure match
            2) Ensure match after translation and rotations
            3) Ensure no-match after large site translation
            4) Ensure match after site shuffling
            """
        sm = StructureMatcher()

        self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1]))

        # Test rotational/translational invariance
        op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False,
                                                    np.array([0.4, 0.7, 0.9]))
        editor = StructureEditor(self.struct_list[1])
        editor.apply_operation(op)
        self.assertTrue(sm.fit(self.struct_list[0], editor.modified_structure))

        #Test failure under large atomic translation
        editor.translate_sites([0], [.4, .4, .2], frac_coords=True)
        self.assertFalse(sm.fit(self.struct_list[0],
                                editor.modified_structure))

        editor.translate_sites([0], [-.4, -.4, -.2], frac_coords=True)
        # random.shuffle(editor._sites)
        self.assertTrue(sm.fit(self.struct_list[0], editor.modified_structure))
        #Test FrameworkComporator
        sm2 = StructureMatcher(comparator=FrameworkComparator())
        lfp = read_structure(os.path.join(test_dir, "LiFePO4.cif"))
        nfp = read_structure(os.path.join(test_dir, "NaFePO4.cif"))
        self.assertTrue(sm2.fit(lfp, nfp))
        self.assertFalse(sm.fit(lfp, nfp))

        #Test anonymous fit.
        self.assertEqual(sm.fit_anonymous(lfp, nfp),
                         {Composition("Li"): Composition("Na")})
        self.assertAlmostEqual(sm.get_minimax_rms_anonymous(lfp, nfp)[0],
                               0.096084154118549828)

        #Test partial occupancies.
        s1 = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]],
                       [{"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.5}],
                       [[0, 0, 0], [0.25, 0.25, 0.25],
                        [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]])
        s2 = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]],
                       [{"Fe": 0.25}, {"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.75}],
                       [[0, 0, 0], [0.25, 0.25, 0.25],
                        [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]])
        self.assertFalse(sm.fit(s1, s2))
        self.assertFalse(sm.fit(s2, s1))
        s2 = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]],
                       [{"Fe": 0.25}, {"Fe": 0.25}, {"Fe": 0.25},
                        {"Fe": 0.25}],
                       [[0, 0, 0], [0.25, 0.25, 0.25],
                        [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]])
        self.assertEqual(sm.fit_anonymous(s1, s2),
                         {Composition("Fe0.5"): Composition("Fe0.25")})

        self.assertAlmostEqual(sm.get_minimax_rms_anonymous(s1, s2)[0], 0)
예제 #13
0
    def get_oxi_state_decorated_structure(self, structure):
        """
        Get an oxidation state decorated structure. This currently works only
        for ordered structures only.

        Args:
            structure:
                Structure to analyze

        Returns:
            A modified structure that is oxidation state decorated.

        Raises:
            A ValueError is the valences cannot be determined.
        """
        valences = self.get_valences(structure)
        editor = StructureEditor(structure)
        editor.add_oxidation_state_by_site(valences)
        return editor.modified_structure
예제 #14
0
 def setUp(self):
     self.si = Element("Si")
     self.fe = Element("Fe")
     self.ge = Element("Ge")
     coords = list()
     coords.append(np.array([0, 0, 0]))
     coords.append(np.array([0.75, 0.5, 0.75]))
     lattice = Lattice.cubic(10)
     s = Structure(lattice, ["Si", "Fe"], coords)
     self.modifier = StructureEditor(s)
    def best_first_ordering(self, structure, num_remove_dict):
        self.logger.debug("Performing best first ordering")
        starttime = time.time()
        self.logger.debug("Performing initial ewald sum...")
        ewaldsum = EwaldSummation(structure)
        self.logger.debug("Ewald sum took {} seconds."
                          .format(time.time() - starttime))
        starttime = time.time()

        ematrix = ewaldsum.total_energy_matrix
        to_delete = []

        totalremovals = sum(num_remove_dict.values())
        removed = {k: 0 for k in num_remove_dict.keys()}
        for i in xrange(totalremovals):
            maxindex = None
            maxe = float("-inf")
            maxindices = None
            for indices in num_remove_dict.keys():
                if removed[indices] < num_remove_dict[indices]:
                    for ind in indices:
                        if ind not in to_delete:
                            energy = sum(ematrix[:, ind]) + \
                                sum(ematrix[:, ind]) - ematrix[ind, ind]
                            if energy > maxe:
                                maxindex = ind
                                maxe = energy
                                maxindices = indices
            removed[maxindices] += 1
            to_delete.append(maxindex)
            ematrix[:, maxindex] = 0
            ematrix[maxindex, :] = 0
        mod = StructureEditor(structure)
        mod.delete_sites(to_delete)
        self.logger.debug("Minimizing Ewald took {} seconds."
                          .format(time.time() - starttime))
        return [{"energy": sum(sum(ematrix)),
                 "structure": mod.modified_structure.get_sorted_structure()}]
예제 #16
0
 def test_add_oxidation_states(self):
     si = Element("Si")
     fe = Element("Fe")
     coords = list()
     coords.append([0, 0, 0])
     coords.append([0.75, 0.5, 0.75])
     lattice = Lattice.cubic(10)
     s = Structure(lattice, [si, fe], coords)
     oxidation_states = {"Fe": 2, "Si": -4}
     mod = StructureEditor(s)
     mod.add_oxidation_state_by_element(oxidation_states)
     mod_s = mod.modified_structure
     for site in mod_s:
         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, mod.add_oxidation_state_by_element,
                       oxidation_states)
     mod.add_oxidation_state_by_site([2, -4])
     mod_s = mod.modified_structure
     self.assertEqual(mod_s[0].specie.oxi_state, 2)
     self.assertRaises(ValueError, mod.add_oxidation_state_by_site,
                       [1])
예제 #17
0
    def test_init(self):
        filepath = os.path.join(test_dir, 'POSCAR')
        p = Poscar.from_file(filepath)
        original_s = p.structure

        modifier = StructureEditor(original_s)
        modifier.add_oxidation_state_by_element({"Li": 1, "Fe": 2,
                                                 "P": 5, "O":-2})
        s = modifier.modified_structure
        ham = EwaldSummation(s)
        self.assertAlmostEqual(ham.real_space_energy, -354.91294268, 4,
                               "Real space energy incorrect!")
        self.assertAlmostEqual(ham.reciprocal_space_energy, 25.475754801, 4)
        self.assertAlmostEqual(ham.point_energy, -790.463835033, 4,
                               "Point space energy incorrect!")
        self.assertAlmostEqual(ham.total_energy, -1119.90102291, 2,
                               "Total space energy incorrect!")
        self.assertAlmostEqual(ham.forces[0,0], -1.98818620e-01, 4,
                               "Forces incorrect")
        self.assertAlmostEqual(sum(sum(abs(ham.forces))), 915.925354346, 4,
                               "Forces incorrect")
        self.assertAlmostEqual(sum(sum(ham.real_space_energy_matrix)),
                               - 354.91294268, 4,
                               "Real space energy matrix incorrect!")
        self.assertAlmostEqual(sum(sum(ham.reciprocal_space_energy_matrix)),
                               25.475754801, 4,
                               "Reciprocal space energy matrix incorrect!")
        self.assertAlmostEqual(sum(ham.point_energy_matrix), -790.463835033,
                               4, "Point space energy matrix incorrect!")
        self.assertAlmostEqual(sum(sum(ham.total_energy_matrix)),
                               - 1119.90102291, 2,
                               "Total space energy matrix incorrect!")
        #note that forces are not individually tested, but should work fine.

        self.assertRaises(ValueError, EwaldSummation, original_s)
        #try sites with charge.
        charges = []
        for site in original_s:
            if site.specie.symbol == "Li":
                charges.append(1)
            elif site.specie.symbol == "Fe":
                charges.append(2)
            elif site.specie.symbol == "P":
                charges.append(5)
            else:
                charges.append(-2)

        editor = StructureEditor(original_s)
        editor.add_site_property('charge', charges)
        ham2 = EwaldSummation(editor.modified_structure)
        self.assertAlmostEqual(ham2.real_space_energy, -354.91294268, 4,
                               "Real space energy incorrect!")
예제 #18
0
    def __init__(
        self,
        structure_a,
        structure_b,
        tolerance_cell_misfit=0.1,
        tolerance_atomic_misfit=1.0,
        supercells_allowed=True,
        anonymized=False,
        fitting_accuracy=FAST_FIT,
        use_symmetry=False,
    ):
        """
        Fits two structures. All fitting parameters have been set with defaults 
        that should work in most cases. To use, initialize the structure fitter
        with parameters.
        E.g.,
        fitter = StructureFitter(a, b)
        print fitter.fit_found
        
        Args:
            structure_a : 
                First structure
            structure_b : 
                Second structure to try to match with first structure
            tolerance_cell_misfit : 
                Tolerance for cell misfit. Default = 0.1
            tolerance_atomic_misfit : 
                Tolerance for atomic misfit. Default = 1.0.
            supercells_allowed : 
                Whether supercell structures are allowed.  Default = True.
            anonymized : 
                Whether to attempt matching of different species.  Setting this
                to true will allow any two structures with the same framework,
                but different species to match to each other. Default = False.
            fitting_accuracy : 
                An integer setting for the fitting accuracy.  Corresponds to
                the max number of candidate rotations considered.  Use the
                static variables, 
                StructureFitter.FAST_FIT
                StructureFitter.NORMAL_FIT
                StructureFitter.ACCURATE_FIT
                to set the tradeoff between accuracy and speed.  The default, 
                FAST_FIT, should work reasonably well in most instances.
            use_symmetry:
                Whether to use pymatgen.spacegroup to determine the spacegroup
                first. Eliminates most non-fits. Defaults to True.
        """

        self._tolerance_cell_misfit = tolerance_cell_misfit
        self._tolerance_atomic_misfit = tolerance_atomic_misfit
        self._supercells_allowed = supercells_allowed
        self._anonymized = anonymized
        self._max_rotations = fitting_accuracy
        # Sort structures first so that they have the same arrangement of species
        self._structure_a = structure_a.get_sorted_structure()
        self._structure_b = structure_b.get_sorted_structure()
        if use_symmetry:
            from pymatgen.symmetry.spglib_adaptor import SymmetryFinder

            finder_a = SymmetryFinder(self._structure_a, symprec=0.1)
            finder_b = SymmetryFinder(self._structure_b, symprec=0.1)
            same_sg = finder_a.get_spacegroup_number() == finder_b.get_spacegroup_number()

        if not use_symmetry or same_sg:
            self._mapping_op = None
            if not self._anonymized:
                self.fit(self._structure_a, self._structure_b)
                if self.fit_found:
                    self.el_mapping = {el: el for el in self._structure_a.composition.elements}
            else:
                comp_a = structure_a.composition
                comp_b = structure_b.composition
                if len(comp_a.elements) == len(comp_b.elements):
                    el_a = comp_a.elements
                    # Create permutations of the specie/elements in structure A
                    for p in itertools.permutations(el_a):
                        # Create mapping of the specie/elements in structure B to that of A.
                        # Then create a modified structure with those elements and try to fit it.
                        el_mapping = dict(zip(comp_b.elements, p))
                        logger.debug("Using specie mapping " + str(el_mapping))
                        mod = StructureEditor(self._structure_b)
                        mod.replace_species(el_mapping)
                        self.fit(self._structure_a, mod.modified_structure)
                        if self._mapping_op != None:
                            # Store successful element mapping
                            self.el_mapping = {el_a: el_b for el_b, el_a in el_mapping.items()}
                            break
                else:
                    logger.debug("No. of elements in structures are unequal.")
        else:
            self._mapping_op = None
            logger.debug("Symmetry is different.")
예제 #19
0
class StructureEditorTest(unittest.TestCase):

    def setUp(self):

        self.si = Element("Si")
        self.fe = Element("Fe")
        self.ge = Element("Ge")
        coords = list()
        coords.append(np.array([0, 0, 0]))
        coords.append(np.array([0.75, 0.5, 0.75]))
        lattice = Lattice.cubic(10)
        s = Structure(lattice, [self.si, self.fe], coords)
        self.modifier = StructureEditor(s)

    def test_translate_sites(self):
        self.modifier.translate_sites([0, 1], [0.5, 0.5, 0.5], frac_coords=True)
        self.assertTrue(np.array_equal(self.modifier.modified_structure.frac_coords[0], np.array([ 0.5, 0.5, 0.5])))

        self.modifier.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=False)
        self.assertTrue(np.array_equal(self.modifier.modified_structure.cart_coords[0], np.array([ 5.5, 5.5, 5.5])))

    def test_append_site(self):
        self.modifier.append_site(self.si, [0, 0.5, 0])
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Si2", "Wrong formula!")
        self.assertRaises(ValueError, self.modifier.append_site, self.si, np.array([0, 0.5, 0]))

    def test_modified_structure(self):
        self.modifier.insert_site(1, self.si, [0, 0.25, 0])
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Si2", "Wrong formula!")

        self.modifier.delete_site(0)
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Si1", "Wrong formula!")

        self.modifier.replace_site(0, self.ge)
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Ge1", "Wrong formula!")

        self.modifier.append_site(self.si, [0, 0.75, 0])
        self.modifier.replace_species({self.si: self.ge})
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Ge2", "Wrong formula!")

        self.modifier.replace_species({self.ge: {self.ge:0.5, self.si:0.5}})
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Si1 Ge1", "Wrong formula!")

        #this should change the .5Si .5Ge sites to .75Si .25Ge
        self.modifier.replace_species({self.ge: {self.ge:0.5, self.si:0.5}})
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Si1.5 Ge0.5", "Wrong formula!")

        d = 0.1
        pre_perturbation_sites = self.modifier.modified_structure.sites
        self.modifier.perturb_structure(distance=d)
        post_perturbation_sites = self.modifier.modified_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_site_property(self):
        self.modifier.add_site_property("charge", [4.1, 5])
        s = self.modifier.modified_structure
        self.assertEqual(s[0].charge, 4.1)
        self.assertEqual(s[1].charge, 5)

        #test adding multiple properties.
        mod2 = StructureEditor(s)
        mod2.add_site_property("magmom", [3, 2])
        s = mod2.modified_structure
        self.assertEqual(s[0].charge, 4.1)
        self.assertEqual(s[0].magmom, 3)
 def apply_transformation(self, structure):
     editor = StructureEditor(structure)
     editor.delete_sites(self._indices)
     return editor.modified_structure
예제 #21
0
class StructureEditorTest(unittest.TestCase):

    def setUp(self):
        self.si = Element("Si")
        self.fe = Element("Fe")
        self.ge = Element("Ge")
        coords = list()
        coords.append(np.array([0, 0, 0]))
        coords.append(np.array([0.75, 0.5, 0.75]))
        lattice = Lattice.cubic(10)
        s = Structure(lattice, ["Si", "Fe"], coords)
        self.modifier = StructureEditor(s)

    def test_to_unit_cell(self):
        self.modifier.append_site(self.fe, [1.75, 0.5, 0.75],
                                  validate_proximity=False)
        self.modifier.to_unit_cell()
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Si1",
                         "Wrong formula!")

    def test_to_unit_cell(self):
        self.modifier.apply_strain(0.01)
        self.assertEqual(self.modifier.modified_structure.lattice.abc,
                         (10.1, 10.1, 10.1))

    def test_translate_sites(self):
        self.modifier.translate_sites([0, 1], [0.5, 0.5, 0.5],
                                      frac_coords=True)
        self.assertTrue(np.array_equal(self.modifier.modified_structure
                                       .frac_coords[0],
                                       np.array([0.5, 0.5, 0.5])))

        self.modifier.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=False)
        self.assertTrue(np.array_equal(self.modifier.modified_structure
                                       .cart_coords[0],
                                       np.array([5.5, 5.5, 5.5])))

    def test_append_site(self):
        self.modifier.append_site(self.si, [0, 0.5, 0])
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Si2",
                         "Wrong formula!")
        self.assertRaises(ValueError, self.modifier.append_site, self.si,
                          np.array([0, 0.5, 0]))

    def test_modified_structure(self):
        self.modifier.insert_site(1, self.si, [0, 0.25, 0])
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Si2",
                         "Wrong formula!")

        self.modifier.delete_site(0)
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Si1",
                         "Wrong formula!")

        self.modifier.replace_site(0, self.ge)
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Ge1",
                         "Wrong formula!")

        self.modifier.append_site(self.si, [0, 0.75, 0])
        self.modifier.replace_species({self.si: self.ge})
        self.assertEqual(self.modifier.modified_structure.formula, "Fe1 Ge2",
                         "Wrong formula!")

        self.modifier.replace_species({self.ge: {self.ge: 0.5, self.si: 0.5}})
        self.assertEqual(self.modifier.modified_structure.formula,
                         "Fe1 Si1 Ge1", "Wrong formula!")

        #this should change the .5Si .5Ge sites to .75Si .25Ge
        self.modifier.replace_species({self.ge: {self.ge: 0.5, self.si: 0.5}})
        self.assertEqual(self.modifier.modified_structure.formula,
                         "Fe1 Si1.5 Ge0.5", "Wrong formula!")

        d = 0.1
        pre_perturbation_sites = self.modifier.modified_structure.sites
        self.modifier.perturb_structure(distance=d)
        post_perturbation_sites = self.modifier.modified_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_site_property(self):
        self.modifier.add_site_property("charge", [4.1, 5])
        s = self.modifier.modified_structure
        self.assertEqual(s[0].charge, 4.1)
        self.assertEqual(s[1].charge, 5)

        #test adding multiple properties.
        mod2 = StructureEditor(s)
        mod2.add_site_property("magmom", [3, 2])
        s = mod2.modified_structure
        self.assertEqual(s[0].charge, 4.1)
        self.assertEqual(s[0].magmom, 3)

    def test_add_oxidation_states(self):
        si = Element("Si")
        fe = Element("Fe")
        coords = list()
        coords.append([0, 0, 0])
        coords.append([0.75, 0.5, 0.75])
        lattice = Lattice.cubic(10)
        s = Structure(lattice, [si, fe], coords)
        oxidation_states = {"Fe": 2, "Si": -4}
        mod = StructureEditor(s)
        mod.add_oxidation_state_by_element(oxidation_states)
        mod_s = mod.modified_structure
        for site in mod_s:
            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, mod.add_oxidation_state_by_element,
                          oxidation_states)
        mod.add_oxidation_state_by_site([2, -4])
        mod_s = mod.modified_structure
        self.assertEqual(mod_s[0].specie.oxi_state, 2)
        self.assertRaises(ValueError, mod.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)
        mod = StructureEditor(s_specie)
        mod.remove_oxidation_states()
        mod_s = mod.modified_structure
        self.assertEqual(s_elem, mod_s, "Oxidation state remover failed")
 def apply_transformation(self, structure):
     editor = StructureEditor(structure)
     for i, sp in self._indices_species_map.items():
         editor.replace_site(int(i), sp)
     return editor.modified_structure
 def apply_transformation(self, structure):
     editor = StructureEditor(structure)
     editor.apply_operation(self._symmop)
     return editor.modified_structure
 def apply_transformation(self, structure):
     editor = StructureEditor(structure)
     editor.perturb_structure(self._amp)
     return editor.modified_structure
예제 #25
0
    def _calc_rms(self, struct1, struct2, break_on_match):
        """
        Calculate RMS displacement between two structures

        Args:
            struct1:
                1st structure
            struct2:
                2nd structure
            break_on_match:
                True or False. Will break if the maximum
                    distance found is less than the
                    provided stol

        Returns:
            rms displacement normalized by (Vol / nsites) ** (1/3) and
            maximum distance found between two paired sites
        """
        stol = self.stol
        comparator = self._comparator
        #initial stored rms
        stored_rms = None

        if comparator.get_structure_hash(struct1) != \
                comparator.get_structure_hash(struct2):
            return None

        #primitive cell transformation
        if self._primitive_cell and struct1.num_sites != struct2.num_sites:
            struct1 = struct1.get_primitive_structure()
            struct2 = struct2.get_primitive_structure()

        # Same number of sites
        if struct1.num_sites != struct2.num_sites:
            return None

        # Get niggli reduced cells. Though technically not necessary, this
        # minimizes cell lengths and speeds up the matching of skewed
        # cells considerably.
        struct1 = struct1.get_reduced_structure(reduction_algo="niggli")
        struct2 = struct2.get_reduced_structure(reduction_algo="niggli")

        nl1 = struct1.lattice
        nl2 = struct2.lattice

        #rescale lattice to same volume
        if self._scale:
            scale_vol = (nl2.volume / nl1.volume) ** (1 / 6)
            se1 = StructureEditor(struct1)
            nl1 = Lattice(nl1.matrix * scale_vol)
            se1.modify_lattice(nl1)
            struct1 = se1.modified_structure
            se2 = StructureEditor(struct2)
            nl2 = Lattice(nl2.matrix / scale_vol)
            se2.modify_lattice(nl2)
            struct2 = se2.modified_structure

        #Volume to determine invalid lattices
        vol_tol = nl2.volume / 2

        #fractional tolerance of atomic positions (2x for initial fitting)
        frac_tol = \
            np.array([stol / ((1 - self.ltol) * np.pi) * i for
                      i in struct1.lattice.reciprocal_lattice.abc]) * \
            ((nl1.volume + nl2.volume) /
             (2 * struct1.num_sites)) ** (1.0 / 3)

        #generate structure coordinate lists
        species_list = []
        s1 = []
        for site in struct1:
            found = False
            for i, species in enumerate(species_list):
                if comparator.are_equal(site.species_and_occu, species):
                    found = True
                    s1[i].append(site.frac_coords)
                    break
            if not found:
                s1.append([site.frac_coords])
                species_list.append(site.species_and_occu)

        zipped = sorted(zip(s1, species_list), key=lambda x: len(x[0]))

        s1 = [x[0] for x in zipped]
        species_list = [x[1] for x in zipped]
        s2_cart = [[] for i in s1]

        for site in struct2:
            found = False
            for i, species in enumerate(species_list):
                if comparator.are_equal(site.species_and_occu, species):
                    found = True
                    s2_cart[i].append(site.coords)
                    break
                #if no site match found return None
            if not found:
                return None

        #check that sizes of the site groups are identical
        for f1, c2 in zip(s1, s2_cart):
            if len(f1) != len(c2):
                return None

        #translate s1
        s1_translation = s1[0][0]
        for i in range(len(species_list)):
            s1[i] = np.mod(s1[i] - s1_translation, 1)
            #do permutations of vectors, check for equality
        for nl in self._get_lattices(struct1, struct2, vol_tol):
            s2 = [nl.get_fractional_coords(c) for c in s2_cart]
            for coord in s2[0]:
                t_s2 = [np.mod(coords - coord, 1) for coords in s2]
                if self._cmp_fractional_struct(s1, t_s2, frac_tol):
                    rms, max_dist = self._cmp_cartesian_struct(s1, t_s2, nl,
                                                               nl1)
                    if break_on_match and max_dist < stol:
                        return max_dist
                    elif stored_rms is None or rms < stored_rms[0]:
                        stored_rms = rms, max_dist

        if break_on_match:
            return None
        else:
            return stored_rms
 def apply_transformation(self, structure):
     editor = StructureEditor(structure)
     editor.remove_oxidation_states()
     return editor.modified_structure
 def apply_transformation(self, structure):
     editor = StructureEditor(structure)
     editor.add_oxidation_state_by_element(self.oxi_states)
     return editor.modified_structure
예제 #28
0
def apply_operation(structure, symmop):
    editor = StructureEditor(structure)
    editor.apply_operation(symmop)
    return editor.modified_structure
예제 #29
0
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        For this transformation, the apply_transformation method will return
        only the ordered structure with the lowest Ewald energy, to be
        consistent with the method signature of the other transformations.  
        However, all structures are stored in the  all_structures attribute in
        the transformation object for easy access.
        
        Args:
            structure:
                Oxidation state decorated disordered structure to order
            return_ranked_list:
                Boolean stating 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 key 'transformation' is reserved for the transformation that
            was actually applied to the structure. 
            This transformation is parsed by the alchemy classes for generating
            a more specific transformation history. Any other information will
            be stored in the transformation_parameters dictionary in the 
            transmuted structure class.
        """
        ordered_sites = []
        sites_to_order = {}

        try:
            num_to_return = int(return_ranked_list)
        except:
            num_to_return = 1

        num_to_return = max(1, num_to_return)

        sites = list(structure.sites)
        for i in range(len(structure)):
            site = sites[i]
            if sum(site.species_and_occu.values()) == 1 and len(site.species_and_occu) == 1:
                ordered_sites.append(site)
            else:
                species = tuple([sp for sp, occu in site.species_and_occu.items()])
                #group the sites by the list of species on that site
                for sp, occu in site.species_and_occu.items():
                    if species not in sites_to_order:
                        sites_to_order[species] = {}
                    if sp not in sites_to_order[species]:
                        sites_to_order[species][sp] = [[occu, i]]
                    else:
                        sites_to_order[species][sp].append([occu, i])

                total_occu = sum(site.species_and_occu.values())
                #if the total occupancy on a site is less than one, add 
                #a list with None as the species (for removal)   
                if total_occu < 1:
                    if None not in sites_to_order[species]:
                        sites_to_order[species][None] = [[1 - total_occu, i]]
                    else:
                        sites_to_order[species][None].append([1 - total_occu, i])

        """
        Create a list of [multiplication fraction, number of replacements, 
        [indices], replacement species]
        """

        m_list = []
        se = StructureEditor(structure)

        for species in sites_to_order.values():
            initial_sp = None
            sorted_keys = sorted(species.keys(), key=lambda x: x is not None and -abs(x.oxi_state) or 1000)
            for sp in sorted_keys:
                if initial_sp is None:
                    initial_sp = sp
                    for site in species[sp]:
                        se.replace_site(site[1], initial_sp)
                else:
                    if sp is None:
                        oxi = 0
                    else:
                        oxi = float(sp.oxi_state)

                    manipulation = [oxi / initial_sp.oxi_state, 0, [], sp]
                    site_list = species[sp]
                    site_list.sort(key=itemgetter(0))

                    prev_fraction = site_list[0][0]
                    for site in site_list:
                        if site[0] - prev_fraction > .1:
                            """
                            tolerance for creating a new group of sites. 
                            if site occupancies are similar, they will be put
                            in a group where the fraction has to be consistent 
                            over the whole.
                            """
                            manipulation[1] = int(round(manipulation[1]))
                            m_list.append(manipulation)
                            manipulation = [oxi / initial_sp.oxi_state, 0, [], sp]
                        prev_fraction = site[0]
                        manipulation[1] += site[0]
                        manipulation[2].append(site[1])

                    if abs(manipulation[1] - round(manipulation[1])) > .25: #if the # of atoms to remove isn't within .25 of an integer
                        raise ValueError('Occupancy fractions not consistent with size of unit cell')

                    manipulation[1] = int(round(manipulation[1]))
                    m_list.append(manipulation)

        structure = se.modified_structure

        matrix = EwaldSummation(structure).total_energy_matrix

        ewald_m = EwaldMinimizer(matrix, m_list, num_to_return, self._algo)

        self._all_structures = []

        lowest_energy = ewald_m.output_lists[0][0]
        num_atoms = sum(structure.composition.values())

        for output in ewald_m.output_lists:
            se = StructureEditor(structure)
            del_indices = [] #do deletions afterwards because they screw up the indices of the structure

            for manipulation in output[1]:
                if manipulation[1] is None:
                    del_indices.append(manipulation[0])
                else:
                    se.replace_site(manipulation[0], manipulation[1])
            se.delete_sites(del_indices)
            self._all_structures.append({'energy':output[0], 'energy_above_minimum':(output[0] - lowest_energy) / num_atoms, 'structure': se.modified_structure.get_sorted_structure()})

        if return_ranked_list:
            return self._all_structures
        else:
            return self._all_structures[0]['structure']
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        For this transformation, the apply_transformation method will return
        only the ordered structure with the lowest Ewald energy, to be
        consistent with the method signature of the other transformations.
        However, all structures are stored in the  all_structures attribute in
        the transformation object for easy access.

        Args:
            structure:
                Oxidation state decorated disordered structure to order
            return_ranked_list:
                Boolean stating 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 key "transformation" is reserved for the transformation that
            was actually applied to the structure.
            This transformation is parsed by the alchemy classes for generating
            a more specific transformation history. Any other information will
            be stored in the transformation_parameters dictionary in the
            transmuted structure class.
        """

        try:
            num_to_return = int(return_ranked_list)
        except ValueError:
            num_to_return = 1

        num_to_return = max(1, num_to_return)

        equivalent_sites = []
        exemplars = []
        #generate list of equivalent sites to order
        #equivalency is determined by sp_and_occu and symmetry
        #if symmetrized structure is true
        for i, site in enumerate(structure):
            if site.is_ordered:
                continue
            found = False
            for j, ex in enumerate(exemplars):
                sp = ex.species_and_occu
                if not site.species_and_occu.almost_equals(sp):
                    continue
                if self._symmetrized:
                    sym_equiv = structure.find_equivalent_sites(ex)
                    sym_test = site in sym_equiv
                else:
                    sym_test = True
                if sym_test:
                    equivalent_sites[j].append(i)
                    found = True
            if not found:
                equivalent_sites.append([i])
                exemplars.append(site)

        #generate the list of manipulations and input structure
        se = StructureEditor(structure)
        m_list = []
        for g in equivalent_sites:
            total_occupancy = sum([structure[i].species_and_occu for i in g],
                                  Composition())
            total_occupancy = dict(total_occupancy.items())
            #round total occupancy to possible values
            for k, v in total_occupancy.items():
                if abs(v - round(v)) > 0.25:
                    raise ValueError("Occupancy fractions not consistent "
                                     "with size of unit cell")
                total_occupancy[k] = int(round(v))
            #start with an ordered structure
            initial_sp = max(total_occupancy.keys(),
                             key=lambda x: abs(x.oxi_state))
            for i in g:
                se.replace_site(i, initial_sp)
            #determine the manipulations
            for k, v in total_occupancy.items():
                if k == initial_sp:
                    continue
                m = [k.oxi_state / initial_sp.oxi_state, v, list(g), k]
                m_list.append(m)
            #determine the number of empty sites
            empty = len(g) - sum(total_occupancy.values())
            if empty > 0.5:
                m_list.append([0, empty, list(g), None])

        structure = se.modified_structure
        matrix = EwaldSummation(structure).total_energy_matrix
        ewald_m = EwaldMinimizer(matrix, m_list, num_to_return, self._algo)

        self._all_structures = []

        lowest_energy = ewald_m.output_lists[0][0]
        num_atoms = sum(structure.composition.values())

        for output in ewald_m.output_lists:
            se = StructureEditor(structure)
            # do deletions afterwards because they screw up the indices of the
            # structure
            del_indices = []
            for manipulation in output[1]:
                if manipulation[1] is None:
                    del_indices.append(manipulation[0])
                else:
                    se.replace_site(manipulation[0], manipulation[1])
            se.delete_sites(del_indices)
            self._all_structures.append(
                {"energy": output[0],
                 "energy_above_minimum":
                 (output[0] - lowest_energy) / num_atoms,
                 "structure": se.modified_structure.get_sorted_structure()})

        if return_ranked_list:
            return self._all_structures
        else:
            return self._all_structures[0]["structure"]
 def apply_transformation(self, structure):
     editor = StructureEditor(structure)
     editor.translate_sites(self._indices, self._vector, self._frac)
     return editor.modified_structure