# -*- coding: utf-8 -*-
"""
Created on 2021-06-03
@author: Vincent Michaud-Rioux
"""
from nptyping import NDArray
from nanotools.base import Base, Quantity
from nanotools.utils import to_quantity, ureg
from nanotools.totalenergy import TotalEnergy
from nanotools.utils import dict_converter
from typing import List
import attr
import numpy as np
[docs]
@attr.s
class EquationOfState(Base):
"""``EquationOfState`` class.
Examples::
from nanotools import Atoms, Cell, System, TotalEnergy
from nanotools.eos import EquationOfState as EOS
a = 5.64 / 2. # lattice constant (ang)
cell = Cell(avec=[[0.,a,a],[a,0.,a],[a,a,0.]], resolution=0.15)
fxyz = [[0.00,0.00,0.00],[0.25,0.25,0.25]]
atoms = Atoms(fractional_positions=fxyz, formula="GaAs")
sys = System(cell=cell, atoms=atoms)
sys.kpoint.set_grid([7,7,7])
calc = TotalEnergy(sys)
eos = EOS.from_totalenergy(calc)
eos.relative_volumes = [0.96, 0.98, 1.00, 1.02, 1.04]
eos.solve()
eos.plot_eos()
Attributes:
calculators:
Stores the calculators for each volume.
equilibrium_volume:
Equilibrium volume.
initial_volume:
Initial volume.
reference_calculator:
Total energy calculator.
relative_volumes:
Relative volumes used to compute the equation of states. For instance, setting
[0.96, 0.98, 1. , 1.02, 1.04] will compute the total energy for five volumes
corresponding to 96% up to 104% of initial_volume.
"""
# input is dictionary with default constructor
reference_calculator: TotalEnergy = attr.ib(
converter=lambda d: dict_converter(d, TotalEnergy),
validator=attr.validators.optional(attr.validators.instance_of(TotalEnergy)),
)
bulk_modulus: Quantity = attr.ib(
default=None,
converter=attr.converters.optional(
lambda x: to_quantity(x, "ev / angstrom ** 3")
),
validator=attr.validators.optional(attr.validators.instance_of(Quantity)),
)
equilibrium_volume: Quantity = attr.ib(
default=None,
converter=attr.converters.optional(lambda x: to_quantity(x, "angstrom ** 3")),
validator=attr.validators.optional(attr.validators.instance_of(Quantity)),
)
initial_volume: Quantity = attr.ib(
default=None,
converter=attr.converters.optional(lambda x: to_quantity(x, "angstrom ** 3")),
validator=attr.validators.optional(attr.validators.instance_of(Quantity)),
)
relative_volumes: NDArray = attr.ib(
default=np.arange(0.94, 1.06, 0.02),
converter=lambda v: np.array(v),
)
calculators: List[TotalEnergy] = attr.ib(default=[])
def __attrs_post_init__(self):
if self.calculators is not None:
calc = []
for s in self.calculators:
if isinstance(s, dict):
calc.append(TotalEnergy(**s))
elif isinstance(s, TotalEnergy):
calc.append(s)
else:
raise Exception("Invalid TotalEnergy calculators.")
self.calculators = calc
@classmethod
def from_totalenergy(cls, totalenergy, **kwargs):
if isinstance(totalenergy, TotalEnergy):
pass
else:
totalenergy = TotalEnergy.read(totalenergy)
calc = cls(totalenergy, **kwargs)
calc.initial_volume = totalenergy.system.cell.get_volume()
return calc
[docs]
def get_bulk_modulus(self):
"""Returns the bulk modulus as calculated with a Birch-Murnaghan fit."""
if self.bulk_modulus is not None:
return self.bulk_modulus
self.get_equilibrium_volume_per_atom()
return self.bulk_modulus
[docs]
def get_equilibrium_volume(self):
"""Returns the equilibrium volume as calculated with a Birch-Murnaghan fit."""
if self.equilibrium_volume is not None:
return self.equilibrium_volume
eos = self._get_eos()
v0, e0, B = eos.fit()
self.equilibrium_volume = v0 * ureg.angstrom**3
self.bulk_modulus = B * ureg.eV / ureg.angstrom**3
return self.equilibrium_volume
def get_total_energies(self):
return Quantity.from_list(
[c.energy.get_total_energy() for c in self.calculators]
)
def get_total_energies_per_atom(self):
return Quantity.from_list(
[c.get_total_energy_per_atom() for c in self.calculators]
)
def get_volumes(self):
return Quantity.from_list(
[c.system.cell.get_volume() for c in self.calculators]
)
def get_volumes_per_atom(self):
natom = self.reference_calculator.system.get_number_of_atoms()
return self.get_volumes() / natom
[docs]
def plot_eos(self, filename=None, show=True):
"""Generates a plot of the equation of states (+ fit).
Args:
filename (str, optional):
If not None, then the figure is saved to filename.
show (bool, optional):
If True block and show figure.
If False, do not show figure.
"""
eos = self._get_eos()
eos.plot(filename=filename, show=show)
def solve(self):
# make sure units are consistent
self.set_units("si")
n = 0
for rv in self.relative_volumes:
calc = self.reference_calculator.copy()
v = self.initial_volume * rv
calc.system.set_volume(v)
calc.solve(output=f"nano_scf_out_{n}.json")
self.calculators.append(calc)
n += 1
self.write("nano_eos_out.json")
def _get_eos(self):
from ase.eos import EquationOfState
volumes = self.get_volumes_per_atom().to("angstrom ** 3")
energies = self.get_total_energies_per_atom().to("eV")
eos = EquationOfState(volumes.m, energies.m, eos="birchmurnaghan")
return eos