Example #1
0
    def _complete_ordering(self, structure, num_remove_dict):
        self.logger.debug("Performing complete ordering...")
        all_structures = []
        symprec = 0.2
        s = SpacegroupAnalyzer(structure, symprec=symprec)
        self.logger.debug(
            "Symmetry of structure is determined to be {}.".format(
                s.get_space_group_symbol()))
        sg = s.get_space_group_operations()
        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)
            s_new = structure.copy()
            s_new.remove_sites(indices_list)
            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 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
Example #3
0
 def test_as_dict(self):
     ham = EwaldSummation(self.s, compute_forces=True)
     d = ham.as_dict()
     self.assertTrue(d["compute_forces"])
     self.assertEqual(d["eta"], ham._eta)
     self.assertEqual(d["acc_factor"], ham._accf)
     self.assertEqual(d["real_space_cut"], ham._rmax)
     self.assertEqual(d["recip_space_cut"], ham._gmax)
Example #4
0
    def test_site(self):
        """Test that uses an uncharged structure"""
        filepath = os.path.join(test_dir, "POSCAR")
        p = Poscar.from_file(filepath, check_for_POTCAR=False)
        original_s = p.structure
        s = original_s.copy()
        s.add_oxidation_state_by_element({"Li": 1, "Fe": 3, "P": 5, "O": -2})

        # Comparison to LAMMPS result
        ham = EwaldSummation(s, compute_forces=True)
        self.assertAlmostEquals(-1226.3335, ham.total_energy, 3)
        self.assertAlmostEquals(-45.8338, ham.get_site_energy(0), 3)
        self.assertAlmostEquals(-27.2978, ham.get_site_energy(8), 3)
Example #5
0
    def test_site(self):
        """Test that uses an uncharged structure"""
        filepath = os.path.join(test_dir, 'POSCAR')
        p = Poscar.from_file(filepath, check_for_POTCAR=False)
        original_s = p.structure
        s = original_s.copy()
        s.add_oxidation_state_by_element({"Li": 1, "Fe": 3,
                                          "P": 5, "O": -2})

        # Comparison to LAMMPS result
        ham = EwaldSummation(s, compute_forces=True)
        self.assertAlmostEquals(-1226.3335, ham.total_energy, 3)
        self.assertAlmostEquals(-45.8338, ham.get_site_energy(0), 3)
        self.assertAlmostEquals(-27.2978, ham.get_site_energy(8), 3)
def calc_elem_max_potential(structure_oxid: mg.Structure,
                            full_list=False,
                            check_vesta=False):
    """
    Return the maximum Madelung potential for all elements in a structure.

    :param structure_oxid: Pymatgen Structure with oxidation states for each site
    :param full_list: Boolean, if True, return all the site potentials associated with the element
    :param check_vesta: Boolean, if True, convert all site potentials' units from V to e/Angstrom
    :return: Dictionary: {Pymatgen Element: maximum Madelung potential for that element}
                      or {Pymatgen Element: list of all Madelung potentials for that element}
    """
    # set the conversion factor if the potential values needs to be compared with the ones from VESTA
    # conversion value obtained from page 107 of https://jp-minerals.org/vesta/archives/VESTA_Manual.pdf
    if check_vesta:
        vesta_conversion = 14.39965
    else:
        vesta_conversion = 1

    # define a dictionary that stores the oxidation states for all the element
    elem_charge_lookup = {
        specie.element: specie.oxi_state
        for specie in structure_oxid.composition.elements
    }
    # if there is only one element, then ewald summation will not work
    if len(elem_charge_lookup) == 1:
        return {elem: None for elem in elem_charge_lookup.keys()}

    # initialize the Ewald Summation object in order to calculate Ewald site energy
    ews = EwaldSummation(structure_oxid)
    # obtain the site indices where each element occupies
    elem_indices, *_ = get_elem_info(structure_oxid, makesupercell=False)
    # get the site potential using indices
    # the Ewald site energy is converted to site potential using V=2E/q
    # TODO: need to resolve division by zero error message
    site_potentials = {
        elem: [
            2 * ews.get_site_energy(index) /
            (elem_charge_lookup[elem] * vesta_conversion) for index in indices
        ]
        for elem, indices in elem_indices.items()
    }
    if full_list:
        return site_potentials

    return {
        elem: max(potentials)
        for elem, potentials in site_potentials.items()
    }
