Skip to main content

Overview

The EOS task calculates the equation of state (EOS) of a crystal by:
  1. Performing a full cell relaxation via the OPT task.
  2. Generating npoints uniformly strained copies of the relaxed cell.
  3. Relaxing atomic positions (fixed cell) for each strained copy.
  4. Fitting the resulting energy–volume data with the Birch–Murnaghan EOS (via pymatgen).
It is registered as a Prefect task with the name EOS and uses a TASK_SOURCE + INPUTS cache policy.

Function signature

from mlip_arena.tasks.eos import run as EOS

result = EOS(
    atoms,
    calculator,
    optimizer="BFGSLineSearch",
    optimizer_kwargs=None,
    filter="FrechetCell",
    filter_kwargs=None,
    criterion=None,
    max_abs_strain=0.1,
    npoints=11,
    concurrent=True,
    cache_opt=False,
)

Parameters

body.atoms
ase.Atoms
required
The input atomic structure.
body.calculator
ase.calculators.calculator.BaseCalculator
required
ASE-compatible calculator for energy and force evaluations.
body.optimizer
Optimizer | str
default:"BFGSLineSearch"
Optimizer used for both the initial full relaxation and the per-strain position relaxations. See OPT for accepted string values.
body.optimizer_kwargs
dict | None
default:"None"
Extra keyword arguments forwarded to the optimizer constructor.
body.filter
Filter | str | None
default:"FrechetCell"
Filter applied during the initial full relaxation only. Subsequent per-strain runs use filter=None (positions only). See OPT for accepted string values.
body.filter_kwargs
dict | None
default:"None"
Extra keyword arguments forwarded to the filter constructor.
body.criterion
dict | None
default:"None"
Convergence criteria forwarded to optimizer.run(). See OPT for details.
body.max_abs_strain
number
default:"0.1"
Maximum absolute volumetric strain applied to each lattice dimension. Linear scale factors span [1 - max_abs_strain, 1 + max_abs_strain]^(1/3).
body.npoints
number
default:"11"
Number of strain points (i.e., structures) used to sample the energy–volume curve.
body.concurrent
boolean
default:"true"
When True, all per-strain OPT sub-tasks are submitted concurrently using OPT.submit() and collected with prefect.futures.wait. When False, they run sequentially.
body.cache_opt
boolean
default:"false"
When True, persists and caches intermediate OPT results so repeated calls with the same inputs skip re-computation. When False, intermediate results are not cached.

Internal OPT chaining

EOS internally calls the OPT task twice per strain point:
  1. Initial relaxation — uses the provided filter to relax both cell and positions.
  2. Per-strain relaxationsfilter=None, so only atomic positions are relaxed at each fixed strained volume.
Both calls respect optimizer, optimizer_kwargs, and criterion. The cache_opt flag controls whether persist_result and refresh_cache are set on the inner OPT calls.

Return value

{
    "atoms": Atoms,
    "eos":   {"volumes": list[float], "energies": list[float]},
    "K":     float,
    "b0":    float,
    "b1":    float,
    "e0":    float,
    "v0":    float,
}
atoms
ase.Atoms
The fully relaxed (equilibrium) structure.
eos
object
Raw energy–volume data used for the fit.
K
number
Bulk modulus in GPa (b0 converted from eV/ų).
b0
number
Bulk modulus in eV/ų (Birch–Murnaghan b0 parameter).
b1
number
Pressure derivative of the bulk modulus (dimensionless).
e0
number
Equilibrium energy in eV.
v0
number
Equilibrium volume in ų.
If the initial relaxation fails, the function returns a Prefect State object instead of a dict. Always check isinstance(result, dict) before accessing keys.

Example

from ase.build import bulk
from mlip_arena.models import MLIPEnum
from mlip_arena.tasks.eos import run as EOS
from mlip_arena.tasks.utils import get_calculator

atoms = bulk("Si", "diamond", a=5.43)
calculator = get_calculator(MLIPEnum["MACE-MP(M)"])

result = EOS(
    atoms=atoms,
    calculator=calculator,
    optimizer="BFGSLineSearch",
    filter="FrechetCell",
    criterion={"fmax": 0.01},
    max_abs_strain=0.1,
    npoints=11,
    concurrent=True,
)

print(f"Bulk modulus: {result['K']:.1f} GPa")
print(f"Equilibrium volume: {result['v0']:.3f} ų")
print(f"Equilibrium energy: {result['e0']:.4f} eV")