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
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)
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 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() }
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()}]
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
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
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))
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)
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
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)]
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)
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
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)
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
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)
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
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]
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)]
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)
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]
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"]
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"]
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"]
def ewald_summation(self, structure): ewald_energy = EwaldSummation(structure).total_energy return ewald_energy
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)
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"]