Example #7
0
    def test_init(self):
        filepath = os.path.join(test_dir, 'POSCAR')
        p = Poscar.from_file(filepath)
        original_s = p.structure
        s = original_s.copy()
        s.add_oxidation_state_by_element({"Li": 1, "Fe": 2, "P": 5, "O": -2})
        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)

        original_s.add_site_property('charge', charges)
        ham2 = EwaldSummation(original_s)
        self.assertAlmostEqual(ham2.real_space_energy, -354.91294268, 4,
                               "Real space energy incorrect!")
    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(f"Ewald sum took {time.time() - starttime} seconds.")
        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 range(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
        s = structure.copy()
        s.remove_sites(to_delete)
        self.logger.debug(f"Minimizing Ewald took {time.time() - starttime} seconds.")
        return [{"energy": sum(sum(ematrix)), "structure": s.get_sorted_structure()}]
Example #9
0
 def get_ewald_sum(self, structure):
     e = EwaldSummation(structure,
                        real_space_cut=None,
                        recip_space_cut=None,
                        eta=None,
                        acc_factor=8.0)
     return e.total_energy
Example #10
0
 def get_energy(self, structure):
     e = EwaldSummation(structure,
                        real_space_cut=self.real_space_cut,
                        recip_space_cut=self.recip_space_cut,
                        eta=self.eta,
                        acc_factor=self.acc_factor)
     return e.total_energy
Example #11
0
    def test_electrostatics(self):
        """Tests that the results are consistent with the electrostatic
        interpretation. Each matrix [i, j] element should correspond to the
        Coulomb energy of a system consisting of the pair of atoms i, j.
        """
        system = H2O
        n_atoms = len(system)
        a = 0.5
        desc = EwaldSumMatrix(n_atoms_max=3, permutation="none", flatten=False)

        # The Ewald matrix contains the electrostatic interaction between atoms
        # i and j. Here we construct the total electrostatic energy for a
        # system consisting of atoms i and j.
        matrix = desc.create(system, a=a, rcut=rcut, gcut=gcut)
        energy_matrix = np.zeros(matrix.shape)
        for i in range(n_atoms):
            for j in range(n_atoms):
                if i == j:
                    energy_matrix[i, j] = matrix[i, j]
                else:
                    energy_matrix[i, j] = matrix[i, j] + matrix[i, i] + matrix[j, j]

        # Converts unit of q*q/r into eV
        conversion = 1e10 * scipy.constants.e / (4 * math.pi * scipy.constants.epsilon_0)
        energy_matrix *= conversion

        # The value in each matrix element should correspond to the Coulomb
        # energy of a system with with only those atoms. Here the energies from
        # the Ewald matrix are compared against the Ewald energy calculated
        # with pymatgen.
        positions = system.get_positions()
        atomic_num = system.get_atomic_numbers()
        for i in range(n_atoms):
            for j in range(n_atoms):
                if i == j:
                    pos = [positions[i]]
                    sym = [atomic_num[i]]
                else:
                    pos = [positions[i], positions[j]]
                    sym = [atomic_num[i], atomic_num[j]]

                i_sys = Atoms(
                    cell=system.get_cell(),
                    positions=pos,
                    symbols=sym,
                    pbc=True,
                )

                structure = Structure(
                    lattice=i_sys.get_cell(),
                    species=i_sys.get_atomic_numbers(),
                    coords=i_sys.get_scaled_positions(),
                )
                structure.add_oxidation_state_by_site(i_sys.get_atomic_numbers())
                ewald = EwaldSummation(structure, eta=a, real_space_cut=rcut, recip_space_cut=gcut)
                energy = ewald.total_energy

                # Check that the energy given by the pymatgen implementation is
                # the same as given by the descriptor
                self.assertTrue(np.allclose(energy_matrix[i, j], energy, atol=0.00001, rtol=0))
Example #12
0
def get_ewald_energy(atoms, use_density=True):
    oxi_states = get_oxidation_states(atoms)

    structure = AseAtomsAdaptor.get_structure(atoms)
    structure.add_oxidation_state_by_site(oxi_states)
    e = EwaldSummation(structure).total_energy

    return e / len(atoms)
Example #13
0
    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:
            s = structure.copy()
            del_indices = []

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

        return all_structures
Example #14
0
    def featurize(self, strc, idx):
        """
        Args:
            struct (Structure): Pymatgen Structure object.
            idx (int): index of target site in structure.
        Returns:
            ([float]) - Electrostatic energy of the site
        """

        # Check if the new input is the last
        #  Note: We use 'is' rather than structure comparisons for speed
        if strc is self.__last_structure:
            ewald = self.__last_ewald
        else:
            self.__last_structure = strc
            ewald = EwaldSummation(strc, acc_factor=self.accuracy)
            self.__last_ewald = ewald
        return [ewald.get_site_energy(idx)]
Example #15
0
    def test_init(self):
        filepath = os.path.join(test_dir, 'POSCAR')
        p = Poscar.from_file(filepath)
        original_s = p.structure
        s = original_s.copy()
        s.add_oxidation_state_by_element({"Li": 1, "Fe": 2,
                                          "P": 5, "O": -2})

        ham = EwaldSummation(s, compute_forces=True)
        self.assertAlmostEqual(ham.real_space_energy, -502.23549897772602, 4)
        self.assertAlmostEqual(ham.reciprocal_space_energy,  6.1541071599534654, 4)
        self.assertAlmostEqual(ham.point_energy, -620.22598358035918, 4)
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            self.assertAlmostEqual(ham.total_energy, -1116.30737539811, 2)
        self.assertAlmostEqual(ham.forces[0, 0], -1.98818620e-01, 4)
        self.assertAlmostEqual(sum(sum(abs(ham.forces))), 915.925354346, 4,
                               "Forces incorrect")
        self.assertAlmostEqual(sum(sum(ham.real_space_energy_matrix)),
                               ham.real_space_energy, 4)
        self.assertAlmostEqual(sum(sum(ham.reciprocal_space_energy_matrix)),
                               ham.reciprocal_space_energy, 4)
        self.assertAlmostEqual(sum(ham.point_energy_matrix), ham.point_energy,
                               4)
        self.assertAlmostEqual(sum(sum(ham.total_energy_matrix)),
                               ham.total_energy, 2)
        #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)

        original_s.add_site_property('charge', charges)
        ham2 = EwaldSummation(original_s)
        self.assertAlmostEqual(ham2.real_space_energy, -502.23549897772602, 4)
Example #16
0
def cm_es(pymat_struct, zeros_up_to=2000):
    """note: function pads constructed descr with zeros up to chosen zeros_up_to. Function
    df_CM_ES_tayloring in create_descriptors.py taylors it to the max non zero index in the
    given dataset"""
    pymat_struct.add_site_property("charge", pymat_struct.atomic_numbers)
    energy_matrix = EwaldSummation(pymat_struct).total_energy_matrix
    e_val_vector = eigvalsh(energy_matrix)
    descr_with_pad_zeros = np.zeros(zeros_up_to)
    descr_with_pad_zeros[:len(e_val_vector)] = e_val_vector
    return descr_with_pad_zeros
Example #17
0
    def test_init(self):
        filepath = os.path.join(test_dir, 'POSCAR')
        p = Poscar.from_file(filepath, check_for_POTCAR=False)
        original_s = p.structure
        s = original_s.copy()
        s.add_oxidation_state_by_element({"Li": 1, "Fe": 2, "P": 5, "O": -2})

        ham = EwaldSummation(s, compute_forces=True)
        self.assertAlmostEqual(ham.real_space_energy, -502.23549897772602, 4)
        self.assertAlmostEqual(ham.reciprocal_space_energy, 6.1541071599534654,
                               4)
        self.assertAlmostEqual(ham.point_energy, -620.22598358035918, 4)
        self.assertAlmostEqual(ham.total_energy, -1123.00766, 1)
        self.assertAlmostEqual(ham.forces[0, 0], -1.98818620e-01, 4)
        self.assertAlmostEqual(sum(sum(abs(ham.forces))), 915.925354346, 4,
                               "Forces incorrect")
        self.assertAlmostEqual(sum(sum(ham.real_space_energy_matrix)),
                               ham.real_space_energy, 4)
        self.assertAlmostEqual(sum(sum(ham.reciprocal_space_energy_matrix)),
                               ham.reciprocal_space_energy, 4)
        self.assertAlmostEqual(sum(ham.point_energy_matrix), ham.point_energy,
                               4)
        self.assertAlmostEqual(
            sum(sum(ham.total_energy_matrix)) + ham._charged_cell_energy,
            ham.total_energy, 2)

        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)

        original_s.add_site_property('charge', charges)
        ham2 = EwaldSummation(original_s)
        self.assertAlmostEqual(ham2.real_space_energy, -502.23549897772602, 4)
