Skip to article frontmatterSkip to article content

Example - Equation of State for bulk Silicon

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