Workflows#
One of immlib’s most powerful features is its tools for creating modular
scientific workflows. Designing workflows using immlib is
straightforward—managed mostly through decorators—and comes with
features like automatic caching and laziness.
Introductory Example#
To explain workflows, it’s easiest to consider a concrete example. Suppose we
need to write a utility that takes, as input, an array of values and that
produces a variety of statistics describing them. We might write such code
using immlib as follows:
import immlib as il, numpy as np
# The input data should be converted into an array:
@il.calc('data', 'n', lazy=False)
def check_data(data):
"""Ensures that the argument `data` is an array of numbers.
Parameters
----------
data : array-like
The input data for which statistics should be calculated. The data must
be a 1-dimensional vector of numbers, and it is converted into a NumPy
array by this calculation.
Outputs
-------
data : numpy.ndarray
A 1-dimensional NumPy array of numbers.
n : int
The number of data-points in `data`.
Raises
------
TypeError
If `data` is not a 1-dimensional vector of numbers.
"""
print('Checking data...')
data = il.to_array(data)
if not il.is_array(data, shape=(-1,), dtype=np.number):
raise TypeError("data must be a vector-like sequence of numbers")
return (data, len(data))
@il.calc('sum_of_squares', 'sum')
def calc_sums(data):
"""Calculates the sum and sum of squares of the data.
Outputs
-------
sum_of_squares : number
The sum of squares of the absolute values of the data.
sum : number
The sum of the values in data.
"""
print('Calculating sums...')
sum_of_sq = np.sum(np.abs(data)**2)
sum = np.sum(data)
return (sum_of_sq, sum)
@il.calc('mean', 'var', 'std')
def calc_mean_etc(sum, sum_of_squares, n):
"""Calculates the mean, variance, and standard deviation of the data.
Outputs
-------
mean : number
The mean of the data.
var : number
The variance of the data.
std : number
The standard deviation of the data.
"""
print('Calculating mean, etc...')
mean = sum / n
var = (sum_of_squares / n) - mean**2
std = np.sqrt(var)
return (mean, var, std)
# Make a plan for calculating these statistics:
stats_plan = il.plan(
check_step=check_data,
sums_step=calc_sums,
mean_step=calc_mean_etc)
Once we have set up the example code above, we can create an instance of our
stats plan called a plandict. When we create the plandict, the check_data
function will automatically run because it is marked as not lazy
(lazy=False):
# Make a plandict for a set of data:
stats = stats_plan(data=np.random.randn(100))
print("The type of stats is", type(stats))
Checking data...
The type of stats is <class 'immlib.plandict'>
The plandict has a key for each of the inputs and outputs in the entire
calculation plan:
sorted(stats.keys())
['data', 'mean', 'n', 'std', 'sum', 'sum_of_squares', 'var']
The values associated with these keys are calculated according to the
calculation plan, with the inputs for each calculation being derived from the
outputs of other calculations. In the plan stats_plan above, there is no
output named data, so it is considered an input of the plan.
stats_plan.inputs
{|'data'|}
When the values associated with keys are requested, they get calculated and cached. So in the example above, in which messages are printed as the values are calculated, the messages will be printed only the first time a key is requested.
# The message about the sums being calculated will appear immediately when this
# key is requested:
sum_sq = stats['sum_of_squares']
# But it won't appear again here:
print("The sum of squares is", stats['sum_of_squares'])
# Similarly, it won't appear again when we request the sum, because the sum was
# calculated in the same function as the sum of squares.
print("The sum is", stats['sum'])
Calculating sums...
The sum of squares is 95.4955119670381
The sum is -5.651956347797989
Downstream values such as mean and var aren’t calculated until they are
required:
print("The mean is", stats['mean'])
print("The standard deviation is", stats['std'])
Calculating mean, etc...
The mean is -0.05651956347797989
The standard deviation is 0.9755822152000515
Calculation Metadata#
The @calc decorator attaches metadata to the calculation functions by
creating a field calc. The calc object that manages the calculation’s
integration with any plans is found here.
calc_sums.calc
<immlib.calc at 0x7f6cdff3c4c0>
Tracking Documentation#
Calculations and plans also track the documentation for their inputs and
outputs. These can be accessed via the input_docs and output_docs fields.
print(calc_sums.calc.output_docs['sum'])
sum : number
The sum of the values in data.
print(check_data.calc.input_docs['data'])
data : array-like
The input data for which statistics should be calculated. The data must
be a 1-dimensional vector of numbers, and it is converted into a NumPy
array by this calculation.
Plans have a docstring that is automatically generated from their constituent calculations.
print(stats_plan.__doc__)
An ``immlib.plan`` object for a set of calculations.
This documentation was generated automatically from the docstrings
of the individual ``immlib.calc`` objects that make up this plan.
This plan contains the following calculations:
* ``check_step``: ``__main__.check_data``
Inputs: ``data``
Outputs: ``data``, ``n``
* ``sums_step``: ``__main__.calc_sums``
Inputs: ``data``
Outputs: ``sum_of_squares``, ``sum``
* ``mean_step``: ``__main__.calc_mean_etc``
Inputs: ``sum``, ``sum_of_squares``, ``n``
Outputs: ``mean``, ``var``, ``std``
Inputs
------
data
**``check_step``** input: ``data`` : array-like
The input data for which statistics should be calculated. The data must
be a 1-dimensional vector of numbers, and it is converted into a NumPy
array by this calculation.
Outputs
-------
data
**``check_step``** output: ``data`` : numpy.ndarray
A 1-dimensional NumPy array of numbers.
n
**``check_step``** output: ``n`` : int
The number of data-points in `data`.
sum_of_squares
**``sums_step``** output: ``sum_of_squares`` : number
The sum of squares of the absolute values of the data.
sum
**``sums_step``** output: ``sum`` : number
The sum of the values in data.
mean
**``mean_step``** output: ``mean`` : number
The mean of the data.
var
**``mean_step``** output: ``var`` : number
The variance of the data.
std
**``mean_step``** output: ``std`` : number
The standard deviation of the data.