Example #18
0
def get_madelung_tag_single(rundict, force=False, verbose=0, oxi_int=False):
    struct_data = rundict.structure_data
    if not force and struct_data[0].properties.get("E_mad", None) is not None:
        print("skipping")
        return True
    poscar, potcar = [p.from_file(rundict.job_folder+f) for (p, f) in
                      [(Poscar, "/POSCAR"), (Potcar, "/POTCAR")]]
    s_dict = {}
    if verbose > 0:
        print("poscar/potcar parsed")
    if oxi_int:
        oxi = rundict.structure.copy()
        try:
            oxi.add_oxidation_state_by_guess()
        except ValueError:
            print("Forced to set oxidation by hand")
            oxi.add_oxidation_state_by_element({"Na": 1,
                                                "Mg": 2,
                                                "Mn": 3.5,
                                                "O": -2,
                                                "Li": 1,
                                                "Ca": 2,
                                                "Fe": 3,
                                                "Zn": 4,
                                                "Co": 2.5,
                                                "Ti": 4,
                                                "S": -2,
                                                'P': 5})
        ew_sum_int = EwaldSummation(oxi)
        oxi.add_site_property("E_mad_int",
                              [ew_sum_int.get_site_energy(i)
                               for i in range(oxi.num_sites)])
        s_dict['oxi_int'] = oxi
        struct_data.add_site_property("E_mad_int",
                                      [ew_sum_int.get_site_energy(i)
                                       for i in range(oxi.num_sites)])
        if verbose > 0:
            print("integer oxidation state computed")

    if struct_data[0].properties.get("charge", None) is None:
        print("No bader charges => no bader madelung")
        return False
        # Test if bader charges were computed correctly

    get_oxidation_state_decorated_structure(struct_data,
                                            potcar, poscar)
    if verbose > 0:
        print("bader oxidation state computed")

    ew_sum = EwaldSummation(rundict.structure_data)
    # madelung_array = []
    struct_data.add_site_property("E_mad",
                                  [ew_sum.get_site_energy(i)
                                   for i in range(struct_data.num_sites)])
    s_dict["oxi_bader"] = struct_data
    print(" ".join(s_dict.values()))
    return True
