Professional Documents
Culture Documents
"""
This module provides classes to run and analyze boltztrap on pymatgen band
structure objects. Boltztrap is a software interpolating band structures and
computing materials properties from this band structure using Boltzmann
semi-classical transport theory.
Boltztrap has been developped by Georg Madsen.
http://www.icams.de/content/departments/ams/madsen/boltztrap.html
You need the version 1.2.3
References are::
Madsen, G. K. H., and Singh, D. J. (2006).
BoltzTraP. A code for calculating band-structure dependent quantities.
Computer Physics Communications, 175, 67-71
"""
__author__ = "Geoffroy Hautier"
__copyright__ = "Copyright 2013, The Materials Project"
__version__ = "1.0"
__maintainer__ = "Geoffroy Hautier"
__email__ = "geoffroy@uclouvain.be"
__status__ = "Development"
__date__ = "August 23, 2013"
import os
import math
import numpy as np
import tempfile
from pymatgen.core.lattice import Lattice
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
from pymatgen.electronic_structure.dos import Dos, Spin, CompleteDos
from pymatgen.electronic_structure.core import Orbital
from pymatgen.electronic_structure.plotter import DosPlotter
from monty.os.path import which
from monty.dev import requires
from monty.json import jsanitize
from pymatgen.core.units import Energy, Length
from pymatgen.core.physical_constants import e, ELECTRON_MASS
import subprocess
try:
import matplotlib.pyplot as plt
except ImportError:
pass
class BoltztrapRunner():
"""
This class is used to run Boltztrap on a band structure object.
Args:
bs:
A band structure object
nelec:
the number of electrons
dos_type:
two options here for the band structure integration: "HISTO"
(histogram) or "TETRA" using the tetrahedon method. TETRA
gives typically better results especially for DOSes but takes
more time
energy_grid:
the energy steps used for the integration. in eV
lpfac:
the number of interpolation points in the real space. By
default 10 gives 10 time more points in the real space than
the number of kpoints given in reciprocal space
type:
type of boltztrap usage by default BOLTZ to compute transport co
efficients
but you can have also "FERMI" to compute fermi surface or more c
orrectly to
get certain bands interpolated
soc:
results from spin-orbit coupling (soc) computations give typical
ly non-polarized (no spin up or down)
results but 1 electron occupations. If the band structure comes
from a soc computation, you should set
soc to True (default False)
"""
@requires(which('x_trans'),
"BoltztrapRunner requires the executables 'x_trans' to be in "
"the path. Please download the Boltztrap at "
"http://www.icams.de/content/departments/ams/madsen/boltztrap"
".html and follow the instructions in the README to compile "
"Bolztrap accordingly. Then add x_trans to your path")
def __init__(self, bs, nelec, dos_type="HISTO", energy_grid=0.005,
lpfac=10, type="BOLTZ", band_nb=None, tauref=0, tauexp=0, tauen
=0, soc=False):
self.lpfac = lpfac
self._bs = bs
self._nelec = nelec
self.dos_type = dos_type
self.energy_grid = energy_grid
self.error = []
self.type = type
self.band_nb = band_nb
self.tauref=tauref
self.tauexp=tauexp
self.tauen=tauen
self.soc=soc
def _make_energy_file(self, file_name):
with open(file_name, 'w') as f:
f.write("test\n")
f.write(str(len(self._bs.kpoints))+"\n")
for i in range(len(self._bs.kpoints)):
tmp_eigs = []
for spin in self._bs._bands:
for j in range(int(math.floor(self._bs._nb_bands * 0.9))):
tmp_eigs.append(Energy(self._bs._bands[spin][j][i] self._bs.efermi, "eV").to("Ry"))
tmp_eigs.sort()
f.write("%12.8f %12.8f %12.8f %d\n"
% (self._bs.kpoints[i].frac_coords[0],
self._bs.kpoints[i].frac_coords[1],
self._bs.kpoints[i].frac_coords[2], len(tmp_eigs)))
for j in range(len(tmp_eigs)):
f.write("%18.8f\n" % float(tmp_eigs[j]))
def _make_struc_file(self, file_name):
sym = SpacegroupAnalyzer(self._bs._structure, symprec=0.01)
with open(file_name, 'w') as f:
f.write(self._bs._structure.composition.formula+" " +
str(sym.get_spacegroup_symbol())+"\n")
for i in range(3):
line = ''
for j in range(3):
line += "%12.5f" % (
Length(self._bs._structure.lattice.matrix[i][j],
"ang").to("bohr"))
f.write(line+'\n')
ops = sym.get_symmetry_dataset()['rotations']
f.write(str(len(ops))+"\n")
for c in ops:
f.write('\n'.join([' '.join([str(int(i)) for i in row])
for row in c]))
f.write('\n')
def _make_def_file(self, def_file_name):
with open(def_file_name,'w') as f:
so = ""
if self._bs.is_spin_polarized or self.soc:
so = "so"
f.write("5, 'boltztrap.intrans',
'old',
'formatted',0\n"+
"6,'boltztrap.outputtrans',
'unknown',
'formatted',0
\n"+
"20,'boltztrap.struct',
"10,'boltztrap.energy"+so+"',
'old',
'formatted',0\n"+
'old',
'formatted',
0\n"+
"48,'boltztrap.engre',
'unknown',
'unformatted',0
\n"+
"49,'boltztrap.transdos',
'unknown',
'formatted',0
\n"+
"50,'boltztrap.sigxx',
'unknown',
'formatted',0\n"
+
"51,'boltztrap.sigxxx',
'unknown',
'formatted',0\n
"+
"21,'boltztrap.trace',
'unknown',
'formatted',0
\n"+
"22,'boltztrap.condtens',
'unknown',
'formatted
"24,'boltztrap.halltens',
'unknown',
'formatted
',0\n"+
',0\n"+
"30,'boltztrap_BZ.cube',
'unknown',
'formatted'
"35,'boltztrap.banddat',
'unknown',
'formatted'
,0\n"+
,0\n"+
"36,'boltztrap_band.gpl',
'unknown',
'formatted
',0\n")
def _make_proj_files(self, file_name, def_file_name):
for o in Orbital.all_orbitals:
for site_nb in range(0, len(self._bs._structure.sites)):
if o in self._bs._projections[Spin.up][0][0]:
with open(file_name+"_"+str(site_nb)+"_"+str(o), 'w') as f:
f.write(self._bs._structure.composition.formula+"\n")
f.write(str(len(self._bs.kpoints))+"\n")
for i in range(len(self._bs.kpoints)):
tmp_proj = []
for spin in self._bs._bands:
for j in range(int(math.floor(self._bs._nb_bands
* 0.9))):
tmp_proj.append(self._bs._projections[spin][
j][i][o][site_nb])
#TODO deal with the sorting going on at the energy l
evel!!!
f.write("%12.8f %12.8f %12.8f %d\n"
% (self._bs.kpoints[i].frac_coords[0],
self._bs.kpoints[i].frac_coords[1],
self._bs.kpoints[i].frac_coords[2], len(t
mp_proj)))
for j in range(len(tmp_proj)):
f.write("%18.8f\n" % float(tmp_proj[j]))
with open(def_file_name,'w') as f:
so = ""
if self._bs.is_spin_polarized:
so = "so"
f.write("5, 'boltztrap.intrans',
'old',
'formatted',0\n"+
"6,'boltztrap.outputtrans',
'unknown',
'formatted',0
\n"+
"20,'boltztrap.struct',
"10,'boltztrap.energy"+so+"',
'old',
'formatted',0\n"+
'old',
'formatted',
0\n"+
"48,'boltztrap.engre',
'unknown',
'unformatted',0
\n"+
"49,'boltztrap.transdos',
'unknown',
'formatted',0
\n"+
"50,'boltztrap.sigxx',
'unknown',
'formatted',0\n"
+
"51,'boltztrap.sigxxx',
'unknown',
'formatted',0\n
"+
"21,'boltztrap.trace',
'unknown',
'formatted',0
\n"+
"22,'boltztrap.condtens',
'unknown',
'formatted
"24,'boltztrap.halltens',
'unknown',
'formatted
',0\n"+
',0\n"+
"30,'boltztrap_BZ.cube',
'unknown',
'formatted'
"35,'boltztrap.banddat',
'unknown',
'formatted'
,0\n"+
,0\n"+
"36,'boltztrap_band.gpl',
'unknown',
'formatted
',0\n")
i = 1000
for o in Orbital.all_orbitals:
for site_nb in range(0, len(self._bs._structure.sites)):
if o in self._bs._projections[Spin.up][0][0]:
f.write(str(i)+",\'"+file_name+"_"+str(site_nb)+"_"+str(
o)
+ "\' \'old\', \'formatted\',0\n")
i += 1
def _make_intrans_file(self, file_name,
doping=[1e15, 1e16, 1e17, 1e18, 1e19, 1e20], type="BO
LTZ", band_nb=None,
tauref=0, tauexp=0, tauen=0):
if type == "BOLTZ":
sorted(analyzer.mu_doping[doping][c], reverse=True) != a
nalyzer.mu_doping[doping][c]:
doping_ok = False
break
if doping == 'n' and sorted(analyzer.mu_doping[doping][c]) != an
alyzer.mu_doping[doping][c]:
doping_ok = False
break
if not doping_ok:
self.energy_grid /= 10
print("lowers energy grid to " + str(self.energy_grid))
if self.energy_grid < 0.00005:
raise BoltztrapError("energy grid lower than 0.00005 and still n
o good doping")
self._make_intrans_file(path_dir + "/" + dir_bz_name + ".intrans")
self.run(prev_sigma=None, path_dir=path_dir_orig)
analyzer = BoltztrapAnalyzer.from_files(path_dir)
#here, we test if a property (eff_mass tensor) converges
if convergence is False:
return path_dir
if prev_sigma is None or \
abs(sum(analyzer.get_eig_average_eff_mass_tensor()['n']) / 3
- prev_sigma)\
/ prev_sigma > 0.05:
if prev_sigma is not None:
print((abs(sum(analyzer.get_eig_average_eff_mass_tensor()['n'])
/ 3 - prev_sigma) / prev_sigma, \
self.lpfac, \
analyzer.get_average_eff_mass_tensor(300, 1e18)))
self.lpfac *= 2
if self.lpfac > 100:
raise BoltztrapError("lpfac higher than 100 and still not conver
ged")
self._make_intrans_file(path_dir + "/" + dir_bz_name + ".intrans")
self.run(
prev_sigma=sum(analyzer.get_eig_average_eff_mass_tensor()
['n']) / 3, path_dir=path_dir_orig)
return path_dir
class BoltztrapError(Exception):
"""
Exception class for boltztrap.
Raised when the boltztrap gives an error
"""
def __init__(self, msg):
self.msg = msg
def __str__(self):
return "BoltztrapError : " + self.msg
class BoltztrapAnalyzer():
"""
Class used to store all the data from a boltztrap run
"""
def __init__(self, gap, mu_steps, cond, seebeck, kappa, hall, doping,
mu_doping, seebeck_doping, cond_doping, kappa_doping,
return BoltztrapAnalyzer(
gap, mu_steps, cond, seebeck, kappa, hall, new_doping, mu_doping,
seebeck_doping, cond_doping, kappa_doping, hall_doping, dos, dos_par
tial, carrier_conc,
vol, warning)
def get_complete_dos(self, structure):
"""
Gives a CompleteDos object with the DOS from the interpolated projected
band structure
Args:
the structure (necessary to identify sites for projection)
Returns:
a CompleteDos object
"""
pdoss = {}
for s in self._dos_partial:
if structure.sites[int(s)] not in pdoss:
pdoss[structure.sites[int(s)]] = {}
for o in self._dos_partial[s]:
if Orbital.from_string(o) not in pdoss[structure.sites[int(s)]]:
pdoss[structure.sites[int(s)]][Orbital.from_string(o)] = {}
pdoss[structure.sites[int(s)]][Orbital.from_string(o)][Spin.up]
= self._dos_partial[s][o]
return CompleteDos(structure, total_dos=self.dos, pdoss=pdoss)
def get_mu_bounds(self, temp=300):
return min(self.mu_doping['p'][temp]), max(self.mu_doping['n'][temp])
def get_average_eff_mass_tensor(self, temperature=300, doping=1e18):
"""
Gives the average effective mass tensor at a given temperature and
doping level.
The average effective mass tensor is defined as the integrated
average of the second derivative
This effective mass tensor takes into account:
-non-parabolicity
-multiple extrema
-multiple bands
Args:
temperature: The temperature in K
doping: The doping in cm^-3
Returns:
a dictionary {'p':[[]],'n':[[]]}
The arrays are 3x3 and represent the effective mass tensor
The 'p' links to hole effective mass tensor and 'n' to electron
effective mass tensor.
"""
index = None
import math
results = {'p': [], 'n': []}
for t in ['n', 'p']:
for d in range(len(self.doping[t])):
if math.fabs(self.doping[t][d]-doping) < 0.001:
index = d
results[t] = np.linalg.inv(
self.cond_doping[t][temperature][index]) * doping \
* 10 ** 6 * e ** 2 / ELECTRON_MASS
return results
data_doping_full = []
data_doping_hall = []
with open(os.path.join(path_dir, "boltztrap.condtens"), 'r') as f:
data_full = []
for line in f:
if not line.startswith("#"):
t_steps.add(int(float(line.split()[1])))
m_steps.add(float(line.split()[0]))
data_full.append([float(c) for c in line.split()])
with open(os.path.join(path_dir, "boltztrap.halltens"), 'r') as f:
data_hall = []
for line in f:
if not line.startswith("#"):
data_hall.append([float(c) for c in line.split()])
data_dos = {'total': [], 'partial':{}}
with open(os.path.join(path_dir, "boltztrap.transdos"), 'r') as f:
count_series = 0
for line in f:
if not line.startswith(" #"):
data_dos['total'].append([Energy(float(line.split()[0]), "Ry
").to("eV"),
float(line.split()[1])])
else:
count_series += 1
if count_series>1:
break
#data_dos['total'].append([float(line.split()[0]),
#
float(line.split()[1])])
for file_name in os.listdir(path_dir):
if file_name.endswith("transdos") and file_name != 'boltztrap.transd
os':
tokens = file_name.split(".")[1].split("_")
with open(os.path.join(path_dir, file_name), 'r') as f:
for line in f:
if not line.startswith(" #"):
if tokens[1] not in data_dos['partial']:
data_dos['partial'][tokens[1]] = {}
if tokens[2] not in data_dos['partial'][tokens[1]]:
data_dos['partial'][tokens[1]][tokens[2]] = []
data_dos['partial'][tokens[1]][tokens[2]].append(flo
at(line.split()[1]))
with open(os.path.join(path_dir, "boltztrap.outputtrans"), 'r') as f:
warning = False
step = 0
for line in f:
if "WARNING" in line:
warning = True
if line.startswith("VBM"):
efermi = Energy(line.split()[1], "Ry").to("eV")
if step == 2:
l_tmp = line.split("-")[1:]
doping.extend([-float(d) for d in l_tmp])
step = 0
if step == 1:
res = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
for i in range(3):
for j in range(3):
res[i][j] = float(a[i][j])
return res
def _make_float_hall(a):
return [i for i in a[:27]]
return BoltztrapAnalyzer(
float(data['gap']), [float(d) for d in data['mu_steps']],
{int(d): [_make_float_array(v) for v in data['cond'][d]]
for d in data['cond']},
{int(d): [_make_float_array(v) for v in data['seebeck'][d]]
for d in data['seebeck']},
{int(d): [_make_float_array(v) for v in data['kappa'][d]]
for d in data['kappa']},
{int(d): [_make_float_hall(v) for v in data['hall'][d]]
for d in data['hall']},
{'p': [float(d) for d in data['doping']['p']],
'n': [float(d) for d in data['doping']['n']]},
{'p': {int(d): [float(v) for v in data['mu_doping']['p'][d]]
for d in data['mu_doping']['p']},
'n': {int(d): [float(v) for v in data['mu_doping']['n'][d]]
for d in data['mu_doping']['n']}},
{'p': {int(d): [_make_float_array(v)
for v in data['seebeck_doping']['p'][d]]
for d in data['seebeck_doping']['p']},
'n': {int(d): [_make_float_array(v)
for v in data['seebeck_doping']['n'][d]]
for d in data['seebeck_doping']['n']}},
{'p': {int(d): [_make_float_array(v)
for v in data['cond_doping']['p'][d]]
for d in data['cond_doping']['p']},
'n': {int(d): [_make_float_array(v)
for v in data['cond_doping']['n'][d]]
for d in data['cond_doping']['n']}},
{'p': {int(d): [_make_float_array(v)
for v in data['kappa_doping']['p'][d]]
for d in data['kappa_doping']['p']},
'n': {int(d): [_make_float_array(v)
for v in data['kappa_doping']['n'][d]]
for d in data['kappa_doping']['n']}},
{'p': {int(d): [_make_float_hall(v)
for v in data['hall_doping']['p'][d]]
for d in data['hall_doping']['p']},
'n': {int(d): [_make_float_hall(v)
for v in data['hall_doping']['n'][d]]
for d in data['hall_doping']['n']}},
Dos.from_dict(data['dos']), data['dos_partial'], data['carrier_conc'
],
data['vol'], str(data['warning']))
class BoltztrapPlotter():
"""
class containing methods to plot the data from Boltztrap.
Args:
bz: a BoltztrapAnalyzer object
"""
return plt
def plot_conductivity(self, temp=300, tau=None, xlim=None):
"""
Plot the conductivity in function of Fermi level. Semi-log plot
Args:
temp: the temperature
xlim: a list of min and max fermi energy by default (0, and band
gap)
tau: A relaxation time in s. By default none and the plot is by
units of relaxation time
Returns:
a matplotlib object
"""
if tau is None:
plt.semilogy(self._bz.mu_steps, [sorted(np.linalg.eigh(c)[0] * 0.01)
for c in self._bz.cond[temp]],
linewidth=3.0)
else:
plt.semilogy(self._bz.mu_steps, [sorted(np.linalg.eigh(c)[0] * 0.01
* tau)
for c in self._bz.cond[temp]],
linewidth=3.0)
self._plot_BG_limits()
self._plot_doping(temp)
plt.legend(['$\sigma_1$', '$\sigma_2$', '$\sigma_3$'])
if xlim is None:
plt.xlim(-0.5, self._bz.gap + 0.5)
else:
plt.xlim(xlim)
if tau is None:
plt.ylim([1e13, 1e20])
else:
plt.ylim([1e13*tau,1e20*tau])
if tau is None:
plt.ylabel("conductivity, $\sigma$/${\\tau}$ (1/($\Omega$ m s))",
fontsize=30.0)
else:
plt.ylabel("conductivity,\n $\sigma$ (1/($\Omega$ m))",
fontsize=30.0)
plt.xlabel("E-E$_f$ (eV)", fontsize=30.0)
plt.xticks(fontsize=25)
plt.yticks(fontsize=25)
return plt
def plot_dos(self, sigma=0.05):
"""
plot dos
Args:
sigma: a smearing
Returns:
a matplotlib object
"""
plotter = DosPlotter(sigma=sigma)
plotter.add_dos("t", self._bz.dos)
return plotter.get_plot()
def plot_carriers(self, temp=300):
"""
Plot the carrier concentration in function of Fermi level
Args:
temp: the temperature
Returns:
a matplotlib object
"""
plt.semilogy(self._bz.mu_steps,
abs(self._bz.carrier_conc[temp]/(self._bz.vol*1e-24)),
linewidth=3.0, color='r')
self._plot_BG_limits()
self._plot_doping(temp)
plt.xlim(-0.5, self._bz.gap+0.5)
plt.ylim(1e14,1e22)
plt.ylabel("carrier concentration (cm-3)", fontsize=30.0)
plt.xlabel("E-E$_f$ (eV)", fontsize=30)
plt.xticks(fontsize=25)
plt.yticks(fontsize=25)
return plt