def solve_for_fermi_energy(self, temperature, chemical_potentials, bulk_dos): """ Solve for the Fermi energy self-consistently as a function of T Observations are Defect concentrations, electron and hole conc Args: temperature: Temperature to equilibrate fermi energies for chemical_potentials: dict of chemical potentials to use for calculation fermi level bulk_dos: bulk system dos (pymatgen Dos object) Returns: Fermi energy dictated by charge neutrality """ fdos = FermiDos(bulk_dos, bandgap=self.band_gap) _,fdos_vbm = fdos.get_cbm_vbm() def _get_total_q(ef): qd_tot = sum([ d['charge'] * d['conc'] for d in self.defect_concentrations( chemical_potentials=chemical_potentials, temperature=temperature, fermi_level=ef) ]) qd_tot += fdos.get_doping(fermi=ef + fdos_vbm, T=temperature) return qd_tot return bisect(_get_total_q, -1., self.band_gap + 1.)
def test_doping_fermi(self): T = 300 fermi0 = self.dos.efermi frange = [fermi0 - 0.5, fermi0, fermi0 + 2.0, fermi0 + 2.2] dopings = [self.dos.get_doping(fermi=f, T=T) for f in frange] ref_dopings = [3.48077e+21, 1.9235e+18, -2.6909e+16, -4.8723e+19] for i, c_ref in enumerate(ref_dopings): self.assertLessEqual(abs(dopings[i] / c_ref - 1.0), 0.01) calc_fermis = [self.dos.get_fermi(c=c, T=T) for c in ref_dopings] for j, f_ref in enumerate(frange): self.assertAlmostEqual(calc_fermis[j], f_ref, 4) sci_dos = FermiDos(self.dos, bandgap=3.0) for i, c_ref in enumerate(ref_dopings): if c_ref < 0: self.assertAlmostEqual(sci_dos.get_fermi(c_ref, T=T) - frange[i], 0.47, places=2) else: self.assertAlmostEqual(sci_dos.get_fermi(c_ref, T=T) - frange[i], -0.47, places=2)
def solve_for_non_equilibrium_fermi_energy(self, temperature, quench_temperature, chemical_potentials, bulk_dos): """ Solve for the Fermi energy after quenching in the defect concentrations at a higher temperature (the quench temperature), as outlined in P. Canepa et al (2017) Chemistry of Materials (doi: 10.1021/acs.chemmater.7b02909) Args: temperature: Temperature to equilibrate fermi energy at after quenching in defects quench_temperature: Temperature to equilibrate defect concentrations at (higher temperature) chemical_potentials: dict of chemical potentials to use for calculation fermi level bulk_dos: bulk system dos (pymatgen Dos object) Returns: Fermi energy dictated by charge neutrality with respect to frozen in defect concentrations """ high_temp_fermi_level = self.solve_for_fermi_energy(quench_temperature, chemical_potentials, bulk_dos) fixed_defect_charge = sum([ d['charge'] * d['conc'] for d in self.defect_concentrations( chemical_potentials=chemical_potentials, temperature=quench_temperature, fermi_level=high_temp_fermi_level) ]) fdos = FermiDos(bulk_dos, bandgap=self.band_gap) _, fdos_vbm = fdos.get_cbm_vbm() def _get_total_q(ef): qd_tot = fixed_defect_charge qd_tot += fdos.get_doping(fermi_level=ef + fdos_vbm, temperature=temperature) return qd_tot return bisect(_get_total_q, -1., self.band_gap + 1.)
def solve_for_fermi_energy(self, temperature, chemical_potentials, bulk_dos): """ Solve for the Fermi energy self-consistently as a function of T Observations are Defect concentrations, electron and hole conc Args: temperature: Temperature to equilibrate fermi energies for chemical_potentials: dict of chemical potentials to use for calculation fermi level bulk_dos: bulk system dos (pymatgen Dos object) Returns: Fermi energy dictated by charge neutrality """ fdos = FermiDos(bulk_dos, bandgap=self.band_gap) _, fdos_vbm = fdos.get_cbm_vbm() def _get_total_q(ef): qd_tot = sum([ d['charge'] * d['conc'] for d in self.defect_concentrations( chemical_potentials=chemical_potentials, temperature=temperature, fermi_level=ef) ]) qd_tot += fdos.get_doping(fermi_level=ef + fdos_vbm, temperature=temperature) return qd_tot return bisect(_get_total_q, -1., self.band_gap + 1.)
class FermiDosTest(unittest.TestCase): def setUp(self): with open(os.path.join(test_dir, "complete_dos.json"), "r") as f: self.dos = CompleteDos.from_dict(json.load(f)) self.dos = FermiDos(self.dos) def test_doping_fermi(self): T = 300 fermi0 = self.dos.efermi frange = [fermi0 - 0.5, fermi0, fermi0 + 2.0, fermi0 + 2.2] dopings = [self.dos.get_doping(fermi=f, T=T) for f in frange] ref_dopings = [3.48077e+21, 1.9235e+18, -2.6909e+16, -4.8723e+19] for i, c_ref in enumerate(ref_dopings): self.assertLessEqual(abs(dopings[i] / c_ref - 1.0), 0.01) calc_fermis = [self.dos.get_fermi(c=c, T=T) for c in ref_dopings] for j, f_ref in enumerate(frange): self.assertAlmostEqual(calc_fermis[j], f_ref, 4) sci_dos = FermiDos(self.dos, bandgap=3.0) for i, c_ref in enumerate(ref_dopings): if c_ref < 0: self.assertAlmostEqual( sci_dos.get_fermi(c_ref, T=T) - frange[i], 0.47, places=2) else: self.assertAlmostEqual( sci_dos.get_fermi(c_ref, T=T) - frange[i], -0.47, places=2) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(-1e26, 300), 7.5108, 4) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(1e26, 300), -1.6884, 4) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(0.0, 300), 3.2382, 4)
class FermiDosTest(unittest.TestCase): def setUp(self): with open(os.path.join(test_dir, "complete_dos.json"), "r") as f: self.dos = CompleteDos.from_dict(json.load(f)) self.dos = FermiDos(self.dos) def test_doping_fermi(self): T = 300 fermi0 = self.dos.efermi frange = [fermi0 - 0.5, fermi0, fermi0 + 2.0, fermi0 + 2.2] dopings = [self.dos.get_doping(fermi=f, T=T) for f in frange] ref_dopings = [3.48077e+21, 1.9235e+18, -2.6909e+16, -4.8723e+19] for i, c_ref in enumerate(ref_dopings): self.assertLessEqual(abs(dopings[i] / c_ref - 1.0), 0.01) calc_fermis = [self.dos.get_fermi(c=c, T=T) for c in ref_dopings] for j, f_ref in enumerate(frange): self.assertAlmostEqual(calc_fermis[j], f_ref, 4) sci_dos = FermiDos(self.dos, bandgap=3.0) for i, c_ref in enumerate(ref_dopings): if c_ref < 0: self.assertAlmostEqual(sci_dos.get_fermi(c_ref, T=T) - frange[i], 0.47, places=2) else: self.assertAlmostEqual(sci_dos.get_fermi(c_ref, T=T) - frange[i], -0.47, places=2)
def test_doping_fermi(self): T = 300 fermi0 = self.dos.efermi frange = [fermi0 - 0.5, fermi0, fermi0 + 2.0, fermi0 + 2.2] dopings = [self.dos.get_doping(fermi_level=f, temperature=T) for f in frange] ref_dopings = [3.48077e21, 1.9235e18, -2.6909e16, -4.8723e19] for i, c_ref in enumerate(ref_dopings): self.assertLessEqual(abs(dopings[i] / c_ref - 1.0), 0.01) calc_fermis = [self.dos.get_fermi(concentration=c, temperature=T) for c in ref_dopings] for j, f_ref in enumerate(frange): self.assertAlmostEqual(calc_fermis[j], f_ref, 4) sci_dos = FermiDos(self.dos, bandgap=3.0) self.assertEqual(sci_dos.get_gap(), 3.0) old_cbm, old_vbm = self.dos.get_cbm_vbm() old_gap = old_cbm - old_vbm new_cbm, new_vbm = sci_dos.get_cbm_vbm() self.assertAlmostEqual(new_cbm - old_cbm, (3.0 - old_gap) / 2.0) self.assertAlmostEqual(old_vbm - new_vbm, (3.0 - old_gap) / 2.0) for i, c_ref in enumerate(ref_dopings): if c_ref < 0: self.assertAlmostEqual(sci_dos.get_fermi(c_ref, temperature=T) - frange[i], 0.47, places=2) else: self.assertAlmostEqual(sci_dos.get_fermi(c_ref, temperature=T) - frange[i], -0.47, places=2) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(-1e26, 300), 7.5108, 4) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(1e26, 300), -1.4182, 4) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(0.0, 300), 2.9071, 4)
class FermiDosTest(unittest.TestCase): def setUp(self): with open( os.path.join(PymatgenTest.TEST_FILES_DIR, "complete_dos.json"), "r") as f: self.dos = CompleteDos.from_dict(json.load(f)) self.dos = FermiDos(self.dos) def test_doping_fermi(self): T = 300 fermi0 = self.dos.efermi frange = [fermi0 - 0.5, fermi0, fermi0 + 2.0, fermi0 + 2.2] dopings = [ self.dos.get_doping(fermi_level=f, temperature=T) for f in frange ] ref_dopings = [3.48077e21, 1.9235e18, -2.6909e16, -4.8723e19] for i, c_ref in enumerate(ref_dopings): self.assertLessEqual(abs(dopings[i] / c_ref - 1.0), 0.01) calc_fermis = [ self.dos.get_fermi(concentration=c, temperature=T) for c in ref_dopings ] for j, f_ref in enumerate(frange): self.assertAlmostEqual(calc_fermis[j], f_ref, 4) sci_dos = FermiDos(self.dos, bandgap=3.0) self.assertEqual(sci_dos.get_gap(), 3.0) old_cbm, old_vbm = self.dos.get_cbm_vbm() old_gap = old_cbm - old_vbm new_cbm, new_vbm = sci_dos.get_cbm_vbm() self.assertAlmostEqual(new_cbm - old_cbm, (3.0 - old_gap) / 2.0) self.assertAlmostEqual(old_vbm - new_vbm, (3.0 - old_gap) / 2.0) for i, c_ref in enumerate(ref_dopings): if c_ref < 0: self.assertAlmostEqual( sci_dos.get_fermi(c_ref, temperature=T) - frange[i], 0.47, places=2) else: self.assertAlmostEqual( sci_dos.get_fermi(c_ref, temperature=T) - frange[i], -0.47, places=2) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(-1e26, 300), 7.5108, 4) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(1e26, 300), -1.4182, 4) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(0.0, 300), 2.5226, 4)
def test_doping_fermi(self): T = 300 fermi0 = self.dos.efermi frange = [fermi0 - 0.5, fermi0, fermi0 + 2.0, fermi0 + 2.2] dopings = [self.dos.get_doping(fermi=f, T=T) for f in frange] ref_dopings = [3.48077e+21, 1.9235e+18, -2.6909e+16, -4.8723e+19] for i, c_ref in enumerate(ref_dopings): self.assertLessEqual(abs(dopings[i] / c_ref - 1.0), 0.01) calc_fermis = [self.dos.get_fermi(c=c, T=T) for c in ref_dopings] for j, f_ref in enumerate(frange): self.assertAlmostEqual(calc_fermis[j], f_ref, 4) sci_dos = FermiDos(self.dos, bandgap=3.0) self.assertEqual( sci_dos.get_gap(), 3.) old_cbm, old_vbm = self.dos.get_cbm_vbm() old_gap = old_cbm - old_vbm new_cbm, new_vbm = sci_dos.get_cbm_vbm() self.assertAlmostEqual( new_cbm - old_cbm, (3. - old_gap) / 2.) self.assertAlmostEqual( old_vbm - new_vbm, (3. - old_gap) / 2.) for i, c_ref in enumerate(ref_dopings): if c_ref < 0: self.assertAlmostEqual( sci_dos.get_fermi(c_ref, T=T) - frange[i], 0.47, places=2) else: self.assertAlmostEqual( sci_dos.get_fermi(c_ref, T=T) - frange[i], -0.47, places=2) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(-1e26, 300), 7.5108, 4) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(1e26, 300), -1.4182, 4) self.assertAlmostEqual(sci_dos.get_fermi_interextrapolated(0.0, 300), 2.5226, 4)
def solve_for_fermi_energy(self, temperature, chemical_potentials, bulk_dos): """ Solve for the Fermi energy self-consistently as a function of T and p_O2 Observations are Defect concentrations, electron and hole conc Args: bulk_dos: bulk system dos (pymatgen Dos object) gap: Can be used to specify experimental gap. Will be useful if the self consistent Fermi level is > DFT gap Returns: Fermi energy """ fdos = FermiDos(bulk_dos, bandgap=self.band_gap) def _get_total_q(ef): qd_tot = sum([ d['charge'] * d['conc'] for d in self.defect_concentrations( chemical_potentials=chemical_potentials, temperature=temperature, fermi_level=ef) ]) qd_tot += fdos.get_doping(fermi=ef + self.vbm, T=temperature) return qd_tot return bisect(_get_total_q, -1., self.band_gap + 1.)
def featurize(self, dos, bandgap=None): """ Args: dos (pymatgen Dos, CompleteDos or FermiDos): bandgap (float): for example the experimentally measured band gap or one that is calculated via more accurate methods than the one used to generate dos. dos will be scissored to have the same electronic band gap as bandgap. Returns ([float]): features are fermi levels in eV at the given concentrations and temperature + eref in eV if return_eref """ dos = FermiDos(dos, bandgap=bandgap) feats = [] eref = 0.0 for c in self.dopings: fermi = dos.get_fermi(concentration=c, temperature=self.T, nstep=50) if isinstance(self.eref, str): if self.eref == "dos_fermi": eref = dos.efermi elif self.eref in ["midgap", "vbm", "cbm"]: ecbm, evbm = dos.get_cbm_vbm() if self.eref == "midgap": eref = (evbm + ecbm) / 2.0 elif self.eref == "vbm": eref = evbm elif self.eref == "cbm": eref = ecbm elif self.eref == "band center": eref = self.BC.featurize(dos.structure.composition)[0] else: raise ValueError('Unsupported "eref": {}'.format( self.eref)) else: eref = self.eref if not self.return_eref: fermi -= eref feats.append(fermi) if self.return_eref: feats.append(eref) return feats
def test_doping_fermi(self): T = 300 fermi0 = self.dos.efermi frange = [fermi0-0.5, fermi0, fermi0+2.0, fermi0+2.2] dopings = [self.dos.get_doping(fermi=f, T=T) for f in frange] ref_dopings = [3.48077e+21, 1.9235e+18, -2.6909e+16, -4.8723e+19] for i, c_ref in enumerate(ref_dopings): self.assertLessEqual(abs(dopings[i]/c_ref-1.0), 0.01) calc_fermis = [self.dos.get_fermi(c=c, T=T) for c in ref_dopings] for j, f_ref in enumerate(frange): self.assertAlmostEqual(calc_fermis[j], f_ref, 4) sci_dos = FermiDos(self.dos, bandgap=3.0) for i, c_ref in enumerate(ref_dopings): if c_ref < 0: self.assertAlmostEqual( sci_dos.get_fermi(c_ref, T=T) - frange[i], 0.47, places=2) else: self.assertAlmostEqual( sci_dos.get_fermi(c_ref, T=T) - frange[i], -0.47, places=2)
def featurize(self, dos, bandgap=None): """ Args: dos (pymatgen Dos, CompleteDos or FermiDos): bandgap (float): for example the experimentally measured band gap or one that is calculated via more accurate methods than the one used to generate dos. dos will be scissored to have the same electronic band gap as bandgap. Returns ([float]): features are fermi levels in eV at the given concentrations and temperature + eref in eV if return_eref """ dos = FermiDos(dos, bandgap=bandgap) feats = [] eref = 0.0 for c in self.dopings: fermi = dos.get_fermi(c=c, T=self.T, nstep=50) if isinstance(self.eref, str): if self.eref == "dos_fermi": eref = dos.efermi elif self.eref in ["midgap", "vbm", "cbm"]: ecbm, evbm = dos.get_cbm_vbm() if self.eref == "midgap": eref = (evbm + ecbm) / 2.0 elif self.eref == "vbm": eref = evbm elif self.eref == "cbm": eref = ecbm elif self.eref == "band center": eref = self.BC.featurize(dos.structure.composition)[0] else: raise ValueError('Unsupported "eref": {}'.format(self.eref)) else: eref = self.eref if not self.return_eref: fermi -= eref feats.append(fermi) if self.return_eref: feats.append(eref) return feats
def solve_for_non_equilibrium_fermi_energy(self, temperature, quench_temperature, chemical_potentials, bulk_dos): """ Solve for the Fermi energy after quenching in the defect concentrations at a higher temperature (the quench temperature), as outlined in P. Canepa et al (2017) Chemistry of Materials (doi: 10.1021/acs.chemmater.7b02909) Args: temperature: Temperature to equilibrate fermi energy at after quenching in defects quench_temperature: Temperature to equilibrate defect concentrations at (higher temperature) chemical_potentials: dict of chemical potentials to use for calculation fermi level bulk_dos: bulk system dos (pymatgen Dos object) Returns: Fermi energy dictated by charge neutrality with respect to frozen in defect concentrations """ high_temp_fermi_level = self.solve_for_fermi_energy( quench_temperature, chemical_potentials, bulk_dos) fixed_defect_charge = sum([ d['charge'] * d['conc'] for d in self.defect_concentrations( chemical_potentials=chemical_potentials, temperature=quench_temperature, fermi_level=high_temp_fermi_level) ]) fdos = FermiDos(bulk_dos, bandgap=self.band_gap) _,fdos_vbm = fdos.get_cbm_vbm() def _get_total_q(ef): qd_tot = fixed_defect_charge qd_tot += fdos.get_doping(fermi=ef + fdos_vbm, T=temperature) return qd_tot return bisect(_get_total_q, -1., self.band_gap + 1.) return
def setUp(self): with open( os.path.join(PymatgenTest.TEST_FILES_DIR, "complete_dos.json")) as f: self.dos = CompleteDos.from_dict(json.load(f)) self.dos = FermiDos(self.dos)
def setUp(self): with open(os.path.join(test_dir, "complete_dos.json"), "r") as f: self.dos = CompleteDos.from_dict(json.load(f)) self.dos = FermiDos(self.dos)