Example #19
0
def get_I_mads(struc):
    # Add oxidation states
    try:
        struc = bva.get_oxi_state_decorated_structure(struc)
        #print (struc)
    except:
        struc.add_oxidation_state_by_guess()
    
	# Check if a specific species is present 
    if (Specie('I',-1) in struc.species):
        ews = EwaldSummation(struc)
        print (ews)
        I_indices = [n  for n,site in enumerate(struc) if 
                             site.specie.symbol == 'I']
        I_mads = np.array([ews.get_site_energy(n) for n in I_indices])
        print (I_indices)
        print (I_mads)
        return I_mads
        return ews.total_energy
        print (ews.total_energy)
Example #20
0
 def get_energy(self, structure):
     """
     :param structure: Structure
     :return: Energy value
     """
     e = EwaldSummation(structure,
                        real_space_cut=self.real_space_cut,
                        recip_space_cut=self.recip_space_cut,
                        eta=self.eta,
                        acc_factor=self.acc_factor)
     return e.total_energy
Example #21
0
    def featurize(self, strc):
        """

        Args:
             (Structure) - Structure being analyzed
        Returns:
            ([float]) - Electrostatic energy of the structure
        """
        # Compute the total energy
        ewald = EwaldSummation(strc, acc_factor=self.accuracy)
        return [ewald.total_energy]
