Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: Add HowTo on writing workflows #4112

Merged
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Apply suggestions from code review
Co-authored-by: Sebastiaan Huber <mail@sphuber.net>
Co-authored-by: Carl Simon Adorf <carl.simon.adorf@gmail.com>
  • Loading branch information
3 people authored May 28, 2020
commit 86258e3a3eab6f6bd459040a7a9c5cb2729d5bfb
29 changes: 14 additions & 15 deletions docs/source/howto/workflows.rst
Original file line number Diff line number Diff line change
@@ -19,13 +19,13 @@ Here we present a brief introduction on how to write both workflow types.

.. note::

For more details on the concept of a workflow, and the different between a work function and a work chain, please see the corresponding :ref:`topics section<topics:workflows:concepts>`.
For more details on the concept of a workflow, and the difference between a work function and a work chain, please see the corresponding :ref:`topics section<topics:workflows:concepts>`.

Work function
-------------

A *work function* is a process function that calls one or more calculation functions and *returns* data that has been *created* by the calculation functions it has called.
mbercx marked this conversation as resolved.
Show resolved Hide resolved
Writing a work function that can be stored in the provenance simply involves writing a Python function that calls a set of calculation functions with the desired logic and decorating it with the ``@workfunction`` decorator:
Writing a work function, whose provenance is automatically stored, is as simple as writing a Python function and decorating it with the ``@workfunction`` decorator:

.. code-block:: python

@@ -42,21 +42,21 @@ Writing a work function that can be stored in the provenance simply involves wri

@workfunction
def add_and_multiply(x, y, z):
sum = add(x, y)
addition = add(x, y)
product = multiply(sum, z)
return product

result = add_and_multiply(Int(1), Int(2), Int(3))
result = add_multiply(Int(1), Int(2), Int(3))

It is important to reiterate here that the ``@workfunction``-decorated ``add_and_multiply`` function does not *create* any new data nodes.
The ``add`` and ``multiply`` calculation functions create the ``Int`` data nodes, all the work function does is *return* the results of the ``multiply`` calculation function.
It is important to reiterate here that the ``@workfunction``-decorated ``add_multiply`` function does not *create* any new data nodes.
The ``add()`` and ``multiply()`` calculation functions create the ``Int`` data nodes, all the work function does is *return* the results of the ``multiply()`` calculation function.
Moreover, both calculation and workflow functions can only accept and return data nodes, i.e. instances of classes that subclass the ``Data`` class.
mbercx marked this conversation as resolved.
Show resolved Hide resolved

Work chain
----------

When the workflow you want to run that is more expensive and complex, it is better to write a *work chain*.
mbercx marked this conversation as resolved.
Show resolved Hide resolved
Writing a work chain in AiiDA requires creating a class that inherits from the ``WorkChain`` class and defines the work chain.
Writing a work chain in AiiDA requires creating a class that inherits from the ``WorkChain`` class.
Below is an example of a work chain that takes three integers as inputs, multiplies the first two and then adds the third to obtain the final result:

.. code-block:: python
@@ -77,7 +77,7 @@ Below is an example of a work chain that takes three integers as inputs, multipl
@classmethod
def define(cls, spec):
"""Specify inputs and outputs."""
super(MultiplyAddWorkChain, cls).define(spec)
super().define(spec)
spec.input('x', valid_type=Int)
spec.input('y', valid_type=Int)
spec.input('z', valid_type=Int)
@@ -102,7 +102,7 @@ Below is an example of a work chain that takes three integers as inputs, multipl
inputs = {'x': self.ctx.multiple, 'y': self.inputs.z, 'code': self.inputs.code}
future = self.submit(ArithmeticAddCalculation, **inputs)

return ToContext({'addition': future})
return ToContext(addition=future)

def validate_result(self):

@@ -122,7 +122,7 @@ Let's go over the methods of the ``MultiplyAddWorkChain`` one by one:
@classmethod
def define(cls, spec):
"""Specify inputs and outputs."""
super(MultiplyAddWorkChain, cls).define(spec)
super().define(spec)
spec.input('x', valid_type=Int)
spec.input('y', valid_type=Int)
spec.input('z', valid_type=Int)
@@ -139,21 +139,21 @@ Let's go over the methods of the ``MultiplyAddWorkChain`` one by one:

The most important method to implement for every work chain is the ``define()`` method.
This class method must always start by calling the ``define()`` method of its parent class.
Next, the ``define()`` method allows the developer to define the characteristics of the work chain, which are contained in the work chain ``spec``:
Next, the ``define()`` method should be used to define the specifications of the work chain, which are contained in the work chain ``spec``:

* the **inputs**, specified using the ``spec.input()`` method.
The first argument of the ``input()`` method is a string that specifies the label of the input, e.g. ``'x'``.
The ``valid_type`` keyword argument allows you to specify the required node type of the input.
Other keyword arguments allow the developer to set a default for the input, or indicate that an input should not be stored in the database, see :ref:`the process topics section <topics:processes:usage:spec>` for more details.
* the **outline** or logic of the workflow, specified using the ``spec.outline()`` method.
The outline of the workflow is constructed from the methods of the ``WorkChain`` class.
For the ``MultiplyAddWorkChain``, the outline is a simple linear sequence of steps, but it's possible to define more complex workflows as well.
For the ``MultiplyAddWorkChain``, the outline is a simple linear sequence of steps, but it's possible to include actual logic, directly in the outline, in order to define more complex workflows as well.
See the :ref:`work chain outline section <topics:workflows:usage:workchains:define_outline>` for more details.
* the **outputs**, specified using the ``spec.output()`` method.
This method is very similar in its usage to the ``input()`` method.
* the **exit codes** of the work chain, specified using the ``spec.exit_code()`` method.
Exit codes are used to clearly communicate known failure modes of the work chain to the user.
The first and second arguments define the ``exit_status`` of the work chain in case of failure (``400``) and the string that the developer can use to reference to the exit code (``ERROR_NEGATIVE_NUMBER``).
The first and second arguments define the ``exit_status`` of the work chain in case of failure (``400``) and the string that the developer can use to reference the exit code (``ERROR_NEGATIVE_NUMBER``).
A descriptive exit message can be provided using the ``message`` keyword argument.
For the ``MultiplyAddWorkChain``, we demand that the final result is not a negative number, which is checked in the ``validate_result`` step of the outline.

@@ -179,7 +179,7 @@ To store the result of this function and use it in the next step of the outline,
inputs = {'x': self.ctx.multiple, 'y': self.inputs.z, 'code': self.inputs.code}
calcjob_node = self.submit(ArithmeticAddCalculation, **inputs)

return ToContext({'addition': calcjob_node})
return ToContext(addition=calcjob_node})

The ``add`` method is the second step in the outline of the work chain.
As this step uses the ``ArithmeticAddCalculation`` calculation job, we start by setting up the inputs for this ``CalcJob`` in a dictionary.
@@ -213,7 +213,6 @@ The final step in the outline is to pass the result to the outputs of the work c
The first argument (``'result'``) specifies the label of the output, which corresponds to the label provided to the spec in the ``define()`` method.
The second argument is the result of the work chain, extracted from the ``Int`` node stored in the context under the ``'addition'`` key.

Hopefully you now have a basic understanding of how to implement workflows in AiiDA.
For a more complete discussion on workflows and their usage, please read :ref:`the corresponding topics section<topics:workflows:usage>`.

.. _how-to:workflows:run: