Skip to content

Commit

Permalink
Add EOS calculator.
Browse files Browse the repository at this point in the history
  • Loading branch information
Shyue Ping Ong committed Aug 9, 2023
1 parent 62c1e31 commit a385ed9
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 6 deletions.
71 changes: 71 additions & 0 deletions matcalc/eos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Calculators for EOS and associated properties."""

from __future__ import annotations

import numpy as np
from pymatgen.analysis.eos import BirchMurnaghan

from .base import PropCalc
from .relaxation import RelaxCalc


class EOSCalc(PropCalc):
"""Equation of state calculator."""

def __init__(
self,
calculator,
relax_structure: bool = True,
fmax: float = 0.1,
steps: int = 500,
n_points: int = 11,
):
"""
Args:
calculator: ASE Calculator to use.
relax_structure: Whether to first relax the structure. Set to False if structures provided are pre-relaxed
with the same calculator.
fmax (float): Max force for relaxation (of structure as well as atoms).
steps (int): Max number of steps for relaxation.
n_points (int): Number of points in which to compute the EOS. Defaults to 11.
"""
self.calculator = calculator
self.relax_structure = relax_structure
self.n_points = n_points
self.fmax = fmax
self.steps = steps

def calc(self, structure):
"""Fit the Birch-Murnaghan equation of state.
Args:
structure: A Structure object.
Returns:
{
"EOS": {
"volumes": volumes,
"energies": energies
},
"K": bm.b0,
}
"""
if self.relax_structure:
relaxer = RelaxCalc(self.calculator, fmax=self.fmax, steps=self.steps)
structure = relaxer.calc(structure)["final_structure"]

volumes, energies = [], []
relaxer = RelaxCalc(self.calculator, fmax=self.fmax, steps=self.steps, relax_cell=False)
for idx in np.linspace(-0.1, 0.1, self.n_points):
structure_strained = structure.copy()
structure_strained.apply_strain([idx, idx, idx])
result = relaxer.calc(structure_strained)
volumes.append(result["final_structure"].volume)
energies.append(result["energy"])
bm = BirchMurnaghan(volumes=volumes, energies=energies)
bm.fit()

return {
"EOS": {"volumes": volumes, "energies": energies},
"K (GPa)": bm.b0_GPa,
}
18 changes: 12 additions & 6 deletions matcalc/relaxation.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,23 +92,26 @@ def __init__(
steps: int = 500,
traj_file: str | None = None,
interval=1,
relax_cell=True,
):
"""
Args:
calculator: ASE Calculator to use.
optimizer (str or ase Optimizer): the optimization algorithm.
Defaults to "FIRE"
fmax (float): total force tolerance for relaxation convergence. fmax is a sum of force and stress forces.
steps (int): max number of steps for relaxation.
traj_file (str): the trajectory file for saving
interval (int): the step interval for saving the trajectories.
fmax (float): Total force tolerance for relaxation convergence. fmax is a sum of force and stress forces.
steps (int): Max number of steps for relaxation.
traj_file (str): The trajectory file for saving
interval (int): The step interval for saving the trajectories.
relax_cell (bool): Whether to relax the cell.
"""
self.calculator = calculator
self.optimizer: Optimizer = OPTIMIZERS[optimizer] if isinstance(optimizer, str) else optimizer
self.fmax = fmax
self.interval = interval
self.steps = steps
self.traj_file = traj_file
self.relax_cell = relax_cell

def calc(self, structure) -> dict:
"""
Expand All @@ -134,14 +137,16 @@ def calc(self, structure) -> dict:
stream = io.StringIO()
with contextlib.redirect_stdout(stream):
obs = TrajectoryObserver(atoms)
atoms = ExpCellFilter(atoms)
if self.relax_cell:
atoms = ExpCellFilter(atoms)
optimizer = self.optimizer(atoms)
optimizer.attach(obs, interval=self.interval)
optimizer.run(fmax=self.fmax, steps=self.steps)
if self.traj_file is not None:
obs()
obs.save(self.traj_file)
atoms = atoms.atoms
if self.relax_cell:
atoms = atoms.atoms

final_structure = ase_adaptor.get_structure(atoms)
lattice = final_structure.lattice
Expand All @@ -155,4 +160,5 @@ def calc(self, structure) -> dict:
"beta": lattice.beta,
"gamma": lattice.gamma,
"volume": lattice.volume,
"energy": obs.energies[-1],
}
20 changes: 20 additions & 0 deletions tests/test_eos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Tests for PhononCalc class"""
from __future__ import annotations

import pytest

from matcalc.eos import EOSCalc


def test_PhononCalc(Li2O, LiFePO4, M3GNetUPCalc):
"""Tests for PhononCalc class"""
calculator = M3GNetUPCalc
# Note that the fmax is probably too high. This is for testing purposes only.
pcalc = EOSCalc(calculator)
results = pcalc.calc(Li2O)

assert results["K (GPa)"] == pytest.approx(69.86879801931632, 1e-4)

results = list(pcalc.calc_many([Li2O, LiFePO4]))
assert len(results) == 2
assert results[1]["K (GPa)"] == pytest.approx(60.083101510774476, 1e-4)

0 comments on commit a385ed9

Please sign in to comment.