Example #22
0
    def featurize(self, strc, idx):
        """
        Args:
            struct (Structure): Pymatgen Structure object.
            idx (int): index of target site in structure.
        Returns:
            ([float]) - Electrostatic energy of the site
        """

        # Check if the new input is the last
        #  Note: We use 'is' rather than structure comparisons for speed
        #
        #  TODO: Figure out if this implementation is thread-safe! I was debating adding
        #        Locks, but think we are OK
        if strc is self.__last_structure:
            ewald = self.__last_ewald
        else:
            self.__last_structure = strc
            ewald = EwaldSummation(strc, acc_factor=self.accuracy)
            self.__last_ewald = ewald
        return [ewald.get_site_energy(idx)]
Example #23
0
    def test_init(self):
        ham = EwaldSummation(self.s, compute_forces=True)
        self.assertAlmostEqual(ham.real_space_energy, -502.23549897772602, 4)
        self.assertAlmostEqual(ham.reciprocal_space_energy, 6.1541071599534654, 4)
        self.assertAlmostEqual(ham.point_energy, -620.22598358035918, 4)
        self.assertAlmostEqual(ham.total_energy, -1123.00766, 1)
        self.assertAlmostEqual(ham.forces[0, 0], -1.98818620e-01, 4)
        self.assertAlmostEqual(
            sum(sum(abs(ham.forces))), 915.925354346, 4, "Forces incorrect"
        )
        self.assertAlmostEqual(
            sum(sum(ham.real_space_energy_matrix)), ham.real_space_energy, 4
        )
        self.assertAlmostEqual(
            sum(sum(ham.reciprocal_space_energy_matrix)), ham.reciprocal_space_energy, 4
        )
        self.assertAlmostEqual(sum(ham.point_energy_matrix), ham.point_energy, 4)
        self.assertAlmostEqual(
            sum(sum(ham.total_energy_matrix)) + ham._charged_cell_energy,
            ham.total_energy,
            2,
        )

        self.assertRaises(ValueError, EwaldSummation, self.original_s)
        # try sites with charge.
        charges = []
        for site in self.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)

        self.original_s.add_site_property("charge", charges)
        ham2 = EwaldSummation(self.original_s)
        self.assertAlmostEqual(ham2.real_space_energy, -502.23549897772602, 4)
Example #24
0
def EMad(structure, Bsite):
# Determines the site Madelung energies for the Bsite and Osite
    B_indexes = []
    O_indexes = []
    for index in range(len(structure.sites)):
        if Bsite in str(structure.sites[index]):
            B_indexes.append(index)
        elif 'O' in str(structure.sites[index]):
            O_indexes.append(index)

    # calculate Madelung energy
    mad_energy = EwaldSummation(structure)
    site_energies = np.array([])
    site_energies = sum(mad_energy.total_energy_matrix)

    # max V_mad_M and min V_mad_O gives the min CT energy
    V_mad_M = max(site_energies[B_indexes])
    V_mad_O = min(-site_energies[O_indexes])

    return [V_mad_M, V_mad_O]
Example #25
0
 def test_from_dict(self):
     ham = EwaldSummation(self.s, compute_forces=True)
     ham2 = EwaldSummation.from_dict(ham.as_dict())
     self.assertIsNone(ham._real)
     self.assertFalse(ham._initialized)
     self.assertIsNone(ham2._real)
     self.assertFalse(ham2._initialized)
     self.assertTrue(
         np.array_equal(ham.total_energy_matrix, ham2.total_energy_matrix))
     # check lazy eval
     self.assertAlmostEqual(ham.total_energy, -1123.00766, 1)
     self.assertIsNotNone(ham._real)
     self.assertTrue(ham._initialized)
     ham2 = EwaldSummation.from_dict(ham.as_dict())
     self.assertIsNotNone(ham2._real)
     self.assertTrue(ham2._initialized)
     self.assertTrue(
         np.array_equal(ham.total_energy_matrix, ham2.total_energy_matrix))
