Skip to content

Writing Flows

Simple Flows

Overview

A flow is a Python function decorated by @flow that contains a collection of jobs. Refer to the Workflow Syntax page for more information on the syntax of flows.

The Most Important Rules of Flows

The flow itself does not contain computationally intensive tasks. It simply calls other jobs (and/or flows) and defines the workflow logic. In general, essentially no operations can be done on the outputs of a job in a flow; they can only be passed as inputs to other jobs. This is because the output is a future whose value is not resolved until runtime. Note that doing result["atoms"], where result is the output of a job in the flow, returns a future and not the actual Atoms object. When passed as an input to a subsequent job, however, it will know to automatically resolve the Atoms object before running.

Production Example

A simple, representative flow can be found in quacc.recipes.vasp.mp.mp_metagga_relax_flow.

Note

All @flow-decorated functions distributed with quacc must allow for the individual job parameters and decorators to be updated by the user, which is typically done via the quacc.wflow_tools.customizers.customize_funcs function. Refer to the example above for details.

Dynamic Flows

quacc fully supports complex, dynamic flows where the number of jobs is not known a priori. In this case, a common pattern is the use of a subflow, defined with a @subflow decorator. A subflow is just like a flow, except the returned object is a list of job outputs.

Minimal Example

Let's do the following:

  1. Add two numbers (e.g. 1 + 2)
  2. Make a list of random length of the output of Step 1 (e.g. [3, 3, 3])
  3. Add a third number to each element of the list from Step 2 (e.g. [3 + 3, 3 + 3, 3 + 3])

In practice, we would want each of the two tasks to be their own compute job.

graph LR
A[Input] --> B(add) --> C(make list)
C(make list) --> D(add) --> G[Output]
C(make list) --> E(add) --> G[Output]
C(make list) --> F(add) --> G[Output]
from random import randint
from quacc import flow, job, subflow


@job
def add(a, b):
    return a + b


@job
def make_list(val):
    return [val] * randint(2, 5)


@subflow
def add_distributed(vals, c):
    outputs = []
    for val in vals:
        output = add(val, c)
        outputs.append(output)
    return outputs


@flow
def workflow(a, b, c):
    output1 = add(a, b)
    output2 = make_list(output1)
    output3 = add_distributed(output2, c)
    return output3

Production Example

A representative example of a complex flow can be found in quacc.recipes.emt.slabs.bulk_to_slabs_flow.

This flow is "dynamic" because the number of slabs is not known until the pre-requisite relaxation step is completed.

Note

To prevent code duplication, we put common recipes that are code-agnostic in quacc.recipes.common. These recipes are not meant to be run directly, but rather called by other recipes.

For instance, quacc.recipes.emt.slabs.bulk_to_slabs_flow and quacc.recipes.vasp.slabs.bulk_to_slabs_flow both call the same quacc.recipes.common.slabs.bulk_to_slabs_subflow.