def test_attributes(self): is_true = {("Xe", "Kr") : "is_noble_gas", ("Fe", "Ni") : 'is_transition_metal', ('Li', 'Cs') : 'is_alkali', ('Ca', 'Mg') : 'is_alkaline', ('F', 'Br', 'I') : 'is_halogen', ('La',) : 'is_lanthanoid', ('U', 'Pu') : 'is_actinoid', ('Si', 'Ge') : 'is_metalloid' } for k, v in is_true.items(): for sym in k: self.assertTrue(getattr(Element(sym), v), sym + ' is false') keys = ["name", "Z", "mendeleev_no", "atomic_mass", "electronic_structure", "X", "atomic_radius", "min_oxidation_state", "max_oxidation_state", "electrical_resistivity", "velocity_of_sound", "reflectivity", "refractive_index", "poissons_ratio", "molar_volume", "thermal_conductivity", "melting_point", "boiling_point", "liquid_range", "critical_temperature", "superconduction_temperature", "bulk_modulus", "youngs_modulus", "brinell_hardness", "rigidity_modulus", "mineral_hardness", "vickers_hardness", "density_of_solid", "coefficient_of_linear_thermal_expansion", "oxidation_states", "common_oxidation_states", 'average_ionic_radius', 'ionic_radii'] #Test all elements up to Uranium for i in range(1, 93): for k in keys: self.assertIsNotNone(getattr(Element.from_Z(i), k)) el = Element.from_Z(i) if len(el.oxidation_states) > 0: self.assertEqual(max(el.oxidation_states), el.max_oxidation_state) self.assertEqual(min(el.oxidation_states), el.min_oxidation_state)
def test_attributes(self): is_true = {("Xe", "Kr"): "is_noble_gas", ("Fe", "Ni"): "is_transition_metal", ("Li", "Cs"): "is_alkali", ("Ca", "Mg"): "is_alkaline", ("F", "Br", "I"): "is_halogen", ("La",): "is_lanthanoid", ("U", "Pu"): "is_actinoid", ("Si", "Ge"): "is_metalloid", ("O", "Te"): "is_chalcogen"} for k, v in is_true.items(): for sym in k: self.assertTrue(getattr(Element(sym), v), sym + " is false") keys = ["mendeleev_no", "atomic_mass", "electronic_structure", "atomic_radius", "min_oxidation_state", "max_oxidation_state", "electrical_resistivity", "velocity_of_sound", "reflectivity", "refractive_index", "poissons_ratio", "molar_volume", "thermal_conductivity", "melting_point", "boiling_point", "liquid_range", "critical_temperature", "superconduction_temperature", "bulk_modulus", "youngs_modulus", "brinell_hardness", "rigidity_modulus", "mineral_hardness", "vickers_hardness", "density_of_solid", "atomic_orbitals", "coefficient_of_linear_thermal_expansion", "oxidation_states", "common_oxidation_states", "average_ionic_radius", "average_cationic_radius", "average_anionic_radius", "ionic_radii", "long_name", "metallic_radius", "iupac_ordering"] # Test all elements up to Uranium for i in range(1, 104): el = Element.from_Z(i) d = el.data for k in keys: k_str = k.capitalize().replace("_", " ") if k_str in d and (not str(d[k_str]).startswith("no data")): self.assertIsNotNone(getattr(el, k)) elif k == "long_name": self.assertEqual(getattr(el, "long_name"), d["Name"]) elif k == "iupac_ordering": self.assertTrue("IUPAC ordering" in d) self.assertIsNotNone(getattr(el, k)) el = Element.from_Z(i) if len(el.oxidation_states) > 0: self.assertEqual(max(el.oxidation_states), el.max_oxidation_state) self.assertEqual(min(el.oxidation_states), el.min_oxidation_state) if el.symbol not in ["He", "Ne", "Ar"]: self.assertTrue(el.X > 0, "No electroneg for %s" % el) self.assertRaises(ValueError, Element.from_Z, 1000)
def test_equals(self): random_z = random.randint(1, 92) fixed_el = Element.from_Z(random_z) other_z = random.randint(1, 92) while other_z == random_z: other_z = random.randint(1, 92) comp1 = Composition({fixed_el:1, Element.from_Z(other_z) :0}) other_z = random.randint(1, 92) while other_z == random_z: other_z = random.randint(1, 92) comp2 = Composition({fixed_el:1, Element.from_Z(other_z) :0}) self.assertEqual(comp1, comp2, "Composition equality test failed. %s should be equal to %s" % (comp1.formula, comp2.formula)) self.assertEqual(comp1.__hash__(), comp2.__hash__(), "Hashcode equality test failed!")
def test_element(self): symbols = list() for i in range(1, 102): el = Element.from_Z(i) self.assertGreater(el.atomic_mass, 0, "Atomic mass cannot be negative!") self.assertNotIn(el.symbol, symbols, "Duplicate symbol for " + el.symbol) symbols.append(""" + el.symbol + """) self.assertIsNotNone(el.group, "Group cannot be none for Z=" + str(i)) self.assertIsNotNone(el.row, "Row cannot be none for Z=" + str(i)) #Test all properties all_attr = ["Z", "symbol", "X", "name", "atomic_mass", "atomic_radius", "max_oxidation_state", "min_oxidation_state", "mendeleev_no", "electrical_resistivity", "velocity_of_sound", "reflectivity", "refractive_index", "poissons_ratio", "molar_volume", "electronic_structure", "thermal_conductivity", "boiling_point", "melting_point", "critical_temperature", "superconduction_temperature", "liquid_range", "bulk_modulus", "youngs_modulus", "brinell_hardness", "rigidity_modulus", "mineral_hardness", "vickers_hardness", "density_of_solid", "coefficient_of_linear_thermal_expansion"] for a in all_attr: self.assertIsNotNone(el, a)
def from_dict(cls, d, lattice=None): """ Create PeriodicSite from dict representation. Args: d (dict): dict representation of PeriodicSite lattice: Optional lattice to override lattice specified in d. Useful for ensuring all sites in a structure share the same lattice. Returns: PeriodicSite """ atoms_n_occu = {} for sp_occu in d["species"]: if "oxidation_state" in sp_occu and Element.is_valid_symbol( sp_occu["element"]): sp = Specie.from_dict(sp_occu) elif "oxidation_state" in sp_occu: sp = DummySpecie.from_dict(sp_occu) else: sp = Element(sp_occu["element"]) atoms_n_occu[sp] = sp_occu["occu"] props = d.get("properties", None) lattice = lattice if lattice else Lattice.from_dict(d["lattice"]) return cls(atoms_n_occu, d["abc"], lattice, properties=props)
def _parse_chomp_and_rank(m, f, m_dict, m_points): """ A helper method for formula parsing that helps in interpreting and ranking indeterminate formulas Author: Anubhav Jain Args: m: A regex match, with the first group being the element and the second group being the amount f: The formula part containing the match m_dict: A symbol:amt dictionary from the previously parsed formula m_points: Number of points gained from the previously parsed formula Returns: A tuple of (f, m_dict, points) where m_dict now contains data from the match and the match has been removed (chomped) from the formula f. The "goodness" of the match determines the number of points returned for chomping. Returns (None, None, None) if no element could be found... """ points = 0 # Points awarded if the first element of the element is correctly # specified as a capital points_first_capital = 100 # Points awarded if the second letter of the element is correctly # specified as lowercase points_second_lowercase = 100 #get element and amount from regex match el = m.group(1) if len(el) > 2 or len(el) < 1: raise CompositionError("Invalid element symbol entered!") amt = float(m.group(2)) if m.group(2).strip() != "" else 1 #convert the element string to proper [uppercase,lowercase] format #and award points if it is already in that format char1 = el[0] char2 = el[1] if len(el) > 1 else "" if char1 == char1.upper(): points += points_first_capital if char2 and char2 == char2.lower(): points += points_second_lowercase el = char1.upper() + char2.lower() #if it's a valid element, chomp and add to the points if Element.is_valid_symbol(el): if el in m_dict: m_dict[el] += amt * factor else: m_dict[el] = amt * factor return f.replace(m.group(), "", 1), m_dict, m_points + points #else return None return None, None, None
def test_pickle(self): el1 = Element.Fe o = pickle.dumps(el1) self.assertEqual(el1, pickle.loads(o)) #Test all elements up to Uranium for i in range(1, 93): self.serialize_with_pickle(Element.from_Z(i), test_eq=True)
def from_dict(cls, d): """ Create Site from dict representation """ atoms_n_occu = {} for sp_occu in d["species"]: if "oxidation_state" in sp_occu and Element.is_valid_symbol( sp_occu["element"]): sp = Specie.from_dict(sp_occu) elif "oxidation_state" in sp_occu: sp = DummySpecie.from_dict(sp_occu) else: sp = Element(sp_occu["element"]) atoms_n_occu[sp] = sp_occu["occu"] props = d.get("properties", None) return cls(atoms_n_occu, d["xyz"], properties=props)
def from_string(string, symbols=None): structures = [] timesteps = [] steps = string.split("ITEM: TIMESTEP") steps.pop(0) for step in steps: lines = tuple(step.split("\n")) mdstep = int(lines[1]) natoms = int(lines[3]) xbox = tuple((lines[5] + " 0").split()) ybox = tuple((lines[6] + " 0").split()) zbox = tuple((lines[7] + " 0").split()) xlo = float(xbox[0]) xhi = float(xbox[1]) xy = float(xbox[2]) ylo = float(ybox[0]) yhi = float(ybox[1]) xz = float(ybox[2]) zlo = float(zbox[0]) zhi = float(zbox[1]) yz = float(zbox[2]) xlo -= np.min([0, xy, xz, xy+xz]) xhi -= np.max([0, xy, xz, xy+xz]) ylo -= np.min([0, yz]) yhi -= np.max([0, yz]) lattice = [xhi-xlo, 0, 0, xy, yhi-ylo, 0, xz, yz, zhi-zlo] atoms = [[float(j) for j in line.split()] for line in lines[9:-1]] atoms = sorted(atoms, key=lambda a_entry: a_entry[0], reverse=True) coords = [] atomic_sym = [] while (len(atoms)): one = atoms.pop() typ = one[1] coo = one[2:5] sym = symbols[typ] if symbols else Element.from_Z(typ).symbol atomic_sym.append(sym) coords.append(coo) struct = Structure(lattice, atomic_sym, coords, to_unit_cell=False, validate_proximity=False, coords_are_cartesian=False) structures.append(struct) timesteps.append(mdstep) return lmpdump(structures, timesteps)
def test_element(self): symbols = list() for i in range(1, 102): el = Element.from_Z(i) self.assertGreater(el.atomic_mass, 0, "Atomic mass cannot be negative!") self.assertNotIn(el.symbol, symbols, "Duplicate symbol for " + el.symbol) symbols.append('"' + el.symbol + '"') self.assertIsNotNone(el.group, "Group cannot be none for Z=" + str(i)) self.assertIsNotNone(el.row, "Row cannot be none for Z=" + str(i)) #Test all properties all_attr = ['Z', 'symbol', 'X', 'name', 'atomic_mass', 'atomic_radius', 'max_oxidation_state', 'min_oxidation_state', 'mendeleev_no', 'electrical_resistivity', 'velocity_of_sound', 'reflectivity', 'refractive_index', 'poissons_ratio', 'molar_volume' , 'electronic_structure', 'thermal_conductivity', 'boiling_point', 'melting_point', 'critical_temperature', 'superconduction_temperature', 'liquid_range', 'bulk_modulus', 'youngs_modulus', 'brinell_hardness', 'rigidity_modulus', 'mineral_hardness', 'vickers_hardness', 'density_of_solid', 'coefficient_of_linear_thermal_expansion'] for a in all_attr: self.assertIsNotNone(el, a)
def get_transformed_entries(entries, elements): comp_matrix = get_comp_matrix(entries, elements) newmat = [] energies = [] for i in xrange(len(elements)): col = comp_matrix[:, i] maxval = max(col) maxind = list(col).index(maxval) newmat.append(comp_matrix[maxind]) energies.append(entries[i].energy_per_atom) invm = np.linalg.inv(np.array(newmat).transpose()) newentries = [] for i in xrange(len(entries)): entry = entries[i] lincomp = np.dot(invm, comp_matrix[i]) lincomp = np.around(lincomp, 5) comp = Composition({Element.from_Z(j + 1):lincomp[j] for j in xrange(len(elements))}) scaled_energy = entry.energy_per_atom - sum(lincomp * energies) newentries.append(TransformedPDEntry(comp, scaled_energy, entry)) return newentries
def write_data_file(self, organism, job_dir_path, composition_space): """ Writes the file (called in.data) containing the structure that LAMMPS reads. Args: organism: the Organism whose structure to write job_dir_path: the path the job directory (as a string) where the file will be written composition_space: the CompositionSpace of the search """ # get xhi, yhi and zhi from the lattice vectors lattice_coords = organism.cell.lattice.matrix xhi = lattice_coords[0][0] yhi = lattice_coords[1][1] zhi = lattice_coords[2][2] box_size = [[0.0, xhi], [0.0, yhi], [0.0, zhi]] # get xy, xz and yz from the lattice vectors xy = lattice_coords[1][0] xz = lattice_coords[2][0] yz = lattice_coords[2][1] # get a list of the elements from the lammps input script to # preserve their order of appearance # TODO: not all formats give the element symbols at the end of the line # containing the pair_coeff keyword. Find a better way. num_elements = len(composition_space.get_all_elements()) if num_elements == 1: all_elements = composition_space.get_all_elements() else: with open(self.input_script, 'r') as f: lines = f.readlines() for line in lines: if 'pair_coeff' in line: element_symbols = line.split()[-1 * num_elements:] all_elements = [] for symbol in element_symbols: all_elements.append(Element(symbol)) # get the dictionary of atomic masses - set the atom types to the order # of their appearance in the lammps input script atomic_masses_dict = {} for i in range(len(all_elements)): atomic_masses_dict[all_elements[i].symbol] = [ i + 1, float(all_elements[i].atomic_mass) ] # get the atoms data atoms_data = LammpsData.get_atoms_data(organism.cell, atomic_masses_dict, set_charge=True) # make a LammpsData object lammps_data = LammpsData(box_size, atomic_masses_dict.values(), atoms_data) # write the data to a file # This method doesn't write the tilts, so we have to add those. lammps_data.write_file(job_dir_path + '/in.data') # read the in.data file as a list of strings with open(job_dir_path + '/in.data', 'r') as f: lines = f.readlines() # find the location to insert the tilts insertion_index = 0 for line in lines: if 'zhi' in line: insertion_index = lines.index(line) + 1 # build the string containing the tilts and add it tilts_string = str(xy) + ' ' + str(xz) + ' ' + str(yz) + ' xy xz yz\n' lines.insert(insertion_index, tilts_string) # overwrite the in.data file with the new contents, including the tilts with open(job_dir_path + '/in.data', 'w') as f: for line in lines: f.write('%s' % line)
def from_string(data, default_names=None): """ Reads a Poscar from a string. The code will try its best to determine the elements in the POSCAR in the following order: 1. If default_names are supplied and valid, it will use those. Usually, default names comes from an external source, such as a POTCAR in the same directory. 2. If there are no valid default names but the input file is Vasp5-like and contains element symbols in the 6th line, the code will use that. 3. Failing (2), the code will check if a symbol is provided at the end of each coordinate. If all else fails, the code will just assign the first n elements in increasing atomic number, where n is the number of species, to the Poscar. For example, H, He, Li, .... This will ensure at least a unique element is assigned to each site and any analysis that does not require specific elemental properties should work fine. Args: data: string containing Poscar data. default_names: default symbols for the POSCAR file, usually coming from a POTCAR in the same directory. Returns: Poscar object. """ chunks = re.split("^\s*$", data.strip(), flags=re.MULTILINE) #Parse positions lines = tuple(clean_lines(chunks[0].split("\n"), False)) comment = lines[0] scale = float(lines[1]) lattice = np.array([map(float, line.split()) for line in lines[2:5]]) if scale < 0: # In vasp, a negative scale factor is treated as a volume. We need # to translate this to a proper lattice vector scaling. vol = abs(det(lattice)) lattice *= (-scale / vol) ** (1 / 3) else: lattice *= scale vasp5_symbols = False try: natoms = map(int, lines[5].split()) ipos = 6 except ValueError: vasp5_symbols = True symbols = lines[5].split() natoms = map(int, lines[6].split()) atomic_symbols = list() for i in xrange(len(natoms)): atomic_symbols.extend([symbols[i]] * natoms[i]) ipos = 7 postype = lines[ipos].split()[0] sdynamics = False # Selective dynamics if postype[0] in "sS": sdynamics = True ipos += 1 postype = lines[ipos].split()[0] cart = postype[0] in "cCkK" nsites = sum(natoms) # If default_names is specified (usually coming from a POTCAR), use # them. This is in line with Vasp"s parsing order that the POTCAR # specified is the default used. if default_names: try: atomic_symbols = [] for i in xrange(len(natoms)): atomic_symbols.extend([default_names[i]] * natoms[i]) vasp5_symbols = True except IndexError: pass if not vasp5_symbols: ind = 3 if not sdynamics else 6 try: #check if names are appended at the end of the coordinates. atomic_symbols = [l.split()[ind] for l in lines[ipos + 1:ipos + 1 + nsites]] #Ensure symbols are valid elements if not all([Element.is_valid_symbol(sym) for sym in atomic_symbols]): raise ValueError("Non-valid symbols detected.") vasp5_symbols = True except (ValueError, IndexError): #Defaulting to false names. atomic_symbols = [] for i in xrange(len(natoms)): sym = Element.from_Z(i + 1).symbol atomic_symbols.extend([sym] * natoms[i]) warnings.warn("Elements in POSCAR cannot be determined. " "Defaulting to false names {}." .format(" ".join(atomic_symbols))) # read the atomic coordinates coords = [] selective_dynamics = list() if sdynamics else None for i in xrange(nsites): toks = lines[ipos + 1 + i].split() coords.append(map(float, toks[:3])) if sdynamics: selective_dynamics.append([tok.upper()[0] == "T" for tok in toks[3:6]]) struct = Structure(lattice, atomic_symbols, coords, False, False, cart) #parse velocities if any velocities = [] if len(chunks) > 1: for line in chunks[1].strip().split("\n"): velocities.append([float(tok) for tok in line.split()]) predictor_corrector = [] if len(chunks) > 2: lines = chunks[2].strip().split("\n") predictor_corrector.append([int(lines[0])]) for line in lines[1:]: predictor_corrector.append([float(tok) for tok in line.split()]) return Poscar(struct, comment, selective_dynamics, vasp5_symbols, velocities=velocities, predictor_corrector=predictor_corrector)
def get_excluded_list(): filename = "excluded_compounds.p" if os.path.exists(filename): with open(filename) as f: return pickle.load(f) from ga_optimization_ternary.fitness_evaluators import FitnessEvaluator, eval_fitness_simple all_AB = FitnessEvaluator(eval_fitness_simple, 10)._reverse_dict.keys() print 'generating exclusion ranks...' exclusions = [] for a in all_AB: for b in all_AB: for x in range(7): #nelectrons must be even ne_a = Element.from_Z(a).Z ne_b = Element.from_Z(b).Z ne_x = None if x == 0: ne_x = Element("O").Z * 3 elif x == 1: ne_x = Element("O").Z * 2 + Element("N").Z elif x == 2: ne_x = Element("O").Z + Element("N").Z * 2 elif x == 3: ne_x = Element("N").Z elif x == 4: ne_x = Element("O").Z * 2 + Element("F").Z elif x == 5: ne_x = Element("O").Z + Element("F").Z + Element("N").Z elif x == 6: ne_x = Element("O").Z * 2 + Element("S").Z #modify the score based on charge-balance even_found = False el_a = Element.from_Z(a) el_b = Element.from_Z(b) val_x = 0 if x == 0: val_x = -2 * 3 elif x == 1: val_x = (-2 * 2) + (-3 * 1) elif x == 2: val_x = (-2 * 1) + (-3 * 2) elif x == 3: val_x = -3 * 3 elif x == 4: val_x = (-2 * 2) + (-1 * 1) elif x == 5: val_x = (-2 * 1) + (-1 * 1) + (-3 * 1) elif x == 6: val_x = (-2 * 2) + (-2 * 1) for a_oxi in el_a.oxidation_states: for b_oxi in el_b.oxidation_states: if (ne_a + ne_b + ne_x) % 2 == 0 and (a_oxi + b_oxi + val_x) == 0: even_found = True if not even_found: exclusions.append((a, b, x)) with open(filename, "wb") as f: pickle.dump(exclusions, f) return exclusions
def test_get_data(self): props = { "energy", "energy_per_atom", "formation_energy_per_atom", "nsites", "unit_cell_formula", "pretty_formula", "is_hubbard", "elements", "nelements", "e_above_hull", "hubbards", "is_compatible", "task_ids", "density", "icsd_ids", "total_magnetization", } mpid = "mp-1143" vals = requests.get(f"http://www.materialsproject.org/materials/{mpid}/json/") expected_vals = vals.json() for prop in props: if prop not in [ "hubbards", "unit_cell_formula", "elements", "icsd_ids", "task_ids", ]: val = self.rester.get_data(mpid, prop=prop)[0][prop] if prop in ["energy", "energy_per_atom"]: prop = "final_" + prop self.assertAlmostEqual(expected_vals[prop], val, 2, "Failed with property %s" % prop) elif prop in ["elements", "icsd_ids", "task_ids"]: upstream_vals = set(self.rester.get_data(mpid, prop=prop)[0][prop]) self.assertLessEqual(set(expected_vals[prop]), upstream_vals) else: self.assertEqual( expected_vals[prop], self.rester.get_data(mpid, prop=prop)[0][prop], ) props = ["structure", "initial_structure", "final_structure", "entry"] for prop in props: obj = self.rester.get_data(mpid, prop=prop)[0][prop] if prop.endswith("structure"): self.assertIsInstance(obj, Structure) elif prop == "entry": obj = self.rester.get_data(mpid, prop=prop)[0][prop] self.assertIsInstance(obj, ComputedEntry) # Test chemsys search data = self.rester.get_data("Fe-Li-O", prop="unit_cell_formula") self.assertTrue(len(data) > 1) elements = {Element("Li"), Element("Fe"), Element("O")} for d in data: self.assertTrue(set(Composition(d["unit_cell_formula"]).elements).issubset(elements)) self.assertRaises(MPRestError, self.rester.get_data, "Fe2O3", "badmethod")
def test_ape(self): f = AtomicPackingEfficiency() ef = ElementFraction() ef.set_n_jobs(1) # Test the APE calculation routines self.assertAlmostEqual(1.11632, f.get_ideal_radius_ratio(15)) self.assertAlmostEqual(0.154701, f.get_ideal_radius_ratio(2)) self.assertAlmostEqual(1.65915, f.get_ideal_radius_ratio(27)) self.assertAlmostEqual(15, f.find_ideal_cluster_size(1.116)[0]) self.assertAlmostEqual(3, f.find_ideal_cluster_size(0.1)[0]) self.assertAlmostEqual(24, f.find_ideal_cluster_size(2)[0]) # Test the nearest neighbor lookup tool nn_lookup = f.create_cluster_lookup_tool( [Element('Cu'), Element('Zr')]) # Check that the table gets the correct structures stable_clusters = [ Composition('CuZr10'), Composition('Cu6Zr6'), Composition('Cu8Zr5'), Composition('Cu13Zr1'), Composition('Cu3Zr12'), Composition('Cu8Zr8'), Composition('Cu12Zr5'), Composition('Cu17Zr') ] ds, _ = nn_lookup.kneighbors(ef.featurize_many(stable_clusters), n_neighbors=1) self.assertArrayAlmostEqual([[0]] * 8, ds) self.assertEqual(8, nn_lookup._fit_X.shape[0]) # Swap the order of the clusters, make sure it gets the same list nn_lookup_swapped = f.create_cluster_lookup_tool( [Element('Zr'), Element('Cu')]) self.assertArrayAlmostEqual(sorted(nn_lookup._fit_X.tolist()), sorted(nn_lookup_swapped._fit_X.tolist())) # Make sure we had a cache hit self.assertEqual(1, f._create_cluster_lookup_tool.cache_info().misses) self.assertEqual(1, f._create_cluster_lookup_tool.cache_info().hits) # Change the tolerance, see if it changes the results properly f.threshold = 0.002 nn_lookup = f.create_cluster_lookup_tool( [Element('Cu'), Element('Zr')]) self.assertEqual(2, nn_lookup._fit_X.shape[0]) ds, _ = nn_lookup.kneighbors(ef.featurize_many( [Composition('CuZr10'), Composition('Cu3Zr12')]), n_neighbors=1) self.assertArrayAlmostEqual([[0]] * 2, ds) # Make sure we had a cache miss self.assertEqual(2, f._create_cluster_lookup_tool.cache_info().misses) self.assertEqual(1, f._create_cluster_lookup_tool.cache_info().hits) # Compute the distances from Cu50Zr50 mean_dists = f.compute_nearest_cluster_distance(Composition('CuZr')) self.assertArrayAlmostEqual([0.424264, 0.667602, 0.800561], mean_dists, decimal=6) # Compute the optimal APE for Cu50Zr50 self.assertArrayAlmostEqual([0.000233857, 0.003508794], f.compute_simultaneous_packing_efficiency( Composition('Cu50Zr50'))) # Test the dataframe calculator df = pd.DataFrame({'comp': [Composition('CuZr')]}) df = f.featurize_dataframe(df, 'comp') self.assertEqual(6, len(df.columns)) self.assertIn('dist from 5 clusters |APE| < 0.002', df.columns) self.assertAlmostEqual(0.003508794, df['mean abs simul. packing efficiency'][0]) # Make sure it works with composition that do not match any efficient clusters feat = f.compute_nearest_cluster_distance(Composition('Al')) self.assertArrayAlmostEqual([1] * 3, feat)
def from_entries(cls, entry1, entry2, working_ion_entry): """ Args: entry1: Entry corresponding to one of the entries in the voltage step. entry2: Entry corresponding to the other entry in the voltage step. working_ion_entry: A single ComputedEntry or PDEntry representing the element that carries charge across the battery, e.g. Li. """ # initialize some internal variables working_element = working_ion_entry.composition.elements[0] entry_charge = entry1 entry_discharge = entry2 if entry_charge.composition.get_atomic_fraction(working_element) > entry2.composition.get_atomic_fraction( working_element ): (entry_charge, entry_discharge) = (entry_discharge, entry_charge) comp_charge = entry_charge.composition comp_discharge = entry_discharge.composition ion_sym = working_element.symbol frame_charge_comp = Composition({el: comp_charge[el] for el in comp_charge if el.symbol != ion_sym}) frame_discharge_comp = Composition({el: comp_discharge[el] for el in comp_discharge if el.symbol != ion_sym}) # Data validation # check that the ion is just a single element if not working_ion_entry.composition.is_element: raise ValueError("VoltagePair: The working ion specified must be " "an element") # check that at least one of the entries contains the working element if ( not comp_charge.get_atomic_fraction(working_element) > 0 and not comp_discharge.get_atomic_fraction(working_element) > 0 ): raise ValueError("VoltagePair: The working ion must be present in " "one of the entries") # check that the entries do not contain the same amount of the workin # element if comp_charge.get_atomic_fraction(working_element) == comp_discharge.get_atomic_fraction(working_element): raise ValueError("VoltagePair: The working ion atomic percentage " "cannot be the same in both the entries") # check that the frameworks of the entries are equivalent if not frame_charge_comp.reduced_formula == frame_discharge_comp.reduced_formula: raise ValueError("VoltagePair: the specified entries must have the" " same compositional framework") # Initialize normalization factors, charged and discharged entries valence_list = Element(ion_sym).oxidation_states working_ion_valence = abs(max(valence_list)) ( framework, norm_charge, ) = frame_charge_comp.get_reduced_composition_and_factor() norm_discharge = frame_discharge_comp.get_reduced_composition_and_factor()[1] # Initialize normalized properties if hasattr(entry_charge, "structure"): _vol_charge = entry_charge.structure.volume / norm_charge else: _vol_charge = entry_charge.data.get("volume") if hasattr(entry_discharge, "structure"): _vol_discharge = entry_discharge.structure.volume / norm_discharge else: _vol_discharge = entry_discharge.data.get("volume") comp_charge = entry_charge.composition comp_discharge = entry_discharge.composition _mass_charge = comp_charge.weight / norm_charge _mass_discharge = comp_discharge.weight / norm_discharge _num_ions_transferred = (comp_discharge[working_element] / norm_discharge) - ( comp_charge[working_element] / norm_charge ) _voltage = ( ((entry_charge.energy / norm_charge) - (entry_discharge.energy / norm_discharge)) / _num_ions_transferred + working_ion_entry.energy_per_atom ) / working_ion_valence _mAh = _num_ions_transferred * Charge(1, "e").to("C") * Time(1, "s").to("h") * N_A * 1000 * working_ion_valence _frac_charge = comp_charge.get_atomic_fraction(working_element) _frac_discharge = comp_discharge.get_atomic_fraction(working_element) vpair = cls( voltage=_voltage, mAh=_mAh, mass_charge=_mass_charge, mass_discharge=_mass_discharge, vol_charge=_vol_charge, vol_discharge=_vol_discharge, frac_charge=_frac_charge, frac_discharge=_frac_discharge, working_ion_entry=working_ion_entry, entry_charge=entry_charge, entry_discharge=entry_discharge, _framework_formula=framework.reduced_formula, ) # Step 4: add (optional) hull and muO2 data vpair.decomp_e_charge = entry_charge.data.get("decomposition_energy", None) vpair.decomp_e_discharge = entry_discharge.data.get("decomposition_energy", None) vpair.muO2_charge = entry_charge.data.get("muO2", None) vpair.muO2_discharge = entry_discharge.data.get("muO2", None) return vpair
def test_get_defectsite_coordinated_elements(self): struct_el = self._mgo_uc.composition.elements for i in range(self._mgo_interstitial.defectsite_count()): for el in self._mgo_interstitial.get_coordinated_elements(i): self.assertTrue( Element(el) in struct_el, "Coordinated elements are wrong")
def __str__(self): out = [] site_descriptions = {} if self.pseudo is not None: site_descriptions = self.pseudo else: c = 1 for site in self.structure: name = None for k, v in site_descriptions.items(): if site.properties == v: name = k if name is None: name = site.specie.symbol + str(c) site_descriptions[name] = site.properties c += 1 def to_str(v): if isinstance(v, str): return "'%s'" % v if isinstance(v, float): return "%s" % str(v).replace("e", "d") if isinstance(v, bool): if v: return ".TRUE." return ".FALSE." return v for k1 in ["control", "system", "electrons", "ions", "cell"]: v1 = self.sections[k1] out.append("&%s" % k1.upper()) sub = [] for k2 in sorted(v1.keys()): if isinstance(v1[k2], list): n = 1 for l in v1[k2][: len(site_descriptions)]: sub.append(" %s(%d) = %s" % (k2, n, to_str(v1[k2][n - 1]))) n += 1 else: sub.append(" %s = %s" % (k2, to_str(v1[k2]))) if k1 == "system": if "ibrav" not in self.sections[k1]: sub.append(" ibrav = 0") if "nat" not in self.sections[k1]: sub.append(" nat = %d" % len(self.structure)) if "ntyp" not in self.sections[k1]: sub.append(" ntyp = %d" % len(site_descriptions)) sub.append("/") out.append(",\n".join(sub)) out.append("ATOMIC_SPECIES") for k, v in sorted(site_descriptions.items(), key=lambda i: i[0]): e = re.match(r"[A-Z][a-z]?", k).group(0) if self.pseudo is not None: p = v else: p = v["pseudo"] out.append(" %s %.4f %s" % (k, Element(e).atomic_mass, p)) out.append("ATOMIC_POSITIONS crystal") if self.pseudo is not None: for site in self.structure: out.append( " %s %.6f %.6f %.6f" % (site.specie.symbol, site.a, site.b, site.c) ) else: for site in self.structure: name = None for k, v in sorted(site_descriptions.items(), key=lambda i: i[0]): if v == site.properties: name = k out.append(" %s %.6f %.6f %.6f" % (name, site.a, site.b, site.c)) out.append("K_POINTS %s" % self.kpoints_mode) kpt_str = ["%s" % i for i in self.kpoints_grid] kpt_str.extend(["%s" % i for i in self.kpoints_shift]) out.append(" %s" % " ".join(kpt_str)) out.append("CELL_PARAMETERS angstrom") for vec in self.structure.lattice.matrix: out.append(" %f %f %f" % (vec[0], vec[1], vec[2])) return "\n".join(out)
def from_string(data): """ Reads the exciting input from a string """ root=ET.fromstring(data) speciesnode=root.find('structure').iter('species') elements = [] positions = [] vectors=[] lockxyz=[] # get title title_in=str(root.find('title').text) # Read elements and coordinates for nodes in speciesnode: symbol = nodes.get('speciesfile').split('.')[0] if len(symbol.split('_'))==2: symbol=symbol.split('_')[0] if Element.is_valid_symbol(symbol): # Try to recognize the element symbol element = symbol else: raise NLValueError("Unknown element!") natoms = nodes.getiterator('atom') for atom in natoms: x, y, z = atom.get('coord').split() positions.append([float(x), float(y), float(z)]) elements.append(element) # Obtain lockxyz for each atom if atom.get('lockxyz') is not None: lxy=[] for l in atom.get('lockxyz').split(): if l=='True' or l=='true': lxyz.append(True) else: lxyz.append(False) lockxyz.append(lxyz) else: lockxyz.append([False, False, False]) #check the atomic positions type if 'cartesian' in root.find('structure').attrib.keys(): if root.find('structure').attrib['cartesian']: cartesian=True for i in range(len(positions)): for j in range(3): positions[i][j]=positions[i][j]*ExcitingInput.bohr2ang print(positions) else: cartesian=False # get the scale attribute scale_in=root.find('structure').find('crystal').get('scale') if scale_in: scale=float(scale_in)*ExcitingInput.bohr2ang else: scale=ExcitingInput.bohr2ang # get the stretch attribute stretch_in=root.find('structure').find('crystal').get('stretch') if stretch_in: stretch=np.array([float(a) for a in stretch_in]) else: stretch=np.array([1.0,1.0,1.0]) # get basis vectors and scale them accordingly basisnode=root.find('structure').find('crystal').iter('basevect') for vect in basisnode: x, y, z=vect.text.split() vectors.append([float(x)*stretch[0]*scale, float(y)*stretch[1]*scale, float(z)*stretch[2]*scale]) # create lattice and structure object lattice_in=Lattice(vectors) structure_in=Structure(lattice_in,elements,positions,coords_are_cartesian=cartesian) return ExcitingInput(structure_in, title_in, lockxyz)
def test_init(self): d = {'Fe': 1, Element('Fe'): 1} self.assertRaises(ValueError, ChemicalPotential, d) for k in ChemicalPotential(Fe=1).keys(): self.assertIsInstance(k, Element)
def _get_oxid_state_guesses(self, all_oxi_states, max_sites, oxi_states_override, target_charge): """ Utility operation for guessing oxidation states. See `oxi_state_guesses` for full details. This operation does the calculation of the most likely oxidation states Args: oxi_states_override (dict): dict of str->list to override an element's common oxidation states, e.g. {"V": [2,3,4,5]} target_charge (int): the desired total charge on the structure. Default is 0 signifying charge balance. all_oxi_states (bool): if True, an element defaults to all oxidation states in pymatgen Element.icsd_oxidation_states. Otherwise, default is Element.common_oxidation_states. Note that the full oxidation state list is *very* inclusive and can produce nonsensical results. max_sites (int): if possible, will reduce Compositions to at most this many sites to speed up oxidation state guesses. If the composition cannot be reduced to this many sites a ValueError will be raised. Set to -1 to just reduce fully. If set to a number less than -1, the formula will be fully reduced but a ValueError will be thrown if the number of atoms in the reduced formula is greater than abs(max_sites). Returns: A list of dicts - each dict reports an element symbol and average oxidation state across all sites in that composition. If the composition is not charge balanced, an empty list is returned. A list of dicts - each dict maps the element symbol to a list of oxidation states for each site of that element. For example, Fe3O4 could return a list of [2,2,2,3,3,3] for the oxidation states of If the composition is """ comp = self.copy() # reduce Composition if necessary if max_sites and max_sites < 0: comp = self.reduced_composition if max_sites < -1 and comp.num_atoms > abs(max_sites): raise ValueError("Composition {} cannot accommodate max_sites " "setting!".format(comp)) elif max_sites and comp.num_atoms > max_sites: reduced_comp, reduced_factor = self. \ get_reduced_composition_and_factor() if reduced_factor > 1: reduced_comp *= max(1, int(max_sites / reduced_comp.num_atoms)) comp = reduced_comp # as close to max_sites as possible if comp.num_atoms > max_sites: raise ValueError("Composition {} cannot accommodate max_sites " "setting!".format(comp)) # Load prior probabilities of oxidation states, used to rank solutions if not Composition.oxi_prob: module_dir = os.path.join( os.path.dirname(os.path.abspath(__file__))) all_data = loadfn( os.path.join(module_dir, "..", "analysis", "icsd_bv.yaml")) Composition.oxi_prob = { Specie.from_string(sp): data for sp, data in all_data["occurrence"].items() } oxi_states_override = oxi_states_override or {} # assert: Composition only has integer amounts if not all(amt == int(amt) for amt in comp.values()): raise ValueError("Charge balance analysis requires integer " "values in Composition!") # for each element, determine all possible sum of oxidations # (taking into account nsites for that particular element) el_amt = comp.get_el_amt_dict() els = el_amt.keys() el_sums = [] # matrix: dim1= el_idx, dim2=possible sums el_sum_scores = defaultdict(set) # dict of el_idx, sum -> score el_best_oxid_combo = { } # dict of el_idx, sum -> oxid combo with best score for idx, el in enumerate(els): el_sum_scores[idx] = {} el_best_oxid_combo[idx] = {} el_sums.append([]) if oxi_states_override.get(el): oxids = oxi_states_override[el] elif all_oxi_states: oxids = Element(el).oxidation_states else: oxids = Element(el).icsd_oxidation_states or \ Element(el).oxidation_states # get all possible combinations of oxidation states # and sum each combination for oxid_combo in combinations_with_replacement( oxids, int(el_amt[el])): # List this sum as a possible option oxid_sum = sum(oxid_combo) if oxid_sum not in el_sums[idx]: el_sums[idx].append(oxid_sum) # Determine how probable is this combo? score = sum([ Composition.oxi_prob.get(Specie(el, o), 0) for o in oxid_combo ]) # If it is the most probable combo for a certain sum, # store the combination if oxid_sum not in el_sum_scores[ idx] or score > el_sum_scores[idx].get(oxid_sum, 0): el_sum_scores[idx][oxid_sum] = score el_best_oxid_combo[idx][oxid_sum] = oxid_combo # Determine which combination of oxidation states for each element # is the most probable all_sols = [] # will contain all solutions all_oxid_combo = [ ] # will contain the best combination of oxidation states for each site all_scores = [] # will contain a score for each solution for x in product(*el_sums): # each x is a trial of one possible oxidation sum for each element if sum(x) == target_charge: # charge balance condition el_sum_sol = dict(zip(els, x)) # element->oxid_sum # normalize oxid_sum by amount to get avg oxid state sol = {el: v / el_amt[el] for el, v in el_sum_sol.items()} all_sols.append( sol) # add the solution to the list of solutions # determine the score for this solution score = 0 for idx, v in enumerate(x): score += el_sum_scores[idx][v] all_scores.append(score) # collect the combination of oxidation states for each site all_oxid_combo.append( dict((e, el_best_oxid_combo[idx][v]) for idx, (e, v) in enumerate(zip(els, x)))) # sort the solutions by highest to lowest score if len(all_scores) > 0: all_sols, all_oxid_combo = zip( *[(y, x) for (z, y, x) in sorted(zip(all_scores, all_sols, all_oxid_combo), key=lambda pair: pair[0], reverse=True)]) return all_sols, all_oxid_combo
def predict_k_g_list(material_id_list, api_key=API_KEY, query_engine=None): """ Predict bulk (K) and shear (G) moduli for a list of materials. :param material_id_list: list of material-ID strings :param api_key: The API key used by pymatgen.matproj.rest.MPRester to connect to Materials Project :param query_engine: (Optional) QueryEngine object used to query materials instead of MPRester :return: (matid_list, predicted_k_list, predicted_g_list, caveats_list) Note that len(matid_list) may be less than len(material_id_list), if any requested material-IDs are not found. """ if len(material_id_list) == 0 or not isinstance(material_id_list, list): return (None, None, None, None ) # material_id_list not properly specified lvpa_list = [] cepa_list = [] rowH1A_list = [] rowHn3A_list = [] xH4A_list = [] xHn4A_list = [] matid_list = [] k_list = [] g_list = [] caveats_list = [] aiab_problem_list = [] # TODO: figure out if closing the query engine (using 'with' ctx mgr) is an issue # If it is a problem then try manually doing a session.close() for MPRester, but ignore for qe mpr = _get_mp_query(api_key, query_engine) for entry in mpr.query(criteria={"task_id": { "$in": material_id_list }}, properties=[ "material_id", "pretty_formula", "nsites", "volume", "energy_per_atom", "is_hubbard" ]): caveats_str = '' aiab_flag = False f_block_flag = False weight_list = [] energy_list = [] row_list = [] x_list = [] # Construct per-element lists for this material composition = Composition(str(entry["pretty_formula"])) for element_key, amount in composition.get_el_amt_dict().iteritems(): element = Element(element_key) weight_list.append(composition.get_atomic_fraction(element)) aiab_energy = get_element_aiab_energy( element_key) # aiab = atom-in-a-box if aiab_energy is None: aiab_flag = True break energy_list.append(aiab_energy) if element.block == 'f': f_block_flag = True row_list.append(element.row) x_list.append(element.X) # On error, add material to aiab_problem_list and continue with next material if aiab_flag: aiab_problem_list.append(str(entry["material_id"])) continue # Check caveats if bool(entry["is_hubbard"]): if len(caveats_str) > 0: caveats_str += " " caveats_str += CAVEAT_HUBBARD if f_block_flag: if len(caveats_str) > 0: caveats_str += " " caveats_str += CAVEAT_F_BLOCK # Calculate intermediate weighted averages (WA) for this material ewa = np.average(energy_list, weights=weight_list) # atom-in-a-box energy WA print str(entry["material_id"]) # Append descriptors for this material to descriptor lists lvpa_list.append( math.log10(float(entry["volume"]) / float(entry["nsites"]))) cepa_list.append(float(entry["energy_per_atom"]) - ewa) rowH1A_list.append(holder_mean(row_list, 1.0, weights=weight_list)) rowHn3A_list.append(holder_mean(row_list, -3.0, weights=weight_list)) xH4A_list.append(holder_mean(x_list, 4.0, weights=weight_list)) xHn4A_list.append(holder_mean(x_list, -4.0, weights=weight_list)) matid_list.append(str(entry["material_id"])) caveats_list.append(caveats_str) if isinstance(mpr, MPRester): mpr.session.close() # Check that at least one valid material was provided num_predictions = len(matid_list) if num_predictions > 0: # Construct descriptor arrays if (len(lvpa_list) != num_predictions or len(cepa_list) != num_predictions or len(rowH1A_list) != num_predictions or len(rowHn3A_list) != num_predictions or len(xH4A_list) != num_predictions or len(xHn4A_list) != num_predictions): return (None, None, None, None) k_descriptors = np.ascontiguousarray( [lvpa_list, rowH1A_list, cepa_list, xHn4A_list], dtype=float) g_descriptors = np.ascontiguousarray( [cepa_list, lvpa_list, rowHn3A_list, xH4A_list], dtype=float) # Allocate prediction arrays k_predictions = np.empty(num_predictions) g_predictions = np.empty(num_predictions) # Make predictions k_filename = os.path.join(os.path.dirname(__file__), DATAFILE_K) g_filename = os.path.join(os.path.dirname(__file__), DATAFILE_G) gbml.core.predict(k_filename, num_predictions, k_descriptors, k_predictions) gbml.core.predict(g_filename, num_predictions, g_descriptors, g_predictions) k_list = np.power(10.0, k_predictions).tolist() g_list = np.power(10.0, g_predictions).tolist() # Append aiab problem cases for entry in aiab_problem_list: matid_list.append(entry) k_list.append(None) g_list.append(None) caveats_list.append(CAVEAT_AIAB) if len(matid_list) == 0: return (None, None, None, None) else: return (matid_list, k_list, g_list, caveats_list)
def get_correction(self, entry) -> float: """ :param entry: A ComputedEntry/ComputedStructureEntry :return: Correction. """ comp = entry.composition if len(comp) == 1: # Skip element entry return 0 correction = 0 # Check for sulfide corrections if Element("S") in comp: sf_type = "sulfide" if entry.data.get("sulfide_type"): sf_type = entry.data["sulfide_type"] elif hasattr(entry, "structure"): sf_type = sulfide_type(entry.structure) if sf_type in self.sulfide_correction: correction += self.sulfide_correction[sf_type] * comp["S"] # Check for oxide, peroxide, superoxide, and ozonide corrections. if Element("O") in comp: if self.correct_peroxide: if entry.data.get("oxide_type"): if entry.data["oxide_type"] in self.oxide_correction: ox_corr = self.oxide_correction[ entry.data["oxide_type"]] correction += ox_corr * comp["O"] if entry.data["oxide_type"] == "hydroxide": ox_corr = self.oxide_correction["oxide"] correction += ox_corr * comp["O"] elif hasattr(entry, "structure"): ox_type, nbonds = oxide_type(entry.structure, 1.05, return_nbonds=True) if ox_type in self.oxide_correction: correction += self.oxide_correction[ox_type] * \ nbonds elif ox_type == "hydroxide": correction += self.oxide_correction["oxide"] * \ comp["O"] else: warnings.warn( "No structure or oxide_type parameter present. Note " "that peroxide/superoxide corrections are not as " "reliable and relies only on detection of special" "formulas, e.g., Li2O2.") rform = entry.composition.reduced_formula if rform in UCorrection.common_peroxides: correction += self.oxide_correction["peroxide"] * \ comp["O"] elif rform in UCorrection.common_superoxides: correction += self.oxide_correction["superoxide"] * \ comp["O"] elif rform in UCorrection.ozonides: correction += self.oxide_correction["ozonide"] * \ comp["O"] elif Element("O") in comp.elements and len(comp.elements) \ > 1: correction += self.oxide_correction['oxide'] * \ comp["O"] else: correction += self.oxide_correction['oxide'] * comp["O"] return correction
def test_get_data(self): props = [ "energy", "energy_per_atom", "formation_energy_per_atom", "nsites", "unit_cell_formula", "pretty_formula", "is_hubbard", "elements", "nelements", "e_above_hull", "hubbards", "is_compatible", "task_ids", "density", "icsd_ids", "total_magnetization" ] # unicode literals have been reintroduced in py>3.2 expected_vals = [ -191.33812137, -6.833504334642858, -2.551358929370749, 28, {k: v for k, v in { 'P': 4, 'Fe': 4, 'O': 16, 'Li': 4 }.items()}, "LiFePO4", True, ['Li', 'O', 'P', 'Fe'], 4, 0.0, { k: v for k, v in { 'Fe': 5.3, 'Li': 0.0, 'O': 0.0, 'P': 0.0 }.items() }, True, [ u'mp-601412', u'mp-19017', u'mp-796535', u'mp-797820', u'mp-540081', u'mp-797269' ], 3.4662026991351147, [ 159107, 154117, 160776, 99860, 181272, 166815, 260571, 92198, 165000, 155580, 38209, 161479, 153699, 260569, 260570, 200155, 260572, 181341, 181342, 72545, 56291, 97764, 162282, 155635 ], 16.0002716 ] for (i, prop) in enumerate(props): if prop not in [ 'hubbards', 'unit_cell_formula', 'elements', 'icsd_ids', 'task_ids' ]: val = self.rester.get_data("mp-19017", prop=prop)[0][prop] self.assertAlmostEqual(expected_vals[i], val) elif prop in ["elements", "icsd_ids", "task_ids"]: self.assertEqual( set(expected_vals[i]), set(self.rester.get_data("mp-19017", prop=prop)[0][prop])) else: self.assertEqual( expected_vals[i], self.rester.get_data("mp-19017", prop=prop)[0][prop]) props = ['structure', 'initial_structure', 'final_structure', 'entry'] for prop in props: obj = self.rester.get_data("mp-19017", prop=prop)[0][prop] if prop.endswith("structure"): self.assertIsInstance(obj, Structure) elif prop == "entry": obj = self.rester.get_data("mp-19017", prop=prop)[0][prop] self.assertIsInstance(obj, ComputedEntry) #Test chemsys search data = self.rester.get_data('Fe-Li-O', prop='unit_cell_formula') self.assertTrue(len(data) > 1) elements = {Element("Li"), Element("Fe"), Element("O")} for d in data: self.assertTrue( set(Composition( d['unit_cell_formula']).elements).issubset(elements)) self.assertRaises(MPRestError, self.rester.get_data, "Fe2O3", "badmethod")
def test_dict(self): fe = Element.Fe d = fe.as_dict() self.assertEqual(fe, Element.from_dict(d))
def test_get_critical_compositions(self): c1 = Composition("Fe2O3") c2 = Composition("Li3FeO4") c3 = Composition("Li2O") comps = self.pd.get_critical_compositions(c1, c2) expected = [ Composition("Fe2O3"), Composition("Li0.3243244Fe0.1621621O0.51351349") * 7.4, Composition("Li3FeO4"), ] for crit, exp in zip(comps, expected): self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5)) comps = self.pd.get_critical_compositions(c1, c3) expected = [ Composition("Fe2O3"), Composition("LiFeO2"), Composition("Li5FeO4") / 3, Composition("Li2O"), ] for crit, exp in zip(comps, expected): self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5)) # Don't fail silently if input compositions aren't in phase diagram # Can be very confusing if you're working with a GrandPotentialPD self.assertRaises( ValueError, self.pd.get_critical_compositions, Composition("Xe"), Composition("Mn"), ) # For the moment, should also fail even if compositions are in the gppd # because it isn't handled properly gppd = GrandPotentialPhaseDiagram(self.pd.all_entries, {"Xe": 1}, self.pd.elements + [Element("Xe")]) self.assertRaises( ValueError, gppd.get_critical_compositions, Composition("Fe2O3"), Composition("Li3FeO4Xe"), ) # check that the function still works though comps = gppd.get_critical_compositions(c1, c2) expected = [ Composition("Fe2O3"), Composition("Li0.3243244Fe0.1621621O0.51351349") * 7.4, Composition("Li3FeO4"), ] for crit, exp in zip(comps, expected): self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5)) # case where the endpoints are identical self.assertEqual(self.pd.get_critical_compositions(c1, c1 * 2), [c1, c1 * 2])
def setUp(self): comp = Composition("LiFeO2") self.entry = PDEntry(comp, 53) self.gpentry = GrandPotPDEntry(self.entry, {Element('O'): 1.5})
def setUp(self): self.entries = EntrySet.from_csv(str(module_dir / "pdentries_test.csv")) self.pd = GrandPotentialPhaseDiagram(self.entries, {Element("O"): -5}) self.pd6 = GrandPotentialPhaseDiagram(self.entries, {Element("O"): -6})
def amu_symbol(self): """Atomic mass units""" amu_list = self.reader.read_value("atomic_mass_units") atomic_numbers = self.reader.read_value("atomic_numbers") amu = {Element.from_Z(at).symbol: a for at, a in zip(atomic_numbers, amu_list)} return amu
def periodic_table_heatmap(elemental_data, cbar_label="", show_plot=False, cmap="YlOrRd", blank_color="grey", value_format=None, max_row=9): """ A static method that generates a heat map overlapped on a periodic table. Args: elemental_data (dict): A dictionary with the element as a key and a value assigned to it, e.g. surface energy and frequency, etc. Elements missing in the elemental_data will be grey by default in the final table elemental_data={"Fe": 4.2, "O": 5.0}. cbar_label (string): Label of the colorbar. Default is "". figure_name (string): Name of the plot (absolute path) being saved if not None. show_plot (bool): Whether to show the heatmap. Default is False. value_format (str): Formatting string to show values. If None, no value is shown. Example: "%.4f" shows float to four decimals. cmap (string): Color scheme of the heatmap. Default is 'coolwarm'. blank_color (string): Color assigned for the missing elements in elemental_data. Default is "grey". max_row (integer): Maximum number of rows of the periodic table to be shown. Default is 9, which means the periodic table heat map covers the first 9 rows of elements. """ # Convert elemental data in the form of numpy array for plotting. max_val = max(elemental_data.values()) min_val = min(elemental_data.values()) max_row = min(max_row, 9) if max_row <= 0: raise ValueError("The input argument 'max_row' must be positive!") value_table = np.empty((max_row, 18)) * np.nan blank_value = min_val - 0.01 for el in Element: if el.row > max_row: continue value = elemental_data.get(el.symbol, blank_value) value_table[el.row - 1, el.group - 1] = value # Initialize the plt object import matplotlib.pyplot as plt fig, ax = plt.subplots() plt.gcf().set_size_inches(12, 8) # We set nan type values to masked values (ie blank spaces) data_mask = np.ma.masked_invalid(value_table.tolist()) heatmap = ax.pcolor(data_mask, cmap=cmap, edgecolors='w', linewidths=1, vmin=min_val-0.001, vmax=max_val+0.001) cbar = fig.colorbar(heatmap) # Grey out missing elements in input data cbar.cmap.set_under(blank_color) cbar.set_label(cbar_label, rotation=270, labelpad=15) cbar.ax.tick_params(labelsize=14) # Refine and make the table look nice ax.axis('off') ax.invert_yaxis() # Label each block with corresponding element and value for i, row in enumerate(value_table): for j, el in enumerate(row): if not np.isnan(el): symbol = Element.from_row_and_group(i+1, j+1).symbol plt.text(j + 0.5, i + 0.25, symbol, horizontalalignment='center', verticalalignment='center', fontsize=14) if el != blank_value and value_format is not None: plt.text(j + 0.5, i + 0.5, value_format % el, horizontalalignment='center', verticalalignment='center', fontsize=10) plt.tight_layout() if show_plot: plt.show() return plt
def parse_oxide(self): """ Determines if an oxide is a peroxide/superoxide/ozonide/normal oxide. Returns: oxide_type (str): Type of oxide ozonide/peroxide/superoxide/hydroxide/None. nbonds (int): Number of peroxide/superoxide/hydroxide bonds in structure. """ structure = self.structure relative_cutoff = self.relative_cutoff o_sites_frac_coords = [] h_sites_frac_coords = [] lattice = structure.lattice if isinstance(structure.composition.elements[0], Element): comp = structure.composition elif isinstance(structure.composition.elements[0], Species): elmap = collections.defaultdict(float) for site in structure: for species, occu in site.species.items(): elmap[species.element] += occu comp = Composition(elmap) if Element("O") not in comp or comp.is_element: return "None", 0 for site in structure: syms = [sp.symbol for sp in site.species.keys()] if "O" in syms: o_sites_frac_coords.append(site.frac_coords) if "H" in syms: h_sites_frac_coords.append(site.frac_coords) if h_sites_frac_coords: dist_matrix = lattice.get_all_distances(o_sites_frac_coords, h_sites_frac_coords) if np.any(dist_matrix < relative_cutoff * 0.93): return ( "hydroxide", len(np.where(dist_matrix < relative_cutoff * 0.93)[0]) / 2.0, ) dist_matrix = lattice.get_all_distances(o_sites_frac_coords, o_sites_frac_coords) np.fill_diagonal(dist_matrix, 1000) is_superoxide = False is_peroxide = False is_ozonide = False if np.any(dist_matrix < relative_cutoff * 1.35): bond_atoms = np.where(dist_matrix < relative_cutoff * 1.35)[0] is_superoxide = True elif np.any(dist_matrix < relative_cutoff * 1.49): is_peroxide = True bond_atoms = np.where(dist_matrix < relative_cutoff * 1.49)[0] if is_superoxide: if len(bond_atoms) > len(set(bond_atoms)): is_superoxide = False is_ozonide = True try: nbonds = len(set(bond_atoms)) except UnboundLocalError: nbonds = 0.0 if is_ozonide: str_oxide = "ozonide" elif is_superoxide: str_oxide = "superoxide" elif is_peroxide: str_oxide = "peroxide" else: str_oxide = "oxide" if str_oxide == "oxide": nbonds = comp["O"] return str_oxide, nbonds
def test_print_periodic_table(self): Element.print_periodic_table()
def get_ranked_list_goldschmidt_halffill(): #TODO: get the oxidation state right!!! filename = "goldschmidt_rank_halffill.p" if os.path.exists(filename): with open(filename) as f: return pickle.load(f) from ga_optimization_ternary.fitness_evaluators import FitnessEvaluator, eval_fitness_simple all_AB = FitnessEvaluator(eval_fitness_simple, 10)._reverse_dict.keys() print 'generating goldschmidt ranks...' cand_score = {} # dictionary of cand_tuple:score. a high score is BAD for a in all_AB: for b in all_AB: for x in range(7): r_a = Element.from_Z(a).average_ionic_radius # TODO: get the correct oxidation state! r_b = Element.from_Z(b).average_ionic_radius r_x = None if x == 0: r_x = Element("O").ionic_radii[-2] elif x == 1: r_x = Element("O").ionic_radii[-2] * 2/3 + Element("N").ionic_radii[-3] * 1/3 elif x == 2: r_x = Element("O").ionic_radii[-2] * 1/3 + Element("N").ionic_radii[-3] * 2/3 elif x == 3: r_x = Element("N").ionic_radii[-3] elif x == 4: r_x = Element("O").ionic_radii[-2] * 2/3 + Element("F").ionic_radii[-1] * 1/3 elif x == 5: r_x = Element("O").ionic_radii[-2] * 1/3 + Element("F").ionic_radii[-1] * 1/3 + Element("N").ionic_radii[-3] * 1/3 elif x == 6: r_x = Element("O").ionic_radii[-2] * 2/3 + Element("S").ionic_radii[-2] * 1/3 goldschmidt = (r_a + r_x)/(math.sqrt(2) *(r_b+r_x)) score = abs(goldschmidt - 1) # a high score is bad, like golf #nelectrons must be even ne_a = Element.from_Z(a).Z ne_b = Element.from_Z(b).Z ne_x = None if x == 0: ne_x = Element("O").Z * 3 elif x == 1: ne_x = Element("O").Z * 2 + Element("N").Z elif x == 2: ne_x = Element("O").Z + Element("N").Z * 2 elif x == 3: ne_x = Element("N").Z elif x == 4: ne_x = Element("O").Z * 2 + Element("F").Z elif x == 5: ne_x = Element("O").Z + Element("F").Z + Element("N").Z elif x == 6: ne_x = Element("O").Z * 2 + Element("S").Z #modify the score based on charge-balance even_found = False el_a = Element.from_Z(a) el_b = Element.from_Z(b) val_x = 0 if x == 0: val_x = -2 * 3 elif x == 1: val_x = (-2 * 2) + (-3 * 1) elif x == 2: val_x = (-2 * 1) + (-3 * 2) elif x == 3: val_x = -3 * 3 elif x == 4: val_x = (-2 * 2) + (-1 * 1) elif x == 5: val_x = (-2 * 1) + (-1 * 1) + (-3 * 1) elif x == 6: val_x = (-2 * 2) + (-2 * 1) for a_oxi in el_a.oxidation_states: for b_oxi in el_b.oxidation_states: if (ne_a + ne_b + ne_x) % 2 == 0 and (a_oxi + b_oxi + val_x) == 0: even_found = True if not even_found: score = score + 100 cand_score[(a, b, x)] = score results = sorted(cand_score, key=cand_score.get) with open(filename, "wb") as f: pickle.dump(results, f) return results
def test_is(self): self.assertTrue(Element("Bi").is_post_transition_metal, True)
def get_relaxed_cell(self, atom_dump_path, data_in_path, element_symbols): """ Parses the relaxed cell from the dump.atom file. Returns the relaxed cell as a Cell object. Args: atom_dump_path: the path (as a string) to the dump.atom file in_data_path: the path (as a string) to the in.data file element_symbols: a tuple containing the set of chemical symbols of all the elements in the compositions space """ # read the dump.atom file as a list of strings with open(atom_dump_path, 'r') as atom_dump: lines = atom_dump.readlines() # get the lattice vectors a_data = lines[5].split() b_data = lines[6].split() c_data = lines[7].split() # parse the tilt factors xy = float(a_data[2]) xz = float(b_data[2]) yz = float(c_data[2]) # parse the bounds xlo_bound = float(a_data[0]) xhi_bound = float(a_data[1]) ylo_bound = float(b_data[0]) yhi_bound = float(b_data[1]) zlo_bound = float(c_data[0]) zhi_bound = float(c_data[1]) # compute xlo, xhi, ylo, yhi, zlo and zhi according to the conversion # given by LAMMPS # http://lammps.sandia.gov/doc/Section_howto.html#howto-12 xlo = xlo_bound - min([0.0, xy, xz, xy + xz]) xhi = xhi_bound - max([0.0, xy, xz, xy + xz]) ylo = ylo_bound - min(0.0, yz) yhi = yhi_bound - max([0.0, yz]) zlo = zlo_bound zhi = zhi_bound # construct a Lattice object from the lo's and hi's and tilts a = [xhi - xlo, 0.0, 0.0] b = [xy, yhi - ylo, 0.0] c = [xz, yz, zhi - zlo] relaxed_lattice = Lattice([a, b, c]) # get the number of atoms num_atoms = int(lines[3]) # get the atom types and their Cartesian coordinates types = [] relaxed_cart_coords = [] for i in range(num_atoms): atom_info = lines[9 + i].split() types.append(int(atom_info[1])) relaxed_cart_coords.append([ float(atom_info[2]) - xlo, float(atom_info[3]) - ylo, float(atom_info[4]) - zlo ]) # read the atom types and corresponding atomic masses from in.data with open(data_in_path, 'r') as data_in: lines = data_in.readlines() types_masses = {} for i in range(len(lines)): if 'Masses' in lines[i]: for j in range(len(element_symbols)): types_masses[int(lines[i + j + 2].split()[0])] = float( lines[i + j + 2].split()[1]) # map the atom types to chemical symbols types_symbols = {} for symbol in element_symbols: for atom_type in types_masses: # round the atomic masses to one decimal point for comparison if format(float(Element(symbol).atomic_mass), '.1f') == format(types_masses[atom_type], '.1f'): types_symbols[atom_type] = symbol # make a list of chemical symbols (one for each site) relaxed_symbols = [] for atom_type in types: relaxed_symbols.append(types_symbols[atom_type]) return Cell(relaxed_lattice, relaxed_symbols, relaxed_cart_coords, coords_are_cartesian=True)
def _parse(self, filename): start_patt = re.compile(" \(Enter \S+l101\.exe\)") route_patt = re.compile(" #[pPnNtT]*.*") charge_mul_patt = re.compile("Charge\s+=\s*([-\\d]+)\s+" "Multiplicity\s+=\s*(\d+)") num_basis_func_patt = re.compile("([0-9]+)\s+basis functions") pcm_patt = re.compile("Polarizable Continuum Model") stat_type_patt = re.compile("imaginary frequencies") scf_patt = re.compile("E\(.*\)\s*=\s*([-\.\d]+)\s+") mp2_patt = re.compile("EUMP2\s*=\s*(.*)") oniom_patt = re.compile("ONIOM:\s+extrapolated energy\s*=\s*(.*)") termination_patt = re.compile("(Normal|Error) termination of Gaussian") std_orientation_patt = re.compile("Standard orientation") end_patt = re.compile("--+") orbital_patt = re.compile("Alpha\s*\S+\s*eigenvalues --(.*)") thermo_patt = re.compile("(Zero-point|Thermal) correction(.*)=" "\s+([\d\.-]+)") self.properly_terminated = False self.is_pcm = False self.stationary_type = "Minimum" self.structures = [] self.corrections = {} self.energies = [] coord_txt = [] read_coord = 0 orbitals_txt = [] parse_stage = 0 num_basis_found = False terminated = False with zopen(filename, "r") as f: for line in f: if parse_stage == 0: if start_patt.search(line): parse_stage = 1 elif route_patt.search(line): self.route = {} for tok in line.split(): sub_tok = tok.strip().split("=") key = sub_tok[0].upper() self.route[key] = sub_tok[1].upper() if len(sub_tok) > 1 else "" m = re.match("(\w+)/([^/]+)", key) if m: self.functional = m.group(1) self.basis_set = m.group(2) elif parse_stage == 1: if charge_mul_patt.search(line): m = charge_mul_patt.search(line) self.charge = int(m.group(1)) self.spin_mult = int(m.group(2)) parse_stage = 2 elif parse_stage == 2: if self.is_pcm: self._check_pcm(line) if "FREQ" in self.route and thermo_patt.search(line): m = thermo_patt.search(line) if m.group(1) == "Zero-point": self.corrections["Zero-point"] = float(m.group(3)) else: key = m.group(2).strip(" to ") self.corrections[key] = float(m.group(3)) if read_coord: if not end_patt.search(line): coord_txt.append(line) else: read_coord = (read_coord + 1) % 4 if not read_coord: sp = [] coords = [] for l in coord_txt[2:]: toks = l.split() sp.append(Element.from_Z(int(toks[1]))) coords.append(map(float, toks[3:6])) self.structures.append(Molecule(sp, coords)) elif termination_patt.search(line): m = termination_patt.search(line) if m.group(1) == "Normal": self.properly_terminated = True terminated = True elif (not num_basis_found) and num_basis_func_patt.search(line): m = num_basis_func_patt.search(line) self.num_basis_func = int(m.group(1)) num_basis_found = True elif (not self.is_pcm) and pcm_patt.search(line): self.is_pcm = True self.pcm = {} elif "FREQ" in self.route and "OPT" in self.route and stat_type_patt.search(line): self.stationary_type = "Saddle" elif mp2_patt.search(line): m = mp2_patt.search(line) self.energies.append(float(m.group(1).replace("D", "E"))) elif oniom_patt.search(line): m = oniom_patt.matcher(line) self.energies.append(float(m.group(1))) elif scf_patt.search(line): m = scf_patt.search(line) self.energies.append(float(m.group(1))) elif std_orientation_patt.search(line): coord_txt = [] read_coord = 1 elif orbital_patt.search(line): orbitals_txt.append(line) if not terminated: raise IOError("Bad Gaussian output file.")
def test_attributes(self): is_true = { ("Xe", "Kr"): "is_noble_gas", ("Fe", "Ni"): "is_transition_metal", ("Li", "Cs"): "is_alkali", ("Ca", "Mg"): "is_alkaline", ("F", "Br", "I"): "is_halogen", ("La",): "is_lanthanoid", ("U", "Pu"): "is_actinoid", ("Si", "Ge"): "is_metalloid", ("O", "Te"): "is_chalcogen", } for k, v in is_true.items(): for sym in k: self.assertTrue(getattr(Element(sym), v), sym + " is false") keys = [ "mendeleev_no", "atomic_mass", "electronic_structure", "atomic_radius", "min_oxidation_state", "max_oxidation_state", "electrical_resistivity", "velocity_of_sound", "reflectivity", "refractive_index", "poissons_ratio", "molar_volume", "thermal_conductivity", "melting_point", "boiling_point", "liquid_range", "critical_temperature", "superconduction_temperature", "bulk_modulus", "youngs_modulus", "brinell_hardness", "rigidity_modulus", "mineral_hardness", "vickers_hardness", "density_of_solid", "atomic_orbitals", "coefficient_of_linear_thermal_expansion", "oxidation_states", "common_oxidation_states", "average_ionic_radius", "average_cationic_radius", "average_anionic_radius", "ionic_radii", "long_name", "metallic_radius", "iupac_ordering", ] # Test all elements up to Uranium for i in range(1, 104): el = Element.from_Z(i) d = el.data for k in keys: k_str = k.capitalize().replace("_", " ") if k_str in d and (not str(d[k_str]).startswith("no data")): self.assertIsNotNone(getattr(el, k)) elif k == "long_name": self.assertEqual(getattr(el, "long_name"), d["Name"]) elif k == "iupac_ordering": self.assertTrue("IUPAC ordering" in d) self.assertIsNotNone(getattr(el, k)) el = Element.from_Z(i) if len(el.oxidation_states) > 0: self.assertEqual(max(el.oxidation_states), el.max_oxidation_state) self.assertEqual(min(el.oxidation_states), el.min_oxidation_state) if el.symbol not in ["He", "Ne", "Ar"]: self.assertTrue(el.X > 0, "No electroneg for %s" % el) self.assertRaises(ValueError, Element.from_Z, 1000)
def get_structure_type(structure, write_poscar_from_cluster=False): """ This is a topology-scaling algorithm used to describe the periodicity of bonded clusters in a bulk structure. Args: structure (structure): Pymatgen structure object to classify. write_poscar_from_cluster (bool): Set to True to write a POSCAR from the sites in the cluster. Returns: string. 'molecular' (0D), 'chain', 'layered', 'heterogeneous' (intercalated 3D), or 'conventional' (3D) """ # The conventional standard structure is much easier to work # with. structure = SpacegroupAnalyzer( structure).get_conventional_standard_structure() # Noble gases don't have well-defined bonding radii. if not len([ e for e in structure.composition if e.symbol in ['He', 'Ne', 'Ar', 'Kr', 'Xe'] ]) == 0: type = 'noble gas' else: if len(structure.sites) < 45: structure.make_supercell(2) # Create a dict of sites as keys and lists of their # bonded neighbors as values. sites = structure.sites bonds = {} for site in sites: bonds[site] = [] for i in range(len(sites)): site_1 = sites[i] for site_2 in sites[i + 1:]: if (site_1.distance(site_2) < float( Element(site_1.specie).atomic_radius + Element(site_2.specie).atomic_radius) * 1.1): bonds[site_1].append(site_2) bonds[site_2].append(site_1) # Assimilate all bonded atoms in a cluster; terminate # when it stops growing. cluster_terminated = False while not cluster_terminated: original_cluster_size = len(bonds[sites[0]]) for site in bonds[sites[0]]: bonds[sites[0]] += [ s for s in bonds[site] if s not in bonds[sites[0]] ] if len(bonds[sites[0]]) == original_cluster_size: cluster_terminated = True original_cluster = bonds[sites[0]] if len(bonds[sites[0]]) == 0: # i.e. the cluster is a single atom. type = 'molecular' elif len(bonds[sites[0]]) == len(sites): # i.e. all atoms are bonded. type = 'conventional' else: # If the cluster's composition is not equal to the # structure's overall composition, it is a heterogeneous # compound. cluster_composition_dict = {} for site in bonds[sites[0]]: if Element(site.specie) in cluster_composition_dict: cluster_composition_dict[Element(site.specie)] += 1 else: cluster_composition_dict[Element(site.specie)] = 1 uniform = True if len(cluster_composition_dict): cmp = Composition.from_dict(cluster_composition_dict) if cmp.reduced_formula != structure.composition.reduced_formula: uniform = False if not uniform: type = 'heterogeneous' else: # Make a 2x2x2 supercell and recalculate the # cluster's new size. If the new cluster size is # the same as the old size, it is a non-periodic # molecule. If it is 2x as big, it's a 1D chain. # If it's 4x as big, it is a layered material. old_cluster_size = len(bonds[sites[0]]) structure.make_supercell(2) sites = structure.sites bonds = {} for site in sites: bonds[site] = [] for i in range(len(sites)): site_1 = sites[i] for site_2 in sites[i + 1:]: if (site_1.distance(site_2) < float( Element(site_1.specie).atomic_radius + Element(site_2.specie).atomic_radius) * 1.1): bonds[site_1].append(site_2) bonds[site_2].append(site_1) cluster_terminated = False while not cluster_terminated: original_cluster_size = len(bonds[sites[0]]) for site in bonds[sites[0]]: bonds[sites[0]] += [ s for s in bonds[site] if s not in bonds[sites[0]] ] if len(bonds[sites[0]]) == original_cluster_size: cluster_terminated = True if len(bonds[sites[0]]) != 4 * old_cluster_size: type = 'molecular' else: type = 'layered' if write_poscar_from_cluster: Structure.from_sites(original_cluster).to('POSCAR', 'POSCAR') return type
def eval_vasp_xml(file="vasprun.xml", recip=False, norm_fermi=True, print_out=False): dft = pymatgen.io.vasp.outputs.Vasprun(file, parse_projected_eigen=False) orbital_energy = pd.read_csv("element_orbital_energy.csv").set_index("element") lattice = jnp.asarray(dft.get_trajectory().as_dict()['lattice']).squeeze() lattice_normed = lattice / jnp.linalg.norm(lattice, axis=1, keepdims=True) lattice_recip = jnp.asarray(Lattice(lattice).reciprocal_lattice.matrix) # wrong! positions_base = dft.get_trajectory().as_dict()['base_positions'] positions = jnp.dot(positions_base, lattice) k_points = jnp.asarray(dft.actual_kpoints) weights = jnp.asarray(dft.actual_kpoints_weights) # how to use ? species_dict = {} species_arr = np.asarray(dft.atomic_symbols) count = 0 print(species_arr) for key in dict.fromkeys(set(dft.atomic_symbols), {}): species_dict["species_" + Element(key).long_name] = {"symbol": key, "number": count, "Es": orbital_energy.loc["C", "E_s"], "Ep": orbital_energy.loc["C", "E_p"], "Ed": orbital_energy.loc["C", "E_d"], } species_arr[species_arr == key] = count # cycles through elements but returns correct one anyway count += 1 species_arr = jnp.asarray(species_arr.astype(int)) for key in dft.eigenvalues.keys(): key_last = key true_inp = np.zeros( (dft.eigenvalues[key_last][:, :, 0].shape[0], dft.eigenvalues[key_last][:, :, 0].shape[1], len(dft.eigenvalues.keys()))) count = 0 if len(dft.eigenvalues.keys()) != 1: print("only one spin direction supported but", len(dft.eigenvalues.keys()), "where given") for key in dft.eigenvalues.keys(): # OrderedDictionary might be nice true_inp[:, :, count] = dft.eigenvalues[key][:, :, 0] # what is [:, :, 0] ??????????????????? occupied = np.max(jnp.nonzero(dft.eigenvalues[key][:, :, 1])[1]) + 1 fermi = find_fermi(true_inp, occupied) count += 1 if norm_fermi: true_inp -= fermi print("E fermi calculated normed", find_fermi(true_inp, occupied, plot=False)) if print_out: print("Lattice", type(lattice), lattice.shape, "\n", lattice) print("Lattice Normed", type(lattice_normed), lattice_normed.shape, lattice_normed) print("Lattice recip", type(lattice_recip), lattice_recip.shape, "\n", lattice_recip) print("Positions", type(positions_base), positions_base.shape, positions_base) print("Positions dot", type(positions), positions.shape, "\n", positions) print("kpts", k_points.shape, k_points) print("weights", weights.shape, weights) print("True shape", true_inp.shape, true_inp) print("species", species_arr.shape, species_arr, "\n", species_dict) # print("true", dft.eigenvalues[:].shape, "\n", dft.eigenvalues[dft.eigenvalues.keys()[0]][0, :, 0], "\n", # dft.eigenvalues[dft.eigenvalues.keys()[0]][0, :, 1]) print("E fermi vasp", dft.efermi) print("Highest occupied", occupied) print("E fermi calculated", fermi) if recip: return k_points, weights, lattice_recip, positions, species_arr, species_dict, true_inp, occupied else: return k_points, weights, lattice, positions, species_arr, species_dict, true_inp, occupied
def test_is_metal(self): for metal in ["Fe", "Eu", "Li", "Ca", "In"]: self.assertTrue(Element(metal).is_metal) for non_metal in ["Ge", "Si", "O", "He"]: self.assertFalse(Element(non_metal).is_metal)
def validate(self, value): if not all(Element.is_valid_symbol(k) for k in value.keys()): self.error('Keys should be element symbols') if not all(isinstance(v, (float, int)) for v in value.values()): self.error('Values should be numbers') super(DictField, self).validate(value)
def test_getmu_range_stability_phase(self): results = self.pd.get_chempot_range_stability_phase( Composition("LiFeO2"), Element("O")) self.assertAlmostEqual(results[Element("O")][1], -4.4501812249999997) self.assertAlmostEqual(results[Element("Fe")][0], -6.5961470999999996) self.assertAlmostEqual(results[Element("Li")][0], -3.6250022625000007)
def write_xdatcar(self, filepath="XDATCAR", groupby_type=True, overwrite=False, to_unit_cell=False): """ Write Xdatcar file with unit cell and atomic positions to file ``filepath``. Args: filepath: Xdatcar filename. If None, a temporary file is created. groupby_type: If True, atoms are grouped by type. Note that this option may change the order of the atoms. This option is needed because there are post-processing tools (e.g. ovito) that do not work as expected if the atoms in the structure are not grouped by type. overwrite: raise RuntimeError, if False and filepath exists. to_unit_cell (bool): Whether to translate sites into the unit cell. Return: path to Xdatcar file. """ if filepath is not None and os.path.exists(filepath) and not overwrite: raise RuntimeError("Cannot overwrite pre-existing file `%s`" % filepath) if filepath is None: import tempfile fd, filepath = tempfile.mkstemp(text=True, suffix="_XDATCAR") # int typat[natom], double znucl[npsp] # NB: typat is double in the HIST.nc file typat = self.reader.read_value("typat").astype(int) znucl = self.reader.read_value("znucl") ntypat = self.reader.read_dimvalue("ntypat") num_pseudos = self.reader.read_dimvalue("npsp") if num_pseudos != ntypat: raise NotImplementedError("Alchemical mixing is not supported, num_pseudos != ntypat") #print("znucl:", znucl, "\ntypat:", typat) symb2pos = OrderedDict() symbols_atom = [] for iatom, itype in enumerate(typat): itype = itype - 1 symbol = Element.from_Z(int(znucl[itype])).symbol if symbol not in symb2pos: symb2pos[symbol] = [] symb2pos[symbol].append(iatom) symbols_atom.append(symbol) if not groupby_type: group_ids = np.arange(self.reader.natom) else: group_ids = [] for pos_list in symb2pos.values(): group_ids.extend(pos_list) group_ids = np.array(group_ids, dtype=np.int) comment = " %s\n" % self.initial_structure.formula with open(filepath, "wt") as fh: # comment line + scaling factor set to 1.0 fh.write(comment) fh.write("1.0\n") for vec in self.initial_structure.lattice.matrix: fh.write("%.12f %.12f %.12f\n" % (vec[0], vec[1], vec[2])) if not groupby_type: fh.write(" ".join(symbols_atom) + "\n") fh.write("1 " * len(symbols_atom) + "\n") else: fh.write(" ".join(symb2pos.keys()) + "\n") fh.write(" ".join(str(len(p)) for p in symb2pos.values()) + "\n") # Write atomic positions in reduced coordinates. xred_list = self.reader.read_value("xred") if to_unit_cell: xred_list = xred_list % 1 for step in range(self.num_steps): fh.write("Direct configuration= %d\n" % (step + 1)) frac_coords = xred_list[step, group_ids] for fs in frac_coords: fh.write("%.12f %.12f %.12f\n" % (fs[0], fs[1], fs[2])) return filepath
def is_element(self, element): try: Element(element) return True except: return False
def __init__(self, entry1, entry2, working_ion_entry): # initialize some internal variables working_element = working_ion_entry.composition.elements[0] entry_charge = entry1 entry_discharge = entry2 if entry_charge.composition.get_atomic_fraction(working_element) \ > entry2.composition.get_atomic_fraction(working_element): (entry_charge, entry_discharge) = (entry_discharge, entry_charge) comp_charge = entry_charge.composition comp_discharge = entry_discharge.composition ion_sym = working_element.symbol frame_charge_comp = Composition({ el: comp_charge[el] for el in comp_charge if el.symbol != ion_sym }) frame_discharge_comp = Composition({ el: comp_discharge[el] for el in comp_discharge if el.symbol != ion_sym }) # Data validation # check that the ion is just a single element if not working_ion_entry.composition.is_element: raise ValueError("VoltagePair: The working ion specified must be " "an element") # check that at least one of the entries contains the working element if not comp_charge.get_atomic_fraction(working_element) > 0 and \ not comp_discharge.get_atomic_fraction(working_element) > 0: raise ValueError("VoltagePair: The working ion must be present in " "one of the entries") # check that the entries do not contain the same amount of the workin # element if comp_charge.get_atomic_fraction(working_element) == \ comp_discharge.get_atomic_fraction(working_element): raise ValueError("VoltagePair: The working ion atomic percentage " "cannot be the same in both the entries") # check that the frameworks of the entries are equivalent if not frame_charge_comp.reduced_formula == \ frame_discharge_comp.reduced_formula: raise ValueError("VoltagePair: the specified entries must have the" " same compositional framework") # Initialize normalization factors, charged and discharged entries valence_list = Element(ion_sym).oxidation_states working_ion_valence = abs(max(valence_list)) (self.framework, norm_charge) = frame_charge_comp.get_reduced_composition_and_factor() norm_discharge = \ frame_discharge_comp.get_reduced_composition_and_factor()[1] self._working_ion_entry = working_ion_entry # Initialize normalized properties self._vol_charge = entry_charge.structure.volume / norm_charge self._vol_discharge = entry_discharge.structure.volume / norm_discharge comp_charge = entry_charge.composition comp_discharge = entry_discharge.composition self._mass_charge = comp_charge.weight / norm_charge self._mass_discharge = comp_discharge.weight / norm_discharge self._num_ions_transferred = \ (comp_discharge[working_element] / norm_discharge) \ - (comp_charge[working_element] / norm_charge) self._voltage = \ (((entry_charge.energy / norm_charge) - (entry_discharge.energy / norm_discharge)) / \ self._num_ions_transferred + working_ion_entry.energy_per_atom) / working_ion_valence self._mAh = self._num_ions_transferred * Charge(1, "e").to("C") * \ Time(1, "s").to("h") * N_A * 1000 * working_ion_valence # Step 4: add (optional) hull and muO2 data self.decomp_e_charge = \ entry_charge.data.get("decomposition_energy", None) self.decomp_e_discharge = \ entry_discharge.data.get("decomposition_energy", None) self.muO2_charge = entry_charge.data.get("muO2", None) self.muO2_discharge = entry_discharge.data.get("muO2", None) self.entry_charge = entry_charge self.entry_discharge = entry_discharge self.normalization_charge = norm_charge self.normalization_discharge = norm_discharge self._frac_charge = comp_charge.get_atomic_fraction(working_element) self._frac_discharge = \ comp_discharge.get_atomic_fraction(working_element)
def periodic_table_heatmap( elemental_data, cbar_label="", cbar_label_size=14, show_plot=False, cmap="YlOrRd", cmap_range=None, blank_color="grey", value_format=None, max_row=9, ): """ A static method that generates a heat map overlayed on a periodic table. Args: elemental_data (dict): A dictionary with the element as a key and a value assigned to it, e.g. surface energy and frequency, etc. Elements missing in the elemental_data will be grey by default in the final table elemental_data={"Fe": 4.2, "O": 5.0}. cbar_label (string): Label of the colorbar. Default is "". cbar_label_size (float): Font size for the colorbar label. Default is 14. cmap_range (tuple): Minimum and maximum value of the colormap scale. If None, the colormap will autotmatically scale to the range of the data. show_plot (bool): Whether to show the heatmap. Default is False. value_format (str): Formatting string to show values. If None, no value is shown. Example: "%.4f" shows float to four decimals. cmap (string): Color scheme of the heatmap. Default is 'YlOrRd'. Refer to the matplotlib documentation for other options. blank_color (string): Color assigned for the missing elements in elemental_data. Default is "grey". max_row (integer): Maximum number of rows of the periodic table to be shown. Default is 9, which means the periodic table heat map covers the first 9 rows of elements. """ # Convert primitive_elemental data in the form of numpy array for plotting. if cmap_range is not None: max_val = cmap_range[1] min_val = cmap_range[0] else: max_val = max(elemental_data.values()) min_val = min(elemental_data.values()) max_row = min(max_row, 9) if max_row <= 0: raise ValueError("The input argument 'max_row' must be positive!") value_table = np.empty((max_row, 18)) * np.nan blank_value = min_val - 0.01 for el in Element: if el.row > max_row: continue value = elemental_data.get(el.symbol, blank_value) value_table[el.row - 1, el.group - 1] = value # Initialize the plt object import matplotlib.pyplot as plt fig, ax = plt.subplots() plt.gcf().set_size_inches(12, 8) # We set nan type values to masked values (ie blank spaces) data_mask = np.ma.masked_invalid(value_table.tolist()) heatmap = ax.pcolor( data_mask, cmap=cmap, edgecolors="w", linewidths=1, vmin=min_val - 0.001, vmax=max_val + 0.001, ) cbar = fig.colorbar(heatmap) # Grey out missing elements in input data cbar.cmap.set_under(blank_color) # Set the colorbar label and tick marks cbar.set_label(cbar_label, rotation=270, labelpad=25, size=cbar_label_size) cbar.ax.tick_params(labelsize=cbar_label_size) # Refine and make the table look nice ax.axis("off") ax.invert_yaxis() # Label each block with corresponding element and value for i, row in enumerate(value_table): for j, el in enumerate(row): if not np.isnan(el): symbol = Element.from_row_and_group(i + 1, j + 1).symbol plt.text( j + 0.5, i + 0.25, symbol, horizontalalignment="center", verticalalignment="center", fontsize=14, ) if el != blank_value and value_format is not None: plt.text( j + 0.5, i + 0.5, value_format % el, horizontalalignment="center", verticalalignment="center", fontsize=10, ) plt.tight_layout() if show_plot: plt.show() return plt
def test_dict(self): fe = Element("Fe") d = fe.as_dict() self.assertEqual(fe, Element.from_dict(d))
def van_arkel_triangle(list_of_materials, annotate=True): """ A static method that generates a binary van Arkel-Ketelaar triangle to quantify the ionic, metallic and covalent character of a compound by plotting the electronegativity difference (y) vs average (x). See: A.E. van Arkel, Molecules and Crystals in Inorganic Chemistry, Interscience, New York (1956) and J.A.A Ketelaar, Chemical Constitution (2nd edn.), An Introduction to the Theory of the Chemical Bond, Elsevier, New York (1958) Args: list_of_materials (list): A list of computed entries of binary materials or a list of lists containing two elements (str). annotate (bool): Whether or not to lable the points on the triangle with reduced formula (if list of entries) or pair of elements (if list of list of str). """ # F-Fr has the largest X difference. We set this # as our top corner of the triangle (most ionic) pt1 = np.array([(Element("F").X + Element("Fr").X) / 2, abs(Element("F").X - Element("Fr").X)]) # Cs-Fr has the lowest average X. We set this as our # bottom left corner of the triangle (most metallic) pt2 = np.array( [ (Element("Cs").X + Element("Fr").X) / 2, abs(Element("Cs").X - Element("Fr").X), ] ) # O-F has the highest average X. We set this as our # bottom right corner of the triangle (most covalent) pt3 = np.array([(Element("O").X + Element("F").X) / 2, abs(Element("O").X - Element("F").X)]) # get the parameters for the lines of the triangle d = np.array(pt1) - np.array(pt2) slope1 = d[1] / d[0] b1 = pt1[1] - slope1 * pt1[0] d = pt3 - pt1 slope2 = d[1] / d[0] b2 = pt3[1] - slope2 * pt3[0] # Initialize the plt object import matplotlib.pyplot as plt # set labels and appropriate limits for plot plt.xlim(pt2[0] - 0.45, -b2 / slope2 + 0.45) plt.ylim(-0.45, pt1[1] + 0.45) plt.annotate("Ionic", xy=[pt1[0] - 0.3, pt1[1] + 0.05], fontsize=20) plt.annotate("Covalent", xy=[-b2 / slope2 - 0.65, -0.4], fontsize=20) plt.annotate("Metallic", xy=[pt2[0] - 0.4, -0.4], fontsize=20) plt.xlabel(r"$\frac{\chi_{A}+\chi_{B}}{2}$", fontsize=25) plt.ylabel(r"$|\chi_{A}-\chi_{B}|$", fontsize=25) # Set the lines of the triangle chi_list = [el.X for el in Element] plt.plot( [min(chi_list), pt1[0]], [slope1 * min(chi_list) + b1, pt1[1]], "k-", linewidth=3, ) plt.plot([pt1[0], -b2 / slope2], [pt1[1], 0], "k-", linewidth=3) plt.plot([min(chi_list), -b2 / slope2], [0, 0], "k-", linewidth=3) plt.xticks(fontsize=15) plt.yticks(fontsize=15) # Shade with appropriate colors corresponding to ionic, metallci and covalent ax = plt.gca() # ionic filling ax.fill_between( [min(chi_list), pt1[0]], [slope1 * min(chi_list) + b1, pt1[1]], facecolor=[1, 1, 0], zorder=-5, edgecolor=[1, 1, 0], ) ax.fill_between( [pt1[0], -b2 / slope2], [pt1[1], slope2 * min(chi_list) - b1], facecolor=[1, 1, 0], zorder=-5, edgecolor=[1, 1, 0], ) # metal filling XPt = Element("Pt").X ax.fill_between( [min(chi_list), (XPt + min(chi_list)) / 2], [0, slope1 * (XPt + min(chi_list)) / 2 + b1], facecolor=[1, 0, 0], zorder=-3, alpha=0.8, ) ax.fill_between( [(XPt + min(chi_list)) / 2, XPt], [slope1 * ((XPt + min(chi_list)) / 2) + b1, 0], facecolor=[1, 0, 0], zorder=-3, alpha=0.8, ) # covalent filling ax.fill_between( [(XPt + min(chi_list)) / 2, ((XPt + min(chi_list)) / 2 + -b2 / slope2) / 2], [0, slope2 * (((XPt + min(chi_list)) / 2 + -b2 / slope2) / 2) + b2], facecolor=[0, 1, 0], zorder=-4, alpha=0.8, ) ax.fill_between( [((XPt + min(chi_list)) / 2 + -b2 / slope2) / 2, -b2 / slope2], [slope2 * (((XPt + min(chi_list)) / 2 + -b2 / slope2) / 2) + b2, 0], facecolor=[0, 1, 0], zorder=-4, alpha=0.8, ) # Label the triangle with datapoints for entry in list_of_materials: if type(entry).__name__ not in ["ComputedEntry", "ComputedStructureEntry"]: X_pair = [Element(el).X for el in entry] formatted_formula = "%s-%s" % tuple(entry) else: X_pair = [Element(el).X for el in entry.composition.as_dict().keys()] formatted_formula = format_formula(entry.composition.reduced_formula) plt.scatter(np.mean(X_pair), abs(X_pair[0] - X_pair[1]), c="b", s=100) if annotate: plt.annotate( formatted_formula, fontsize=15, xy=[np.mean(X_pair) + 0.005, abs(X_pair[0] - X_pair[1])], ) plt.tight_layout() return plt
def from_dict(cls, d): entries = [ComputedEntry.from_dict(dd) for dd in d["all_entries"]] elements = [Element.from_dict(dd) for dd in d["elements"]] return cls(entries, elements)
def from_string(data): """ Reads the exciting input from a string """ root = ET.fromstring(data) speciesnode = root.find("structure").iter("species") elements = [] positions = [] vectors = [] lockxyz = [] # get title title_in = str(root.find("title").text) # Read elements and coordinates for nodes in speciesnode: symbol = nodes.get("speciesfile").split(".")[0] if len(symbol.split("_")) == 2: symbol = symbol.split("_")[0] if Element.is_valid_symbol(symbol): # Try to recognize the element symbol element = symbol else: raise ValueError("Unknown element!") for atom in nodes.iter("atom"): x, y, z = atom.get("coord").split() positions.append([float(x), float(y), float(z)]) elements.append(element) # Obtain lockxyz for each atom if atom.get("lockxyz") is not None: lxyz = [] for l in atom.get("lockxyz").split(): if l in ("True", "true"): lxyz.append(True) else: lxyz.append(False) lockxyz.append(lxyz) else: lockxyz.append([False, False, False]) # check the atomic positions type if "cartesian" in root.find("structure").attrib.keys(): if root.find("structure").attrib["cartesian"]: cartesian = True for i, p in enumerate(positions): for j in range(3): p[j] = p[j] * ExcitingInput.bohr2ang print(positions) else: cartesian = False # get the scale attribute scale_in = root.find("structure").find("crystal").get("scale") if scale_in: scale = float(scale_in) * ExcitingInput.bohr2ang else: scale = ExcitingInput.bohr2ang # get the stretch attribute stretch_in = root.find("structure").find("crystal").get("stretch") if stretch_in: stretch = np.array([float(a) for a in stretch_in]) else: stretch = np.array([1.0, 1.0, 1.0]) # get basis vectors and scale them accordingly basisnode = root.find("structure").find("crystal").iter("basevect") for vect in basisnode: x, y, z = vect.text.split() vectors.append([ float(x) * stretch[0] * scale, float(y) * stretch[1] * scale, float(z) * stretch[2] * scale, ]) # create lattice and structure object lattice_in = Lattice(vectors) structure_in = Structure(lattice_in, elements, positions, coords_are_cartesian=cartesian) return ExcitingInput(structure_in, title_in, lockxyz)