Source code for qmpy.materials.formation_energy
# qmpy/materials/formation_energy.py
from django.db import models
from django.db.models import Min
import json
import pprint
import logging
from qmpy.db.custom import DictField
import qmpy.materials.composition as Comp
import qmpy.analysis as vasp
import qmpy.materials.element as elt
from qmpy.data import *
from qmpy.utils import *
logger = logging.getLogger(__name__)
[docs]class ExptFormationEnergy(models.Model):
"""Experimentally measured formation energy.
Any external formation energy should be entered as an ExptFormationEnergy
object, rather than a FormationEnergy. If the external source is also
computational, set the "dft" attribute to be True.
Relationships:
| :mod:`~qmpy.Composition` via composition
| :mod:`~qmpy.Fit` via fit
Attributes:
| id: integer primary key.
| delta_e: measured formation energy.
| delta_g: measured free energy of formation.
| dft: (bool) True if the formation energy is from a non-OQMD DFT
| calculation.
| source: (str) Identifier for the source.
"""
composition = models.ForeignKey(
"Composition", null=True, blank=True, on_delete=models.PROTECT
)
delta_e = models.FloatField(null=True)
delta_g = models.FloatField(null=True)
source = models.CharField(max_length=127, blank=True, null=True)
dft = models.BooleanField(default=False)
class Meta:
app_label = "qmpy"
db_table = "expt_formation_energies"
def __str__(self):
return "%s : %s" % (self.composition, self.delta_e)
@classmethod
def read_file(cls, filename, dft=False):
source = filename.split(".")[0]
expts = []
for line in open(filename, "r"):
comp, energy = line.strip().split()
expt, new = ExptFormationEnergy.objects.get_or_create(
delta_e=energy,
composition=Comp.Composition.get(comp),
source=source,
dft=dft,
)
if new:
expt.save()
expts.append(expt)
return expts
[docs]class HubbardCorrection(models.Model):
"""
Energy correction for DFT+U energies.
Relationships:
| :mod:`~qmpy.Fit` via fit
| :mod:`~qmpy.Element` via element
| :mod:`~qmpy.Hubbard` via hubbard
Attributes:
| id
| value: Correction energy (eV/atom)
"""
element = models.ForeignKey("Element", on_delete=models.CASCADE)
hubbard = models.ForeignKey("Hubbard", on_delete=models.CASCADE)
value = models.FloatField()
fit = models.ForeignKey(
"Fit",
blank=True,
null=True,
on_delete=models.CASCADE,
related_name="hubbard_correction_set",
)
class Meta:
app_label = "qmpy"
db_table = "hubbard_corrections"
[docs]class ReferenceEnergy(models.Model):
"""
Elemental reference energy for evaluating heats of formation.
Relationships:
| :mod:`~qmpy.Fit` via fit
| :mod:`~qmpy.Element` via element
Attributes:
| id
| value: Reference energy (eV/atom)
"""
element = models.ForeignKey("Element", on_delete=models.CASCADE)
value = models.FloatField()
fit = models.ForeignKey(
"Fit",
blank=True,
null=True,
on_delete=models.CASCADE,
related_name="reference_energy_set",
)
class Meta:
app_label = "qmpy"
db_table = "reference_energies"
[docs]class FormationEnergy(models.Model):
"""
Base class for a formation energy.
Relationships:
| :mod:`~qmpy.Calculation` via calculation
| :mod:`~qmpy.Composition` via composition
| :mod:`~qmpy.Entry` via entry
| :mod:`~qmpy.FormationEnergy` via equilibrium
| :mod:`~qmpy.Fit` via fit
Attributes:
| id
| delta_e: Formation energy (eV/atom)
| description: A label of the source of the formation energy.
| stability: Distance from the convex hull (eV/atom)
"""
composition = models.ForeignKey(
"Composition", null=True, blank=True, on_delete=models.PROTECT
)
entry = models.ForeignKey("Entry", null=True, blank=True, on_delete=models.CASCADE)
calculation = models.ForeignKey(
"Calculation", null=True, blank=True, on_delete=models.CASCADE
)
description = models.CharField(max_length=20, null=True, blank=True)
fit = models.ForeignKey("Fit", null=True, on_delete=models.PROTECT)
stability = models.FloatField(blank=True, null=True)
delta_e = models.FloatField(null=True)
equilibrium = models.ManyToManyField("self", blank=True)
class Meta:
app_label = "qmpy"
db_table = "formation_energies"
@classmethod
def get(cls, calculation, fit="standard"):
fit = Fit.get(fit)
try:
return FormationEnergy.objects.get(calculation=calculation, fit=fit)
except FormationEnergy.DoesNotExist:
return FormationEnergy(calculation=calculation, fit=fit)
@staticmethod
def search(bounds, fit="standard"):
space = set()
if isinstance(bounds, str):
bounds = bounds.split("-")
for b in bounds:
bound = parse_comp(b)
space |= set(bound.keys())
in_elts = elt.Element.objects.filter(symbol__in=space)
out_elts = elt.Element.objects.exclude(symbol__in=space)
forms = FormationEnergy.objects.filter(fit=fit)
forms = forms.exclude(composition__element_set__in=out_elts)
forms = forms.filter(composition__element_set__in=in_elts)
forms = forms.filter(composition__ntypes__lte=len(space))
return forms.distinct()
def __str__(self):
return "%s : %s" % (self.composition, self.delta_e)
[docs] def save(self, *args, **kwargs):
self.composition = self.calculation.composition
self.entry = self.calculation.entry
super(FormationEnergy, self).save(*args, **kwargs)
Formation = FormationEnergy
[docs]class Fit(models.Model):
"""
The core model for a reference energy fitting scheme.
The Fit model links to the experimental data (ExptFormationEnergy objects)
that informed the fit, as well as the DFT calculations (Calculation objects)
that were matched to each experimental formation energy. Once the fit is
completed, it also stores a list of chemical potentials both as a
relationship to ReferenceEnergy and HubbardCorrection objects.
These correction energies can also be accessed by dictionaries at
Fit.mus and Fit.hubbard_mus.
Relationships:
| :mod:`~qmpy.Calculation` via dft
| :mod:`~qmpy.ExptFormationEnergy` via experiments
| :mod:`~qmpy.FormationEnergy` via formationenergy_set
| :mod:`~qmpy.HubbardCorrection` via hubbard_correction_set
| :mod:`~qmpy.ReferenceEnergy` via reference_energy_set
Attributes:
| name: Name for the fitting
Examples::
>>> f = Fit.get('standard')
>>> f.experiments.count()
>>> f.dft.count()
>>> f.mus
>>> f.hubbard_mus
"""
name = models.CharField(max_length=255, primary_key=True)
elements = models.ManyToManyField("Element")
experiments = models.ManyToManyField("ExptFormationEnergy")
dft = models.ManyToManyField("Calculation")
class Meta:
app_label = "qmpy"
db_table = "fits"
@classmethod
def get(cls, name):
try:
return Fit.objects.get(name=name)
except Fit.DoesNotExist:
return Fit(name=name)
@property
def mus(self):
mus = self.reference_energy_set.values_list("element_id", "value")
return dict(mus)
@property
def hubbard_mus(self):
hm = self.hubbard_correction_set.all()
return dict((h.hubbard.key, h.value) for h in hm)