def test(self, structure):
failures = []
if self.is_valid:
if not structure.is_valid():
failures.append("IS_VALID=False")
if self.potcar_exists:
elements = structure.composition.elements
if set(elements).intersection(set(self.NO_POTCARS)):
failures.append("POTCAR_EXISTS=False")
if self.max_natoms:
if structure.num_sites > self.max_natoms:
failures.append("MAX_NATOMS=Exceeded")
if self.is_ordered:
if not structure.is_ordered:
failures.append("IS_ORDERED=False")
if self.not_in_MP:
mpr = MPRester(self.MAPI_KEY)
mpids = mpr.find_structure(structure)
if mpids:
if self.require_bandstructure:
for mpid in mpids:
try:
bs = mpr.get_bandstructure_by_material_id(mpid)
if bs:
failures.append("NOT_IN_MP=False ({})".format(mpid))
except:
pass
else:
failures.append("NOT_IN_MP=False ({})".format(mpids[0]))
return True if not failures else False
def __init__(self, api_key=None):
"""
Args:
api_key: (str) Your Materials Project API key, or None if you've
set up your pymatgen config.
"""
self.mprester = MPRester(api_key=api_key)
def get_materials_list():
"""Fetch data (from local cache if available)."""
try:
_log.info('Trying data cache for materials')
with open('materials_list.pickle') as f:
return pickle.load(f)
except IOError:
_log.info('Fetching remote data')
m = MPRester()
materials_list = m.query(
criteria={"elasticity": {"$exists": True}},
properties=['pretty_formula', 'reduced_cell_formula', 'task_id',
"elasticity.K_VRH", "elasticity.K_VRH", 'volume',
'density', 'formation_energy_per_atom', 'nsites'])
# Save for later
with open('materials_list.pickle', 'w') as f:
pickle.dump(materials_list, f)
_log.info('Data loaded')
return materials_list
def __init__(self, materials_write, mapi_key=None, update_all=False):
"""
Starting with an existing materials collection, adds stability information and The Materials Project ID.
Args:
materials_write: mongodb collection for materials (write access needed)
mapi_key: (str) Materials API key (if MAPI_KEY env. var. not set)
update_all: (bool) - if true, updates all docs. If false, only updates docs w/o a stability key
"""
self._materials = materials_write
self.mpr = MPRester(api_key=mapi_key)
self.update_all = update_all
class MaterialsEhullBuilder:
def __init__(self, materials_write, mapi_key=None, update_all=False):
"""
Starting with an existing materials collection, adds stability information and
The Materials Project ID.
Args:
materials_write: mongodb collection for materials (write access needed)
mapi_key: (str) Materials API key (if MAPI_KEY env. var. not set)
update_all: (bool) - if true, updates all docs. If false, only updates
docs w/o a stability key
"""
self._materials = materials_write
self.mpr = MPRester(api_key=mapi_key)
self.update_all = update_all
def run(self):
print("MaterialsEhullBuilder starting...")
self._build_indexes()
q = {"thermo.energy": {"$exists": True}}
if not self.update_all:
q["stability"] = {"$exists": False}
mats = [m for m in self._materials.find(q, {"calc_settings": 1, "structure": 1,
"thermo.energy": 1, "material_id": 1})]
pbar = tqdm(mats)
for m in pbar:
pbar.set_description("Processing materials_id: {}".format(m['material_id']))
try:
params = {}
for x in ["is_hubbard", "hubbards", "potcar_spec"]:
params[x] = m["calc_settings"][x]
structure = Structure.from_dict(m["structure"])
energy = m["thermo"]["energy"]
my_entry = ComputedEntry(structure.composition, energy, parameters=params)
self._materials.update_one({"material_id": m["material_id"]},
{"$set": {"stability": self.mpr.get_stability([my_entry])[0]}})
mpids = self.mpr.find_structure(structure)
self._materials.update_one({"material_id": m["material_id"]}, {"$set": {"mpids": mpids}})
except:
import traceback
print("<---")
print("There was an error processing material_id: {}".format(m))
traceback.print_exc()
print("--->")
print("MaterialsEhullBuilder finished processing.")
def reset(self):
self._materials.update_many({}, {"$unset": {"stability": 1}})
self._build_indexes()
def _build_indexes(self):
self._materials.create_index("stability.e_above_hull")
@staticmethod
def from_db_file(db_file, m="materials", **kwargs):
"""
Get a MaterialsEhullBuilder using only a db file
Args:
db_file: (str) path to db file
m: (str) name of "materials" collection
**kwargs: other parameters to feed into the builder, e.g. mapi_key
"""
db_write = get_database(db_file, admin=True)
return MaterialsEhullBuilder(db_write[m], **kwargs)
class MaterialsEhullBuilder(AbstractBuilder):
def __init__(self, materials_write, mapi_key=None, update_all=False):
"""
Starting with an existing materials collection, adds stability information and
The Materials Project ID.
Args:
materials_write: mongodb collection for materials (write access needed)
mapi_key: (str) Materials API key (if MAPI_KEY env. var. not set)
update_all: (bool) - if true, updates all docs. If false, only updates
docs w/o a stability key
"""
self._materials = materials_write
self.mpr = MPRester(api_key=mapi_key)
self.update_all = update_all
def run(self):
logger.info("MaterialsEhullBuilder starting...")
self._build_indexes()
q = {"thermo.energy": {"$exists": True}}
if not self.update_all:
q["stability"] = {"$exists": False}
mats = [m for m in self._materials.find(q, {"calc_settings": 1, "structure": 1,
"thermo.energy": 1, "material_id": 1})]
pbar = tqdm(mats)
for m in pbar:
pbar.set_description("Processing materials_id: {}".format(m['material_id']))
try:
params = {}
for x in ["is_hubbard", "hubbards", "potcar_spec"]:
params[x] = m["calc_settings"][x]
structure = Structure.from_dict(m["structure"])
energy = m["thermo"]["energy"]
my_entry = ComputedEntry(structure.composition, energy, parameters=params)
# TODO: @computron This only calculates Ehull with respect to Materials Project.
# It should also account for the current database's results. -computron
self._materials.update_one({"material_id": m["material_id"]},
{"$set": {"stability": self.mpr.get_stability([my_entry])[0]}})
# TODO: @computron: also add additional properties like inverse hull energy?
# TODO: @computron it's better to use PD tool or reaction energy calculator
# Otherwise the compatibility schemes might have issues...one strategy might be
# use MP only to retrieve entries but compute the PD locally -computron
for el, elx in my_entry.composition.items():
entries = self.mpr.get_entries(el.symbol, compatible_only=True)
min_e = min(entries, key=lambda x: x.energy_per_atom).energy_per_atom
energy -= elx * min_e
self._materials.update_one({"material_id": m["material_id"]},
{"$set": {"thermo.formation_energy_per_atom": energy / structure.num_sites}})
mpids = self.mpr.find_structure(structure)
self._materials.update_one({"material_id": m["material_id"]}, {"$set": {"mpids": mpids}})
except:
import traceback
logger.exception("<---")
logger.exception("There was an error processing material_id: {}".format(m))
logger.exception(traceback.format_exc())
logger.exception("--->")
logger.info("MaterialsEhullBuilder finished processing.")
def reset(self):
logger.info("Resetting MaterialsEhullBuilder")
self._materials.update_many({}, {"$unset": {"stability": 1}})
self._build_indexes()
logger.info("Finished resetting MaterialsEhullBuilder")
def _build_indexes(self):
self._materials.create_index("stability.e_above_hull")
@classmethod
def from_file(cls, db_file, m="materials", **kwargs):
"""
Get a MaterialsEhullBuilder using only a db file
Args:
db_file: (str) path to db file
m: (str) name of "materials" collection
**kwargs: other parameters to feed into the builder, e.g. mapi_key
"""
db_write = get_database(db_file, admin=True)
return cls(db_write[m], **kwargs)
class SurfaceEnergyAnalyzer(object):
"""
A class used for analyzing the surface energies of a material of a given
material_id. By default, this will use entries calculated from the
Materials Project to obtain chemical potential and bulk energy. As a
result, the difference in VASP parameters between the user's entry
(vasprun_dict) and the parameters used by Materials Project, may lead
to a rough estimate of the surface energy. For best results, it is
recommend that the user calculates all decomposition components first,
and insert the results into their own database as a pymatgen-db entry
and use those entries instead (custom_entries). In addition, this code
will only use one bulk entry to calculate surface energy. Ideally, to
get the most accurate surface energy, the user should compare their
slab energy to the energy of the oriented unit cell with both calculations
containing consistent k-points to avoid converegence problems as the
slab size is varied. See:
Sun, W.; Ceder, G. Efficient creation and convergence of surface slabs,
Surface Science, 2013, 617, 53–59, doi:10.1016/j.susc.2013.05.016.
and
Rogal, J., & Reuter, K. (2007). Ab Initio Atomistic Thermodynamics for
Surfaces : A Primer. Experiment, Modeling and Simulation of Gas-Surface
Interactions for Reactive Flows in Hypersonic Flights, 2–1 – 2–18.
.. attribute:: ref_element
All chemical potentials cna be written in terms of the range of chemical
potential of this element which will be used to calculate surface energy.
.. attribute:: mprester
Materials project rester for querying entries from the materials project.
Requires user MAPIKEY.
.. attribute:: ucell_entry
Materials Project entry of the material of the slab.
.. attribute:: x
Reduced amount composition of decomposed compound A in the bulk.
.. attribute:: y
Reduced amount composition of ref_element in the bulk.
.. attribute:: gbulk
Gibbs free energy of the bulk per formula unit
.. attribute:: chempot_range
List of the min and max chemical potential of ref_element.
.. attribute:: e_of_element
Energy per atom of ground state ref_element, eg. if ref_element=O,
than e_of_element=1/2*E_O2.
.. attribute:: vasprun_dict
Dictionary containing a list of Vaspruns for slab calculations as
items and the corresponding Miller index of the slab as the key
"""
def __init__(self, material_id, vasprun_dict, ref_element,
exclude_ids=[], custom_entries=[], mapi_key=None):
"""
Analyzes surface energies and Wulff shape of a particular
material using the chemical potential.
Args:
material_id (str): Materials Project material_id (a string,
e.g., mp-1234).
vasprun_dict (dict): Dictionary containing a list of Vaspruns
for slab calculations as items and the corresponding Miller
index of the slab as the key.
eg. vasprun_dict = {(1,1,1): [vasprun_111_1, vasprun_111_2,
vasprun_111_3], (1,1,0): [vasprun_111_1, vasprun_111_2], ...}
element: element to be considered as independent
variables. E.g., if you want to show the stability
ranges of all Li-Co-O phases wrt to uLi
exclude_ids (list of material_ids): List of material_ids
to exclude when obtaining the decomposition components
to calculate the chemical potential
custom_entries (list of pymatgen-db type entries): List of
user specified pymatgen-db type entries to use in finding
decomposition components for the chemical potential
mapi_key (str): Materials Project API key for accessing the
MP database via MPRester
"""
self.ref_element = ref_element
self.mprester = MPRester(mapi_key) if mapi_key else MPRester()
self.ucell_entry = \
self.mprester.get_entry_by_material_id(material_id,
inc_structure=True,
property_data=
["formation_energy_per_atom"])
ucell = self.ucell_entry.structure
#.........这里部分代码省略.........
def __init__(self, material_id, vasprun_dict, ref_element,
exclude_ids=[], custom_entries=[], mapi_key=None):
"""
Analyzes surface energies and Wulff shape of a particular
material using the chemical potential.
Args:
material_id (str): Materials Project material_id (a string,
e.g., mp-1234).
vasprun_dict (dict): Dictionary containing a list of Vaspruns
for slab calculations as items and the corresponding Miller
index of the slab as the key.
eg. vasprun_dict = {(1,1,1): [vasprun_111_1, vasprun_111_2,
vasprun_111_3], (1,1,0): [vasprun_111_1, vasprun_111_2], ...}
element: element to be considered as independent
variables. E.g., if you want to show the stability
ranges of all Li-Co-O phases wrt to uLi
exclude_ids (list of material_ids): List of material_ids
to exclude when obtaining the decomposition components
to calculate the chemical potential
custom_entries (list of pymatgen-db type entries): List of
user specified pymatgen-db type entries to use in finding
decomposition components for the chemical potential
mapi_key (str): Materials Project API key for accessing the
MP database via MPRester
"""
self.ref_element = ref_element
self.mprester = MPRester(mapi_key) if mapi_key else MPRester()
self.ucell_entry = \
self.mprester.get_entry_by_material_id(material_id,
inc_structure=True,
property_data=
["formation_energy_per_atom"])
ucell = self.ucell_entry.structure
# Get x and y, the number of species in a formula unit of the bulk
reduced_comp = ucell.composition.reduced_composition.as_dict()
if len(reduced_comp.keys()) == 1:
x = y = reduced_comp[ucell[0].species_string]
else:
for el in reduced_comp.keys():
if self.ref_element == el:
y = reduced_comp[el]
else:
x = reduced_comp[el]
# Calculate Gibbs free energy of the bulk per unit formula
gbulk = self.ucell_entry.energy /\
(len([site for site in ucell
if site.species_string == self.ref_element]) / y)
entries = [entry for entry in
self.mprester.get_entries_in_chemsys(list(reduced_comp.keys()),
property_data=["e_above_hull",
"material_id"])
if entry.data["e_above_hull"] == 0 and
entry.data["material_id"] not in exclude_ids] \
if not custom_entries else custom_entries
pd = PhaseDiagram(entries)
chempot_ranges = pd.get_chempot_range_map([Element(self.ref_element)])
# If no chemical potential is found, we return u=0, eg.
# for a elemental system, the relative u of Cu for Cu is 0
chempot_range = [chempot_ranges[entry] for entry in chempot_ranges.keys()
if entry.composition ==
self.ucell_entry.composition][0][0]._coords if \
chempot_ranges else [[0,0], [0,0]]
e_of_element = [entry.energy_per_atom for entry in
entries if str(entry.composition.reduced_composition)
== self.ref_element + "1"][0]
self.x = x
self.y = y
self.gbulk = gbulk
chempot_range = list(chempot_range)
self.chempot_range = sorted([chempot_range[0][0], chempot_range[1][0]])
self.e_of_element = e_of_element
self.vasprun_dict = vasprun_dict
class MPDataRetrieval(BaseDataRetrieval):
"""
Retrieves data from the Materials Project database.
If you use this data retrieval class, please additionally cite:
Ong, S.P., Cholia, S., Jain, A., Brafman, M., Gunter, D., Ceder, G.,
Persson, K.A., 2015. The Materials Application Programming Interface
(API): A simple, flexible and efficient API for materials data based on
REpresentational State Transfer (REST) principles. Computational
Materials Science 97, 209–215.
https://doi.org/10.1016/j.commatsci.2014.10.037
"""
def __init__(self, api_key=None):
"""
Args:
api_key: (str) Your Materials Project API key, or None if you've
set up your pymatgen config.
"""
self.mprester = MPRester(api_key=api_key)
def api_link(self):
return "https://materialsproject.org/wiki/index.php/The_Materials_API"
def get_dataframe(self, criteria, properties, index_mpid=True, **kwargs):
"""
Gets data from MP in a dataframe format. See api_link for more details.
Args:
criteria (dict): the same as in get_data
properties ([str]): the same properties supported as in get_data
plus: "structure", "initial_structure", "final_structure",
"bandstructure" (line mode), "bandstructure_uniform",
"phonon_bandstructure", "phonon_ddb", "phonon_bandstructure",
"phonon_dos". Note that for a long list of compounds, it may
take a long time to retrieve some of these objects.
index_mpid (bool): the same as in get_data
kwargs (dict): the same keyword arguments as in get_data
Returns (pandas.Dataframe):
"""
data = self.get_data(criteria=criteria, properties=properties,
index_mpid=index_mpid, **kwargs)
df = pd.DataFrame(data, columns=properties)
for prop in ["dos", "phonon_dos",
"phonon_bandstructure", "phonon_ddb"]:
if prop in properties:
df[prop] = self.try_get_prop_by_material_id(
prop=prop, material_id_list=df["material_id"].values)
if "bandstructure" in properties:
df["bandstructure"] = self.try_get_prop_by_material_id(
prop="bandstructure",
material_id_list=df["material_id"].values,
line_mode=True)
if "bandstructure_uniform" in properties:
df["bandstructure_uniform"] = self.try_get_prop_by_material_id(
prop="bandstructure",
material_id_list=df["material_id"].values,
line_mode=False)
if index_mpid:
df = df.set_index("material_id")
return df
def get_data(self, criteria, properties, mp_decode=False, index_mpid=True):
"""
Args:
criteria: (str/dict) see MPRester.query() for a description of this
parameter. String examples: "mp-1234", "Fe2O3", "Li-Fe-O',
"\\*2O3". Dict example: {"band_gap": {"$gt": 1}}
properties: (list) see MPRester.query() for a description of this
parameter. Example: ["formula", "formation_energy_per_atom"]
mp_decode: (bool) see MPRester.query() for a description of this
parameter. Whether to decode to a Pymatgen object where
possible.
index_mpid: (bool) Whether to set the materials_id as the dataframe
index.
Returns ([dict]):
a list of jsons that match the criteria and contain properties
"""
if index_mpid and "material_id" not in properties:
properties.append("material_id")
data = self.mprester.query(criteria, properties, mp_decode)
return data
def try_get_prop_by_material_id(self, prop, material_id_list, **kwargs):
"""
Call the relevant get_prop_by_material_id. "prop" is a property such
as bandstructure that is not readily available in supported properties
of the get_data function but via the get_bandstructure_by_material_id
method for example.
Args:
prop (str): the name of the property. Options are:
"bandstructure", "dos", "phonon_dos", "phonon_bandstructure",
#.........这里部分代码省略.........
def compute_environments(chemenv_configuration):
string_sources = {'cif': {'string': 'a Cif file', 'regexp': '.*\.cif$'},
'mp': {'string': 'the Materials Project database', 'regexp': 'mp-[0-9]+$'}}
questions = {'c': 'cif'}
if chemenv_configuration.has_materials_project_access:
questions['m'] = 'mp'
lgf = LocalGeometryFinder()
lgf.setup_parameters()
allcg = AllCoordinationGeometries()
strategy_class = strategies_class_lookup[chemenv_configuration.package_options['default_strategy']['strategy']]
#TODO: Add the possibility to change the parameters and save them in the chemenv_configuration
default_strategy = strategy_class()
default_strategy.setup_options(chemenv_configuration.package_options['default_strategy']['strategy_options'])
max_dist_factor = chemenv_configuration.package_options['default_max_distance_factor']
firsttime = True
while True:
if len(questions) > 1:
found = False
print('Enter the source from which the structure is coming or <q> to quit :')
for key_character, qq in questions.items():
print(' - <{}> for a structure from {}'.format(key_character, string_sources[qq]['string']))
test = input(' ... ')
if test == 'q':
break
if test not in list(questions.keys()):
for key_character, qq in questions.items():
if re.match(string_sources[qq]['regexp'], str(test)) is not None:
found = True
source_type = qq
if not found:
print('Wrong key, try again ...')
continue
else:
source_type = questions[test]
else:
found = False
source_type = list(questions.values())[0]
if found and len(questions) > 1:
input_source = test
if source_type == 'cif':
if not found:
input_source = input('Enter path to cif file : ')
cp = CifParser(input_source)
structure = cp.get_structures()[0]
elif source_type == 'mp':
if not found:
input_source = input('Enter materials project id (e.g. "mp-1902") : ')
a = MPRester(chemenv_configuration.materials_project_api_key)
structure = a.get_structure_by_material_id(input_source)
lgf.setup_structure(structure)
print('Computing environments for {} ... '.format(structure.composition.reduced_formula))
se = lgf.compute_structure_environments_detailed_voronoi(maximum_distance_factor=max_dist_factor)
print('Computing environments finished')
while True:
test = input('See list of environments determined for each (unequivalent) site ? '
'("y" or "n", "d" with details, "g" to see the grid) : ')
strategy = default_strategy
if test in ['y', 'd', 'g']:
strategy.set_structure_environments(se)
for eqslist in se.equivalent_sites:
site = eqslist[0]
isite = se.structure.index(site)
try:
if strategy.uniquely_determines_coordination_environments:
ces = strategy.get_site_coordination_environments(site)
else:
ces = strategy.get_site_coordination_environments_fractions(site)
except NeighborsNotComputedChemenvError:
continue
if ces is None:
continue
if len(ces) == 0:
continue
comp = site.species_and_occu
#ce = strategy.get_site_coordination_environment(site)
if strategy.uniquely_determines_coordination_environments:
ce = ces[0]
if ce is None:
continue
thecg = allcg.get_geometry_from_mp_symbol(ce[0])
mystring = 'Environment for site #{} {} ({}) : {} ({})\n'.format(str(isite),
comp.get_reduced_formula_and_factor()[0],
str(comp),
thecg.name,
ce[0])
else:
mystring = 'Environments for site #{} {} ({}) : \n'.format(str(isite),
comp.get_reduced_formula_and_factor()[0],
str(comp))
for ce in ces:
cg = allcg.get_geometry_from_mp_symbol(ce[0])
csm = ce[1]['other_symmetry_measures']['csm_wcs_ctwcc']
mystring += ' - {} ({}): {:.2f} % (csm : {:2f})\n'.format(cg.name, cg.mp_symbol,
100.0*ce[2],
csm)
if test in ['d', 'g'] and strategy.uniquely_determines_coordination_environments:
if thecg.mp_symbol != UNCLEAR_ENVIRONMENT_SYMBOL:
mystring += ' <Continuous symmetry measures> '
mingeoms = se.ce_list[isite][thecg.coordination_number][0].minimum_geometries()
for mingeom in mingeoms:
#.........这里部分代码省略.........
请发表评论