# 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) ```