How To Write a Template

autojob uses Jinja2 to template task and calculation scripts. For details on syntax, please see the Jinja2 documentation.

How to Structure Script Templates

Although autojob does not place any restrictions on the structure of task and calculation scripts, we have found that the following structure for task scripts works quite well:

  1. Specify scheduler resources

  2. Configure environment: load modules, activate virtual environment, define environment variables

  3. Execute task: CLI command or by calling calculation script, autojob run

  4. Harvest task results: autojob harvest

  5. Clean up directory: autojob clean

  6. Advance workflow: autojob restart and autojob advance

For calculation scripts, the only requirement is that upon successful execution, calculation scripts should write an output trajectory file to SETTINGS.OUTPUT_ATOMS_FILE. For example,

# my_run.py.j2

from ase.calculators.emt import EMT
import ase.io

atoms = ase.io.read({{ task_inputs.atoms_filename }})
atoms.calc = EMT()
atoms.calc.calculate(atoms, properties=["energy"])
atoms.write({{ settings.OUTPUT_ATOMS_FILE }})

When this template is rendered, {{ task_inputs.atoms_filename }} will be replaced with the input atoms filename of the task whose inputs are being written. Similarly, {{ settings.OUTPUT_ATOMS_FILE }} will be replaced with the default output atoms filename.

How to Access Task Attributes

In autojob-defined tasks, the InputWriter protocol is implemented such that task and calculation script templates are passed each attribute of the task as a variable to the Jinja context. That is, the task_metadata can be accessed in templates via the variable task_metadata, and the name of the input atoms filename can be accessed in templates as task_inputs.atoms_filename. In addition, script templates are passed the values of autojob settings so that the name of the default archive file can be accessed as settings.ARCHIVE_FILE.

#!/bin/bash
# my_task_script.sh.j2

{% for x in task_inputs.items() %}
echo {{ x }}
{% endfor %}

{% for x in task_metadata.items() %}
echo {{ x }}
{% endfor %}

{% for x in settings.items() %}
echo {{ x }}
{% endfor %}

cp {{ task_inputs.atoms_filename }} {{ settings.OUTPUT_ATOMS_FILE }}
autojob harvest

The task script template above, my_task_script.sh.j2, will generate as task script that will print out the task inputs, task metadata, and settings active when the task script is written to a file. In addition, the task script will copy the input atoms file to a filename matching the default output atoms filename and then run autojob harvest.

How to Specify SLURM Parameters

autojob specially formats the keys and values of SchedulerInputs fields for use in task scripts. SLURM submission scripts must specify resource requirements as comments that precede any executable code. To do write task scripts that can be submitted with SLURM, try the following:

#!/bin/bash
# my_task_script.sh.j2

{% if scheduler_inputs %}
{% for key, value in scheduler_inputs.items() %}
{% if value %}
#SBATCH {{ key }}={{ value }}
{% endif %}
{% endfor %}
{% endif %}

How to Write Auto-Restarting Templates

autojob run is a CLI utility that can be used to run timed commands within task scripts.

autojob run --buffer=0.1 --time-source=run.sh 'python run.py'

When executed within a task script in which scheduler inputs are specified as outlined in How to Specify SLURM Parameters, the snippet above will execute the command python run.py with a time limit. The time limit will be determined by reading the value of the --time SLURM option set in the run.sh script. Specifically, autojob run will allow the command to run for 10% of the total time specified by --time. If the argument to --buffer is greater than 1, then it will be interpreted as the number of seconds to leave at the end of the total time. To take advantage of templating, autojob run should be incorporated into task script templates like so:

autojob run --buffer=0.1 --time-source={{ task_inputs.task_script }} 'python {{ calculation_inputs.calculation_script }}'

To set the time limit by directly templating the scheduler input value, use this snippet:

autojob run --buffer=0.1 --time={{ scheduler_inputs.time }} 'python {{ calculation_inputs.calculation_script }}'

autojob run exits with code 124 if the time limit was reached. That means that task scripts can check this exit code and use it determine whether to restart or advance the task. For example,

#!/bin/bash
# my_task_script.sh.j2

...
autojob run --buffer=0.1 --time={{ scheduler_inputs.time }} 'python {{ calculation_inputs.calculation_script }}'
exit_code=$?
...
if [ $exit_code = 124 ]; then
autojob restart;  else;
autojob advance; fi
...

An example of this logic can also be found in the calc.sh.j2 template.

Tip

Type autojob run -h into the terminal for more complete help.