def Calc_Ewald(pmg_struct, formal_val=[]):
    """
    input: pmg_struct: pymatgen structure
           formal_val: list - list of valence for each atom

    TBD: use input formal valence list to decorate the structure
    """
    # GET valence list
    if len(formal_val)==0:
        bv_analyzer=BVAnalyzer(max_radius=4) #max_radius default is 4
        formal_val=bv_analyzer.get_valences(pmg_struct)

    # default for cutoff is set to None
    real_cutoff = None
    rec_cutoff = None

    # Oxidation states are decorated automatically.  
    decorated_struct = bv_analyzer.get_oxi_state_decorated_structure(pmg_struct)
    NUM_sites=pmg_struct.num_sites
    
    # Per atom
    ewald_per_atom=1/NUM_sites*EwaldSummation(decorated_struct,
        real_space_cut=real_cutoff, recip_space_cut=rec_cutoff).total_energy
    return ewald_per_atom
    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 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"]
Example #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 (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 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)

        if self.no_oxi_states:
            structure = Structure.from_sites(structure)
            for i, site in enumerate(structure):
                structure[i] = {"%s0+" % k.symbol: v for k, v in site.species.items()}

        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
            for j, ex in enumerate(exemplars):
                sp = ex.species
                if not site.species.almost_equals(sp):
                    continue
                if self.symmetrized_structures:
                    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)
                    break
            else:
                equivalent_sites.append([i])
                exemplars.append(site)

        # generate the list of manipulations and input structure
        s = Structure.from_sites(structure)

        m_list = []
        for g in equivalent_sites:
            total_occupancy = sum((structure[i].species 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:
                s[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 if initial_sp.oxi_state else 0,
                    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])

        matrix = EwaldSummation(s).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:
            s_copy = s.copy()
            # 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:
                    s_copy[manipulation[0]] = manipulation[1]
            s_copy.remove_sites(del_indices)

            if self.no_oxi_states:
                s_copy.remove_oxidation_states()

            self._all_structures.append(
                {
                    "energy": output[0],
                    "energy_above_minimum": (output[0] - lowest_energy) / num_atoms,
                    "structure": s_copy.get_sorted_structure(),
                }
            )

        if return_ranked_list:
            return self._all_structures[:num_to_return]
        return self._all_structures[0]["structure"]
Example #30
0
def calc_redox(cl1,
               cl2,
               energy_ref=None,
               value=0,
               temp=None,
               silent=0,
               mode=None,
               scale=1):
    """
    Calculated average redox potential and change of volume
    cl1 (Calculation) - structure with higher concentration
    cl2 (Calculation) - structure with lower concentration
    energy_ref (float) - energy in eV per one alkali ion in anode; default value is for Li; -1.31 eV for Na, -1.02 eV for K
    
    temp(float) - potential at temperature, self.F is expected from phonopy calculations
    
    mode (str) - special 
        electrostatic_only - use Ewald summation to obtain electrostatic energy
        ewald_vasp

    scale - experimental 


    return dic {'redox_pot', 'vol_red', ...}
    """
    if cl1 is None or cl2 is None:
        printlog('Warning! cl1 or cl2 is none; return')
        return
    if not hasattr(cl1.end, 'znucl') or not hasattr(cl2.end, 'znucl'):
        printlog('Warning! cl1 or cl2 is bad')
        return

    energy_ref_dict = {3: -1.9, 11: -1.31, 19: -1.02, 37: -0.93}
    z_alk_ions = [3, 11, 19, 37]

    #normalize numbers of atoms by some element except Li, Na, K
    alk1l = []
    alk2l = []
    # print cl1.end.znucl
    for i, z in enumerate(cl1.end.znucl):
        # print i, z
        if z in z_alk_ions:
            alk1l.append(i)
            # print 'i_alk is found'
            continue
        # print i, z

        for j, zb in enumerate(cl2.end.znucl):
            if zb in z_alk_ions:
                # j_alk = j
                alk2l.append(j)
                continue

            if z == zb:
                # print "I use ", z, " to normalize"
                i_n1 = i
                i_n2 = j

    n1 = cl1.end.nznucl[i_n1]
    n2 = cl2.end.nznucl[i_n2]

    # print(n1,n2)

    nz1_dict = {}
    nz2_dict = {}
    n_alk1 = 0
    n_alk2 = 0
    for z in z_alk_ions:
        nz1_dict[z] = 0
        nz2_dict[z] = 0

    for i in alk1l:
        nz1_dict[cl1.end.znucl[i]] = cl1.end.nznucl[i]
    for i in alk2l:
        nz2_dict[cl2.end.znucl[i]] = cl2.end.nznucl[i]

    for z in z_alk_ions:
        mul = (nz1_dict[z] / n1 - nz2_dict[z] / n2)
        # print(mul)
        if abs(
                mul
        ) > 0:  #only change of concentration of one ion type is allowed; the first found is used
            printlog('Change of concentration detected for ',
                     element_name_inv(z))
            if not energy_ref:  #take energy ref from dict
                energy_ref = energy_ref_dict[z]
            break

    # print(energy_ref)
    # print(cl1.energy_sigma0, cl2.energy_sigma0, mul)

    if mode == 'electrostatic_only':
        # st1 = cl1.end.copy()
        # st2 = cl2.end.copy()
        st1 = cl1.end
        st2 = cl2.end
        # st1 = set_oxidation_states(st1)
        # st2 = set_oxidation_states(st2)

        # st1 = st1.remove_atoms(['Ti'])
        st1.charges = cl1.charges
        st2.charges = cl2.charges
        # sys.exit()
        stpm1 = st1.convert2pymatgen(chg_type='ox')
        stpm2 = st2.convert2pymatgen(chg_type='ox')
        ew1 = EwaldSummation(stpm1)
        ew2 = EwaldSummation(stpm2)

        e1 = ew1.total_energy
        e2 = ew2.total_energy
        # print(ew1.get_site_energy(0), ew1.get_site_energy(4), ew2.get_site_energy(9) )

    elif mode == 'ewald_vasp':
        e1 = cl1.energy.ewald
        e2 = cl2.energy.ewald

    else:
        e1 = cl1.e0
        e2 = cl2.e0

    print(e1, e2)

    if temp != None:
        #temperature corrections
        e1 += cl1.F(temp)
        e2 += cl2.F(temp)
        # print(cl1.F(temp), cl2.F(temp))
        # print(e1, cl1.energy_sigma0)
        # print(e2, cl2.energy_sigma0)

    if abs(mul) > 0:
        redox = -((e1 / n1 - e2 / n2) / mul - energy_ref) / scale
    else:
        redox = 0

    # print(n1, n2)

    dV = cl1.end.vol / n1 - cl2.end.vol / n2

    vol_red = dV / (cl1.end.vol / n1) * 100  # %

    # final_outstring = ("{:} | {:.2f} eV \n1".format(cl1.id[0]+'.'+cl1.id[1], redox  ))
    final_outstring = (
        "{:45} | {:30} | {:10.2f} V | {:10.1f} % | {:6.2f}| {:6.2f}| {:6.0f}| {:6.0f} | {:3.0f}"
        .format(cl1.name, cl2.name, redox, vol_red, cl1.energy_sigma0,
                cl2.energy_sigma0, cl1.maxforce, cl2.maxforce, value))

    if not silent:
        printlog(final_outstring, end='\n', imp='y')

    try:
        cl1.set.update()

        results_dic = {
            'is': cl1.id[0],
            'redox_pot': redox,
            'id_is': cl1.id,
            'id_ds': cl2.id,
            'kspacing': cl1.set.kspacing,
            'time': cl1.time / 3600.,
            'mdstep': cl1.mdstep,
            'ecut': cl1.set.ecut,
            'niter': cl1.iterat / cl1.mdstep,
            'set_is': cl1.id[1],
            'vol_red': vol_red
        }
    except:
        results_dic = {'redox_pot': redox, 'vol_red': vol_red}

    return results_dic
    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"]
Example #32
0
 def ewald_summation(self, structure):
     ewald_energy = EwaldSummation(structure).total_energy
     return ewald_energy
Example #33
0
def _eval_structure(oxi, filename):
    struc = Poscar.from_file(filename).structure
    struc.add_oxidation_state_by_element(oxi)
    assert(struc.charge == 0.0)
    energy = EwaldSummation(struc).total_energy
    return (filename, energy)
Example #34
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"]
    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"]