Source code for twod_materials.stability.analysis

from __future__ import print_function, division, unicode_literals

import os

import operator

from pymatgen.phasediagram.analyzer import PDAnalyzer
from pymatgen.phasediagram.maker import PhaseDiagram
from pymatgen.core.structure import Structure
from pymatgen.io.vasp.outputs import Vasprun
from pymatgen.entries.computed_entries import ComputedEntry
from pymatgen.matproj.rest import MPRester

from monty.serialization import loadfn

from twod_materials.utils import is_converged

import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt

import twod_materials


PACKAGE_PATH = twod_materials.__file__.replace('__init__.pyc', '')
PACKAGE_PATH = PACKAGE_PATH.replace('__init__.py', '')
PACKAGE_PATH = '/'.join(PACKAGE_PATH.split('/')[:-2])

try:
    config_vars = loadfn(os.path.join(os.path.expanduser('~'), 'config.yaml'))
except:
    print('WARNING: No config.yaml file was found. please configure the '\
    'config.yaml and put it in your home directory.')
    # Still set them for testing purposes.
    config_vars = loadfn(os.path.join(PACKAGE_PATH, 'config.yaml'))
if 'MP_API' in os.environ:
    MPR = MPRester(os.environ['MP_API'])
else:
    MPR = MPRester(config_vars['mp_api'])


[docs]def get_competing_phases(): """ Collect the species to which the material might decompose to. Returns: A list of phases as tuples formatted as [(formula_1, Materials_Project_ID_1), (formula_2, Materials_Project_ID_2), ...] """ total_competing_phases = [] composition = Structure.from_file('POSCAR').composition try: energy = Vasprun('vasprun.xml').final_energy except: energy = 100 # The function can work without a vasprun.xml entries = MPR.get_entries_in_chemsys( [elt.symbol for elt in composition] ) my_entry = ComputedEntry(composition, energy) entries.append(my_entry) pda = PDAnalyzer(PhaseDiagram(entries)) decomp = pda.get_decomp_and_e_above_hull(my_entry, allow_negative=True) competing_phases = [ (entry.composition.reduced_formula, entry.entry_id) for entry in decomp[0] ] return competing_phases
[docs]def get_hull_distance(competing_phase_directory='../competing_phases'): """ Calculate the material's distance to the thermodynamic hull, based on species in the Materials Project database. Args: competing_phase_directory (str): absolute or relative path to the location where your competing phases have been relaxed. The default expectation is that they are stored in a directory named 'competing_phases' at the same level as your material's relaxation directory. Returns: float. distance (eV/atom) between the material and the hull. """ finished_competitors = {} original_directory = os.getcwd() # Determine which competing phases have been relaxed in the current # framework and store them in a dictionary ({formula: entry}). if os.path.isdir(competing_phase_directory): os.chdir(competing_phase_directory) for comp_dir in [ dir for dir in os.listdir(os.getcwd()) if os.path.isdir(dir) and is_converged(dir) ]: vasprun = Vasprun('{}/vasprun.xml'.format(comp_dir)) composition = vasprun.final_structure.composition energy = vasprun.final_energy finished_competitors[comp_dir] = ComputedEntry(composition, energy) os.chdir(original_directory) else: raise ValueError('Competing phase directory does not exist.') composition = Structure.from_file('POSCAR').composition try: energy = Vasprun('vasprun.xml').final_energy except: raise ValueError('This directory does not have a converged vasprun.xml') my_entry = ComputedEntry(composition, energy) # 2D material entries = MPR.get_entries_in_chemsys( [elt.symbol for elt in composition] ) # If the energies of competing phases have been calculated in # the current framework, put them in the phase diagram instead # of the MP energies. for i in range(len(entries)): formula = entries[i].composition.reduced_formula if formula in finished_competitors: entries[i] = finished_competitors[formula] else: entries[i] = ComputedEntry(entries[i].composition, 100) entries.append(my_entry) # 2D material pda = PDAnalyzer(PhaseDiagram(entries)) decomp = pda.get_decomp_and_e_above_hull(my_entry, allow_negative=True) return decomp[1]
[docs]def plot_hull_distances(hull_distances, fmt='pdf'): """ Create a bar graph of the formation energies of several 2D materials. Args: hull_distances (dict): follow the format: {reduced_formula: hull_distance (in eV/atom)} fmt (str): matplotlib format style. Check the matplotlib docs for options. """ hsize = 12 + (len(hull_distances) - 4) / 3 ax = plt.figure(figsize=(hsize, 10)).gca() ax.set_ylim(0, 700) ax.set_xlim(0, len(hull_distances)) x_ticklabels = [] i = 0 for compound in sorted( hull_distances.items(), key=operator.itemgetter(1)): proper_formula = '' for char in compound[0]: try: int(char) proper_formula += '_{}'.format(char) except ValueError: proper_formula += char x_ticklabels.append(r'$\mathrm{%s}$' % proper_formula) hull_distance = hull_distances[compound[0]] * 1000 # Good chance of stability if hull_distance < 100: color_code = 0.5 # Decent chance of stability elif hull_distance < 200: color_code = 0.71 # Poor chance of stability else: color_code = 0.92 ax.add_patch(plt.Rectangle((i + 0.1, 0), height=hull_distance, width=0.8, linewidth=0, facecolor=plt.cm.jet(color_code))) i += 1 ax.set_xticks([x + 0.5 for x in range(len(hull_distances))]) ax.set_xticklabels(x_ticklabels, family='serif', size=20, rotation=60) ax.set_yticklabels(ax.get_yticks(), family='serif', size=20) ax.set_ylabel(r'$\mathrm{E_F\/(meV/atom)}$', size=40) plt.savefig('stability_plot.{}'.format(fmt), transparent=True)