In the following we show how to run the Equation of States (EoS) for Silicon, using the atomistic StructureData
as input.
We build up the workflow using the aiida-workgraph
package, adapting the EoS WorkGraph
from the workgraph-collections
and using the Silicon example as reference.
from aiida import load_profile, orm
from aiida_workgraph import task, WorkGraph
from aiida_quantumespresso.workflows.pw.base import PwBaseWorkChain
from aiida_atomistic import StructureData, StructureDataMutable
load_profile()
Profile<uuid='1a5a8d0836814a04a238c67cc7481655' name='default'>
Let’s define the functions to rescale the structure and then to fit the EoS (we refer to the original functions here:
# explicitly define the output socket name to match the return value of the function
@task.calcfunction(outputs=[{"name": "structures"}, {"name": "volumes"}])
def scale_structure(structure: StructureData, scales: list):
"""Scale the structure by the given scales."""
atoms = structure.to_ase()
volumes = {}
structures = {}
for i in range(len(scales)):
atoms1 = atoms.copy()
atoms1.set_cell(atoms.cell * scales[i], scale_atoms=True)
structure = StructureData.from_ase(atoms1)
structures[f"s_{i}"] = structure
volumes[f"s_{i}"] = structure.properties.cell_volume
return {"structures": structures, "volumes": orm.Dict(volumes)}
@task.calcfunction()
# because this is a calcfunction, and the input scf_outputs are dynamic, we need use **scf_outputs.
def fit_eos(volumes: dict = None, **scf_outputs):
"""Fit the EOS of the data."""
from ase.eos import EquationOfState
from ase.units import kJ
volumes_list = []
energies = []
for key, data in scf_outputs.items():
unit = data.dict.energy_units
energy = data.dict.energy
if unit == "a.u.": # convert to eV
energy = energy * 27.21138602
energies.append(energy)
volumes_list.append(volumes.get_dict()[key])
#
eos = EquationOfState(volumes_list, energies)
v0, e0, B = eos.fit()
# convert B to GPa
B = B / kJ * 1.0e24
eos = orm.Dict({"energy unit": "eV", "v0": v0, "e0": e0, "B": B})
return eos
Then we build the WorkGraph
:
# Output result from context to the output socket
@task.graph_builder(outputs=[{"name": "result", "from": "context.result"}])
def all_scf(structures, scf_inputs, pseudo_family_name="SSSP/1.3/PBEsol/efficiency"):
"""Run the scf calculation for each structure."""
from aiida_workgraph import WorkGraph
from aiida_quantumespresso.calculations.pw import PwCalculation
wg = WorkGraph()
for key, structure in structures.items():
pseudo_family = orm.load_group(pseudo_family_name)
scf_inputs["pseudos"] = pseudo_family.get_pseudos(structure=structure)
scf = wg.tasks.new(PwCalculation, name=f"scf_{key}", structure=structure)
scf.set(scf_inputs)
# save the output parameters to the context
scf.set_context({"output_parameters": f"result.{key}"})
return wg
@task.graph_builder(outputs=[{"name": "result", "from": "fit_eos.result"}])
def eos_workgraph(
structure: StructureData = None,
code: orm.Code = None,
scales: list = None,
parameters: dict = None,
kpoints: orm.KpointsData = None,
pseudo_family_name: str = "SSSP/1.3/PBEsol/efficiency",
metadata: dict = None,
):
"""Workgraph for EOS calculation.
1. Get the scaled structures.
2. Run the SCF calculation for each scaled structure.
3. Fit the EOS.
"""
wg = WorkGraph("EOS")
scale_structure1 = wg.tasks.new(
scale_structure, name="scale_structure", structure=structure, scales=scales
)
all_scf1 = wg.tasks.new(
all_scf,
name="all_scf",
structures=scale_structure1.outputs["structures"],
scf_inputs={
"code": code,
"parameters": orm.Dict(parameters),
"kpoints": kpoints,
"metadata": metadata,
},
)
wg.tasks.new(
fit_eos,
name="fit_eos",
volumes=scale_structure1.outputs["volumes"],
scf_outputs=all_scf1.outputs["result"],
)
return wg
Then we build the structure and we initialise the workgraph:
from ase.build import bulk
ase_silicon = bulk("Si")
structure = StructureData.from_ase(ase_silicon)
parameters = {
"CONTROL": {
"calculation": "scf",
},
"SYSTEM": {
"ecutwfc": 30,
"ecutrho": 240,
"occupations": "smearing",
"smearing": "gaussian",
"degauss": 0.1,
},
}
kpoints = orm.KpointsData()
kpoints.set_kpoints_mesh([3, 3, 3])
#
metadata = {
"options": {
"resources": {
"num_machines": 1,
"num_mpiprocs_per_machine": 1,
},
}
}
wg = eos_workgraph(
structure = structure,
code = orm.load_code("pw-qe-7.2@localhost"),
scales = [0.98+i/100 for i in range(5)],
parameters=parameters,
kpoints=kpoints,
pseudo_family_name="SSSP/1.3/PBEsol/efficiency",
metadata=metadata,
)
wg.submit()
WorkGraph process created, PK: 2198
<WorkChainNode: uuid: c445becb-1c9f-4aa1-b20e-453a1f2e0fbc (pk: 2198) (aiida.workflows:workgraph.engine)>
#------------------------- Print the output -------------------------
data = orm.load_node(wg.pk).called[-1].outputs.result.get_dict()
print('\nResult: \nB: {B}\nv0: {v0}\ne0: {e0}\nv0: {v0}'.format(**data))
Result:
B: 83.930472219935
v0: 40.947349586556
e0: -308.18973212512
v0: 40.947349586556