# Run QE workchain in aiida without services
###### tags: `usability`
The general (no QE) doc for light run of AiiDA is https://github.com/aiidateam/aiida-core/pull/6261.
## What to test?
### Is caching working? (Yes)
Turn on the caching for pw calculation, the second time runs the same workchain, all calcjobs are loaded from caching.
### Is pw calculation run in parallel in workchain? (Yes)
Launch the workchain and the firing jobs are not blocked.
There are then 5 pw.x processes runing.
Note: The workflow is run synchrounsly because it is designed to be like this.
Open question: can this improved without go to Xing's `WorkTree` way?
## Pre-requiresites
Install aiida-quatumespresso
```bash
pip install aiida-quantumespresso
```
Install qe from conda:
```
mamba install qe
```
Set a new profile with sqlite
```bash
verdi profile setup core.sqlite_dos -n --profile quickstart --email quickstart@example.com
```
(For non-RMQ profile)
Modify the `.aiida/config.json` , change the `process_control` backend to `null`, the config looks as below.
```yaml
{
"CONFIG_VERSION": {
"CURRENT": 9,
"OLDEST_COMPATIBLE": 9
},
"profiles": {
"quickstart": {
"storage": {
"backend": "core.sqlite_dos",
"config": {
"filepath": "/home/aiida/.aiida/repository/sqlite_dos_471d4a2b000446d9a03f20718654e266"
}
},
"process_control": {
"backend": "null",
"config": {}
},
"PROFILE_UUID": "db71124c84cd4796bb4a7cbf78c9b691",
"options": {},
"default_user_email": "quickstart@example.com"
}
},
"default_profile": "quickstart"
}
```
Set runner intervel to 1s, otherwise it will take long to poll a process
```
verdi config set runner.poll.interval 1
```
Setup computer (localhost)
```
verdi computer setup -L localhost -H localhost -T core.local -S core.direct -w `echo $PWD/work` -n
verdi computer configure core.local localhost --safe-interval 1 -n
```
Setup pw codes
```
# Set up caching
verdi config set -a caching.enabled_for aiida.calculations:quantumespresso.pw
# Set up code
verdi code create core.code.installed \
--non-interactive \
--label pw \
--description "pw.x code on localhost" \
--default-calc-job-plugin quantumespresso.pw \
--computer localhost --prepend-text 'eval "$(conda shell.posix hook)"\nconda activate base\nexport OMP_NUM_THREADS=1' \
--filepath-executable pw.x
```
Setup the pseudos
```
aiida-pseudo install sssp --functional PBE -p precision
```
The python script
## Run EOS workflow case
```python
"""Simple workflow example"""
from aiida.engine import run, Process, calcfunction, workfunction
from aiida.orm import Dict, Float, load_group
from aiida.plugins import CalculationFactory, DbImporterFactory, DataFactory
# Load the calculation class 'PwCalculation' using its entry point 'quantumespresso.pw'
PwCalculation = CalculationFactory("quantumespresso.pw")
Dict = DataFactory("dict")
KpointsData = DataFactory("array.kpoints")
def generate_scf_input_params(structure, code, pseudo_family):
"""Construct a builder for the `PwCalculation` class and populate its inputs.
:return: `ProcessBuilder` instance for `PwCalculation` with preset inputs
"""
parameters = {
"CONTROL": {
"calculation": "scf",
"tstress": True, # Important that this stays to get stress
"tprnfor": True,
},
"SYSTEM": {
"ecutwfc": 30.0,
"ecutrho": 200.0,
},
"ELECTRONS": {
"conv_thr": 1.0e-6,
},
}
kpoints = KpointsData()
kpoints.set_kpoints_mesh([2, 2, 2])
builder = PwCalculation.get_builder()
builder.code = code
builder.structure = structure
builder.kpoints = kpoints
builder.parameters = Dict(dict=parameters)
builder.pseudos = pseudo_family.get_pseudos(structure=structure)
builder.metadata.options.resources = {"num_machines": 1, "num_mpiprocs_per_machine": 2}
builder.metadata.options.max_wallclock_seconds = 30 * 60
return builder
@calcfunction
def rescale(structure, scale):
"""Calculation function to rescale a structure
:param structure: An AiiDA `StructureData` to rescale
:param scale: The scale factor (for the lattice constant)
:return: The rescaled structure
"""
from aiida.orm import StructureData
ase_structure = structure.get_ase()
scale_value = scale.value
new_cell = ase_structure.get_cell() * scale_value
ase_structure.set_cell(new_cell, scale_atoms=True)
return StructureData(ase=ase_structure)
@calcfunction
def create_eos_dictionary(**kwargs):
"""Create a single `Dict` node from the `Dict` output parameters of completed `PwCalculations`.
The dictionary will contain a list of tuples, where each tuple contains the volume, total energy and its units
of the results of a calculation.
:return: `Dict` node with the equation of state results
"""
eos = [
(result.dict.volume, result.dict.energy, result.dict.energy_units)
for label, result in kwargs.items()
]
return Dict(dict={"eos": eos})
@workfunction
def run_eos_wf(code, pseudo_family_label, structure):
"""Run an equation of state of a bulk crystal structure for the given element."""
# This will print the pk of the work function
print("Running run_eos_wf<{}>".format(Process.current().pid))
scale_factors = (0.96, 0.98, 1.0, 1.02, 1.04)
labels = ["c1", "c2", "c3", "c4", "c5"]
pseudo_family = load_group(pseudo_family_label.value)
calculations = {}
# Loop over the label and scale_factor pairs
for label, factor in list(zip(labels, scale_factors)):
# Generated the scaled structure from the initial structure
rescaled_structure = rescale(structure, Float(factor))
# Generate the inputs for the `PwCalculation`
inputs = generate_scf_input_params(rescaled_structure, code, pseudo_family)
# Launch a `PwCalculation` for each scaled structure
print(
"Running a scf for {} with scale factor {}".format(
structure.get_formula(), factor
)
)
calculations[label] = run(PwCalculation, **inputs)
# Bundle the individual results from each `PwCalculation` in a single dictionary node.
# Note: since we are 'creating' new data from existing data, we *have* to go through a `calcfunction`, otherwise
# the provenance would be lost!
inputs = {
label: result["output_parameters"] for label, result in calculations.items()
}
eos = create_eos_dictionary(**inputs)
# Finally, return the eos Dict node
return eos
# load structure from COD
CodDbImporter = DbImporterFactory('cod')
cod = CodDbImporter()
structure = cod.query(id='1526655')[0].get_aiida_structure()
# If it is a cif file can import in CLI as
# verdi data core.structure import ase /opt/examples/Si.cif
# Code
code = load_code('pw@localhost')
# Pseudo
pseudo_family_label = Str('SSSP/1.3/PBE/precision')
# Launch the workflow
result = run_eos_wf(code, pseudo_family_label, structure)
print(result)
```
Run the script by:
```bash
verdi run run-eos-wf.py
```
## Run EOS workchain case
Save the EOS workchain module in a file `$HOME/eos.py`, add the path to PYTHONPATH: `export PYTHONPATH=\$PYTHONPATH:$HOME`
```python
"""Equation of State WorkChain."""
from aiida.engine import WorkChain, ToContext, calcfunction
from aiida.orm import Code, Dict, Float, Str, StructureData, load_group
from aiida.plugins import CalculationFactory, DataFactory
from aiida.engine import run
scale_facs = (0.96, 0.98, 1.0, 1.02, 1.04)
labels = ["c1", "c2", "c3", "c4", "c5"]
# Load the calculation class 'PwCalculation' using its entry point 'quantumespresso.pw'
PwCalculation = CalculationFactory("quantumespresso.pw")
Dict = DataFactory("dict")
KpointsData = DataFactory("array.kpoints")
def generate_scf_input_params(structure, code, pseudo_family):
"""Construct a builder for the `PwCalculation` class and populate its inputs.
:return: `ProcessBuilder` instance for `PwCalculation` with preset inputs
"""
parameters = {
"CONTROL": {
"calculation": "scf",
"tstress": True, # Important that this stays to get stress
"tprnfor": True,
},
"SYSTEM": {
"ecutwfc": 30.0,
"ecutrho": 200.0,
},
"ELECTRONS": {
"conv_thr": 1.0e-6,
},
}
kpoints = KpointsData()
kpoints.set_kpoints_mesh([2, 2, 2])
builder = PwCalculation.get_builder()
builder.code = code
builder.structure = structure
builder.kpoints = kpoints
builder.parameters = Dict(dict=parameters)
builder.pseudos = pseudo_family.get_pseudos(structure=structure)
builder.metadata.options.resources = {"num_machines": 1, "num_mpiprocs_per_machine": 2}
builder.metadata.options.max_wallclock_seconds = 30 * 60
return builder
@calcfunction
def rescale(structure, scale):
"""Calculation function to rescale a structure
:param structure: An AiiDA `StructureData` to rescale
:param scale: The scale factor (for the lattice constant)
:return: The rescaled structure
"""
from aiida.orm import StructureData
ase_structure = structure.get_ase()
scale_value = scale.value
new_cell = ase_structure.get_cell() * scale_value
ase_structure.set_cell(new_cell, scale_atoms=True)
return StructureData(ase=ase_structure)
@calcfunction
def get_eos_data(**kwargs):
"""Store EOS data in Dict node."""
eos = [
(result.dict.volume, result.dict.energy, result.dict.energy_units)
for label, result in kwargs.items()
]
return Dict(dict={"eos": eos})
class EquationOfState(WorkChain):
"""WorkChain to compute Equation of State using Quantum ESPRESSO."""
@classmethod
def define(cls, spec):
"""Specify inputs and outputs."""
super().define(spec)
spec.input("code", valid_type=Code)
spec.input("pseudo_family_label", valid_type=Str)
spec.input("structure", valid_type=StructureData)
spec.output("eos", valid_type=Dict)
spec.outline(
cls.run_eos,
cls.results,
)
def run_eos(self):
"""Run calculations for equation of state."""
# Create basic structure and attach it as an output
structure = self.inputs.structure
calculations = {}
pseudo_family = load_group(self.inputs.pseudo_family_label.value)
for label, factor in zip(labels, scale_facs):
rescaled_structure = rescale(structure, Float(factor))
inputs = generate_scf_input_params(
rescaled_structure, self.inputs.code, pseudo_family
)
self.report(
"Running an SCF calculation for {} with scale factor {}".format(
structure.get_formula(), factor
)
)
calcjob_node = self.submit(PwCalculation, **inputs)
calculations[label] = calcjob_node
# Ask the workflow to continue when the results are ready and store them in the context
return ToContext(**calculations)
def results(self):
"""Process results."""
inputs = {
label: self.ctx[label].get_outgoing().get_node_by_label("output_parameters")
for label in labels
}
eos = get_eos_data(**inputs)
# Attach Equation of State results as output node to be able to plot the EOS later
self.out("eos", eos)
```
Start the AiiDA ipython (or create a new scrip) by `verdi shell` and run:
```python
from aiida.plugins import DbImporterFactory
from aiida.engine import run
from eos import EquationOfState
# load structure from COD
CodDbImporter = DbImporterFactory('cod')
cod = CodDbImporter()
structure = cod.query(id='1526655')[0].get_aiida_structure()
# Code
code = load_code('pw@localhost')
# Pseudo
pseudo_family_label = Str('SSSP/1.3/PBE/precision')
node = run(EquationOfState, code=load_code('pw@localhost'), pseudo_family_label=Str('SSSP/1.3/PBE/precision'), structure=structure)
print(node)
```