Default Templates

This page describes the structure and design of the default task templates. These templates are used to write task and calculation scripts when writing task inputs (i.e., with Task.write_inputs()). All templates are written in Jinja.

run.sh.j2

run.sh.j2 is the default task script template for all autojob-defined TaskBase implementations. With this template, scheduler inputs are written, configuration files (/etc/profile and $HOME/.bash_profile) are sourced, and the task is executed. The resulting script is intended to be executed with Bash.

 1#!/bin/bash
 2{% if scheduler_inputs %}
 3{% for key, value in scheduler_inputs.items() %}
 4{% if value %}
 5#SBATCH {{ key }}={{ value }}
 6{% endif %}
 7{% endfor %}
 8{% endif %}
 9
10echo "\n... Setting up shell environment ...\n"
11shopt -s expand_aliases
12
13if test -e "/etc/profile"; then source "/etc/profile"; fi
14if test -e "$HOME/.bash_profile"; then source "$HOME/.bash_profile"; fi
15
16# run task
17{% if calculation_inputs %}
18python {{ calculation_inputs.calculation_script }}
19{% else %}
20cp {{ task_inputs.atoms_filename }} {{ settings.OUTPUT_ATOMS_FILE }}
21{% endif %}

The first block (i.e., the block starting with {% if scheduler_inputs %}) prints out the scheduler inputs for the task. The block writes the scheduler inputs (if defined) in a format supported by the SLURM workload manager. Next, the expand_aliases shell option is enabled. (This is useful since aliases are often used to condense useful commands for use in task scripts. For example, it is common to define an alias for activating Python virtual environments.) This is followed by sourcing common Bash configuration files, /etc/profile and $HOME/.bash_profile. Finally, depending on whether the task has calculation inputs or not, the last block will either execute the calculation script with Python or copy the inputs file to the outputs file.

calc.sh.j2

calc.sh.j2 is a built-in task script template that leverages advanced features of autojob. This template extends run.sh.j2 be loading modules, calling custom commands (activate_env, debug-slurm, log-job), and then calling autojob CLI commands (autojob run, autojob harvest, autojob clean, autojob restart, and autojob advance).

 1#!/bin/bash
 2{% if scheduler_inputs %}
 3{% for key, value in scheduler_inputs.items() %}
 4{% if value %}
 5#SBATCH {{ key }}={{ value }}
 6{% endif %}
 7{% endfor %}
 8{% endif %}
 9
10echo "\n... Setting up shell environment ...\n"
11shopt -s expand_aliases
12module purge
13
14if test -e "/etc/profile"; then source "/etc/profile"; fi
15if test -e "$HOME/.bash_profile"; then source "$HOME/.bash_profile"; fi
16
17unset LANG; ulimit -s unlimited; ulimit -a -S; ulimit -a -H
18export LC_ALL="C"; export MKL_NUM_THREADS=1; export OMP_NUM_THREADS=1
19
20module load python {{ calculator|lower }}
21activate_env
22debug-slurm
23
24# run task
25{% if calculation_inputs %}
26autojob run --buffer=0.1 --time-source={{ task_inputs.task_script }} 'python {{ calculation_inputs.calculation_script }}'
27exit_code=$?
28autojob -vv harvest
29{% endif %}
30autojob clean
31log-job
32
33if [ "$exit_code" -eq 124 ]; then
34autojob -vv restart -S; else
35autojob -vv advance; fi

Lines 17-18 define common environment variables for running computational codes.

run.py.j2

run.sh.j2 is the default calculation script template for all autojob-defined Calculation subclasses. This Python script template loads the input Atoms, configures the ASE Calculator and Optimizer, calls the ccu function run_calculation(), and then runs any analyses.

 1from importlib import import_module
 2import json
 3import logging
 4from pathlib import Path
 5
 6import ase.io
 7from ase.calculators.calculator import get_calculator_class
 8from ccu.workflows.calculation import run_calculation
 9
10from autojob.plugins import get_analysis
11from autojob.tasks.calculation import CalculationInputs
12
13logging.basicConfig(level=logging.DEBUG)
14
15with Path("{{ settings.INPUTS_FILE }}").open(mode="r", encoding="utf-8") as file:
16    inputs = json.load(file)
17
18atoms = ase.io.read(inputs["task_inputs"]["atoms_filename"])
19
20# Configure calculator
21calc_inputs = CalculationInputs(**inputs["calculation_inputs"])
22calculator = get_calculator_class(calc_inputs.calculator)
23calc = calculator(**calc_inputs.calc_params)
24atoms.calc = calc
25
26# Configure optimizer
27if calc_inputs.optimizer:
28    *opt_mod_parts, opt_cls_name = calc_inputs.optimizer.split(".")
29    opt_cls = getattr(import_module(".".join(opt_mod_parts)), opt_cls_name)
30    opt = opt_cls(atoms, **calc_inputs.opt_params["init"])
31    opt_run_params = calc_inputs.opt_params["run"]
32else:
33    opt = None
34    opt_run_params = {}
35
36run_calculation(atoms, opt=opt, **opt_run_params, output={{ settings.OUTPUT_ATOMS_FILE }})
37
38# Run analyses
39if calc_inputs.analyses:
40    for name, (args, kwargs) in calc_inputs.analyses.items():
41        analysis = get_analysis(name)
42        _ = analysis(*args, **kwargs)

The run_calculation() function called on line 36 will result in the output trajectory being written.

vib.py.j2

vib.sh.j2 is the a calculation script template intended for use by Vibration tasks. This Python script template loads the input Atoms, configures the ASE Calculator and calls the ccu function run_vibration(), and then runs any analyses.

 1import json
 2import logging
 3from pathlib import Path
 4
 5from ase.calculators.calculator import get_calculator_class
 6import ase.io
 7from ccu.workflows.vibration import run_vibration
 8
 9from autojob.plugins import get_analysis
10from autojob.tasks.calculation import CalculationInputs
11
12logging.basicConfig(level=logging.DEBUG)
13
14with Path("{{ settings.INPUTS_FILE }}").open(
15    mode="r", encoding="utf-8"
16) as file:
17    inputs = json.load(file)
18
19atoms = ase.io.read(inputs["task_inputs"]["atoms_filename"])
20
21# Configure calculator
22calc_inputs = CalculationInputs(**inputs["calculation_inputs"])
23calculator = get_calculator_class(calc_inputs.calculator)
24calc = calculator(**calc_inputs.calc_params)
25atoms.calc = calc
26
27run_vibration(atoms, **inputs["vibration_inputs"])
28
29# Run analyses
30if calc_inputs.analyses:
31    for name, (args, kwargs) in calc_inputs.analyses.items():
32        analysis = get_analysis(name)
33        _ = analysis(*args, **kwargs)

The run_vibration() function called on line 27 will result in the output trajectory being written along with a JSON file containing the information needed to instantiate a VibrationsData object representing the results of the vibrational analysis.