Skip to content

API Reference

context

This module contains functions that manipulate the context of the graph, i.e., the values assigned to its nodes.

clear_values(graph, *args)

Clear (i.e. erase) values in the graph nodes.

Parameters

graph : grapes Graph The graph whose values are to be cleared. *args : hashables (typically strings) Names of nodes whose values are to be cleared. If no names are given, all values are cleared.

Notes

Frozen nodes are not cleared.

Source code in grapes/context.py
def clear_values(graph, *args):
    """
    Clear (i.e. erase) values in the graph nodes.

    Parameters
    ----------
    graph : grapes Graph
        The graph whose values are to be cleared.
    *args : hashables (typically strings)
        Names of nodes whose values are to be cleared. If no names are given, all values are cleared.

    Notes
    -----
    Frozen nodes are not cleared.
    """
    if len(args) == 0:  # Interpret as "Clear everything"
        nodes_to_clear = graph.nodes
    else:
        nodes_to_clear = args & graph.nodes  # Intersection

    for node in nodes_to_clear:
        if get_is_frozen(graph, node):
            continue
        unset_value(graph, node)

get_dict_of_values(graph, list_of_keys)

Get dictionary of key:value pairs corresponding to list of keys.

Parameters

graph : grapes Graph The graph from which to get the values. list_of_keys : list of hashables (typically strings) List of names of nodes whose values are required.

Returns

dict Dictionary whose keys are the elements of list_of_keys and whose values are the corresponding node values.

Source code in grapes/context.py
def get_dict_of_values(graph, list_of_keys):
    """
    Get dictionary of key:value pairs corresponding to list of keys.

    Parameters
    ----------
    graph : grapes Graph
        The graph from which to get the values.
    list_of_keys : list of hashables (typically strings)
        List of names of nodes whose values are required.

    Returns
    -------
    dict
        Dictionary whose keys are the elements of list_of_keys and whose values are the corresponding node values.
    """
    return {key: get_value(graph, key) for key in list_of_keys}

get_internal_context(graph, exclude_recipes=False)

Get the internal context.

Parameters

graph : grapes Graph The graph whose context is to be retrieved. exclude_recipes : bool Whether to exclude recipes from the returned dictionary or keep them, default: False.

Returns

dict Dictionary with the current values of the graph nodes.

Source code in grapes/context.py
def get_internal_context(graph, exclude_recipes=False):
    """
    Get the internal context.

    Parameters
    ----------
    graph : grapes Graph
        The graph whose context is to be retrieved.
    exclude_recipes : bool
        Whether to exclude recipes from the returned dictionary or keep them, default: False.

    Returns
    -------
    dict
        Dictionary with the current values of the graph nodes.
    """
    if exclude_recipes:
        return {
            key: get_value(graph, key)
            for key in graph.nodes
            if (get_has_value(graph, key) and not get_is_recipe(graph, key))
        }
    else:
        return {
            key: get_value(graph, key)
            for key in graph.nodes
            if get_has_value(graph, key)
        }

get_kwargs_values(graph, dictionary)

Get values from the graph, using a dictionary that works like function kwargs.

Parameters

graph : grapes Graph The graph from which to get the values. dictionary : dict Dictionary whose keys are to be interpreted as keys for function kwargs, while values in dictionary are node names.

Returns

dict A dict with the same keys of the input dictionary, but with values replaced by the values of the nodes.

Source code in grapes/context.py
def get_kwargs_values(graph, dictionary):
    """
    Get values from the graph, using a dictionary that works like function kwargs.

    Parameters
    ----------
    graph : grapes Graph
        The graph from which to get the values.
    dictionary : dict
        Dictionary whose keys are to be interpreted as keys for function kwargs, while values in dictionary are node names.

    Returns
    -------
    dict
        A dict with the same keys of the input dictionary, but with values replaced by the values of the nodes.
    """
    return {key: get_value(graph, value) for key, value in dictionary.items()}

get_list_of_values(graph, list_of_keys)

Get list of values corresponding to list of keys.

Parameters

graph : grapes Graph The graph from which to get the values. list_of_keys : list of hashables (typically strings) List of names of nodes whose values are required.

Returns

list List like list_of_keys which contains values of nodes.

Source code in grapes/context.py
def get_list_of_values(graph, list_of_keys):
    """
    Get list of values corresponding to list of keys.

    Parameters
    ----------
    graph : grapes Graph
        The graph from which to get the values.
    list_of_keys : list of hashables (typically strings)
        List of names of nodes whose values are required.

    Returns
    -------
    list
        List like list_of_keys which contains values of nodes.
    """
    res = []
    for key in list_of_keys:
        res.append(get_value(graph, key))
    return res

set_internal_context(graph, dictionary)

Clear all values and then set a new internal context with a dictionary.

Parameters

graph : grapes Graph The graph to update. dictionary : dict Dictionary with the new values.

Notes

Only keys that are already nodes in the graph are considered. Frozen nodes are not cleared.

Source code in grapes/context.py
def set_internal_context(graph, dictionary):
    """
    Clear all values and then set a new internal context with a dictionary.

    Parameters
    ----------
    graph : grapes Graph
        The graph to update.
    dictionary : dict
        Dictionary with the new values.

    Notes
    -----
    Only keys that are already nodes in the graph are considered.
    Frozen nodes are not cleared.
    """
    clear_values(graph)
    update_internal_context(graph, dictionary)

update_internal_context(graph, dictionary)

Update the internal context with a dictionary.

Parameters

graph : grapes Graph The graph to update. dictionary : dict Dictionary with the new values. Keys are node names, values are the values to assign to those nodes.

Notes

Only keys that are already nodes in the graph are considered.

Source code in grapes/context.py
def update_internal_context(graph, dictionary):
    """
    Update the internal context with a dictionary.

    Parameters
    ----------
    graph : grapes Graph
        The graph to update.
    dictionary : dict
        Dictionary with the new values. Keys are node names, values are the values to assign to those nodes.

    Notes
    -----
    Only keys that are already nodes in the graph are considered.
    """
    for key, value in dictionary.items():
        # Accept dictionaries with more keys than needed
        if key in graph.nodes:
            set_value(graph, key, value)

update_recipes_from_module(graph, module)

Update the internal context to assign to recipes the functions with the same name taken from a module.

Parameters

graph : grapes Graph The graph to update. module : module Module from which to load the functions.

Notes

Only functions whose name matches a recipe in the graph are loaded.

Source code in grapes/context.py
def update_recipes_from_module(graph, module):
    """
    Update the internal context to assign to recipes the functions with the same name taken from a module.

    Parameters
    ----------
    graph : grapes Graph
        The graph to update.
    module : module
        Module from which to load the functions.

    Notes
    -----
    Only functions whose name matches a recipe in the graph are loaded.
    """
    context = {
        name: func
        for name, func in inspect.getmembers(module, predicate=inspect.isfunction)
    }
    update_internal_context(graph, context)

core

This module is the core of the grapes package. It contains the Graph class.

Graph

Class that represents a graph of nodes.

Source code in grapes/core.py
class Graph:
    """
    Class that represents a graph of nodes.
    """

    def __init__(self, nx_digraph=None):
        """
        Initialize a Graph object.

        Parameters
        ----------
        nx_digraph : networkx.DiGraph, optional
            A pre-existing directed graph to initialize from. If None, an empty graph is created. Default is None.
        """
        # Internally, we handle a nx_digraph
        if nx_digraph == None:
            self._nxdg = nx.DiGraph()
        else:
            self._nxdg = nx_digraph
        # Alias for easy access
        self.nodes = self._nxdg.nodes

    def __getitem__(self, node):
        """
        Get the value of a node with [].

        Parameters
        ----------
        node : hashable (typically string)
            Name of the node whose value is to be retrieved.

        Returns
        -------
        value : any
            Value of the node.

        Raises
        ------
        ValueError
            If the node has no value.
        KeyError
            If the node does not exist.
        """
        return get_value(self, node)

    def __setitem__(self, node, value):
        """
        Set the value of a node with [].

        Parameters
        ----------
        node : hashable (typically string)
            Name of the node whose value is to be set.
        value : any
            Value to set.

        Raises
        ------
        KeyError
            If the node does not exist.
        """
        set_value(self, node, value)

    def __eq__(self, other):
        """
        Equality check between graphs based on all members.

        Parameters
        ----------
        other : Graph
            Graph to compare with.

        Returns
        -------
        bool
            True if the graphs are equal, False otherwise.

        Notes
        -----
        Two graphs are equal if they are isomorphic and all their node and edge attributes are equal (including values).
        """
        return isinstance(other, self.__class__) and nx.is_isomorphic(
            self._nxdg, other._nxdg, dict.__eq__, dict.__eq__
        )

__eq__(other)

Equality check between graphs based on all members.

Parameters

other : Graph Graph to compare with.

Returns

bool True if the graphs are equal, False otherwise.

Notes

Two graphs are equal if they are isomorphic and all their node and edge attributes are equal (including values).

Source code in grapes/core.py
def __eq__(self, other):
    """
    Equality check between graphs based on all members.

    Parameters
    ----------
    other : Graph
        Graph to compare with.

    Returns
    -------
    bool
        True if the graphs are equal, False otherwise.

    Notes
    -----
    Two graphs are equal if they are isomorphic and all their node and edge attributes are equal (including values).
    """
    return isinstance(other, self.__class__) and nx.is_isomorphic(
        self._nxdg, other._nxdg, dict.__eq__, dict.__eq__
    )

__getitem__(node)

Get the value of a node with [].

Parameters

node : hashable (typically string) Name of the node whose value is to be retrieved.

Returns

value : any Value of the node.

Raises

ValueError If the node has no value. KeyError If the node does not exist.

Source code in grapes/core.py
def __getitem__(self, node):
    """
    Get the value of a node with [].

    Parameters
    ----------
    node : hashable (typically string)
        Name of the node whose value is to be retrieved.

    Returns
    -------
    value : any
        Value of the node.

    Raises
    ------
    ValueError
        If the node has no value.
    KeyError
        If the node does not exist.
    """
    return get_value(self, node)

__init__(nx_digraph=None)

Initialize a Graph object.

Parameters

nx_digraph : networkx.DiGraph, optional A pre-existing directed graph to initialize from. If None, an empty graph is created. Default is None.

Source code in grapes/core.py
def __init__(self, nx_digraph=None):
    """
    Initialize a Graph object.

    Parameters
    ----------
    nx_digraph : networkx.DiGraph, optional
        A pre-existing directed graph to initialize from. If None, an empty graph is created. Default is None.
    """
    # Internally, we handle a nx_digraph
    if nx_digraph == None:
        self._nxdg = nx.DiGraph()
    else:
        self._nxdg = nx_digraph
    # Alias for easy access
    self.nodes = self._nxdg.nodes

__setitem__(node, value)

Set the value of a node with [].

Parameters

node : hashable (typically string) Name of the node whose value is to be set. value : any Value to set.

Raises

KeyError If the node does not exist.

Source code in grapes/core.py
def __setitem__(self, node, value):
    """
    Set the value of a node with [].

    Parameters
    ----------
    node : hashable (typically string)
        Name of the node whose value is to be set.
    value : any
        Value to set.

    Raises
    ------
    KeyError
        If the node does not exist.
    """
    set_value(self, node, value)

design

This module contains functions to design the graph.

add_multiple_conditional(graph, name, conditions, possibilities)

Interface to add a multiple conditional to the graph. A multiple conditional is a node that takes the value of one of its possibilities depending on which of its condition nodes evaluates to True.

Parameters

graph : grapes Graph The graph to which to add the conditional. name : hashable (typically string) Name of the conditional node to add. conditions : list of hashables (typically strings) Names of the condition nodes to add. possibilities : list of hashables (typically strings) Names of the nodes to add as possibilities.

Raises

ValueError If the number of possibilities is not equal to the number of conditions or to the number of conditions plus one (to allow for a default possibility).

Source code in grapes/design.py
def add_multiple_conditional(graph, name, conditions, possibilities):
    """
    Interface to add a multiple conditional to the graph.
    A multiple conditional is a node that takes the value of one of its possibilities depending on which of its condition nodes evaluates to True.

    Parameters
    ----------
    graph : grapes Graph
        The graph to which to add the conditional.
    name : hashable (typically string)
        Name of the conditional node to add.
    conditions : list of hashables (typically strings)
        Names of the condition nodes to add.
    possibilities : list of hashables (typically strings)
        Names of the nodes to add as possibilities.

    Raises
    ------
    ValueError
        If the number of possibilities is not equal to the number of conditions or to the number of conditions plus one (to allow for a default possibility).
    """
    if (
        len(possibilities) != len(conditions)
        and len(possibilities) != len(conditions) + 1
    ):
        raise ValueError(
            "The number of possibilities must be equal to the number of conditions or to the number of conditions plus one (to allow for a default possibility)"
        )
    # Add all nodes and connect all edges
    # Avoid adding existing node so as not to overwrite attributes
    if name not in graph.nodes:
        graph._nxdg.add_node(name, **starting_node_properties)
    for node in conditions + possibilities:
        # Avoid adding existing dependencies so as not to overwrite attributes
        if node not in graph.nodes:
            graph._nxdg.add_node(node, **starting_node_properties)
        graph._nxdg.add_edge(node, name)

    # Specify that this node is a conditional
    set_type(graph, name, "conditional")

    # Add conditions name to the list of conditions of the conditional
    set_conditions(graph, name, conditions)

    # Add possibilities to the list of possibilities of the conditional
    set_possibilities(graph, name, possibilities)

add_simple_conditional(graph, name, condition, value_true, value_false)

Interface to add a conditional to the graph. A conditional is a node that takes the value of another node (one of the possibilities) depending on the boolean value of a condition node.

Parameters

graph : grapes Graph The graph to which to add the conditional. name : hashable (typically string) Name of the conditional node to add. condition : hashable (typically string) Name of the condition node to add. The condition node controls which of the possibility nodes passes its value to the conditional. value_true : hashable (typically string) Name of the node to add as possibility if the condition is true. value_false : hashable (typically string) Name of the node to add as possibility if the condition is false.

Source code in grapes/design.py
def add_simple_conditional(graph, name, condition, value_true, value_false):
    """
    Interface to add a conditional to the graph.
    A conditional is a node that takes the value of another node (one of the possibilities) depending on the boolean value of a condition node.

    Parameters
    ----------
    graph : grapes Graph
        The graph to which to add the conditional.
    name : hashable (typically string)
        Name of the conditional node to add.
    condition : hashable (typically string)
        Name of the condition node to add.
        The condition node controls which of the possibility nodes passes its value to the conditional.
    value_true : hashable (typically string)
        Name of the node to add as possibility if the condition is true.
    value_false : hashable (typically string)
        Name of the node to add as possibility if the condition is false.
    """
    add_multiple_conditional(
        graph, name, conditions=[condition], possibilities=[value_true, value_false]
    )

add_step(graph, name, recipe=None, *args, **kwargs)

Interface to add a node to the graph, with all its dependencies.

Parameters

graph : grapes Graph The graph to which the step is to be added. name : hashable (typically string) Name of the node to add. recipe : hashable (typically string), optional Name of the recipe node to add, if any. Default is None. args : hashables (typically strings) Names of nodes to add as positional dependencies. *kwargs : hashables (typically strings) Names of nodes to add as keyword dependencies. Keys in the dicts are the keywords to be used when calling the recipe.

Raises

ValueError If a node with dependencies is added without a recipe.

Source code in grapes/design.py
def add_step(graph, name, recipe=None, *args, **kwargs):
    """
    Interface to add a node to the graph, with all its dependencies.

    Parameters
    ----------
    graph : grapes Graph
        The graph to which the step is to be added.
    name : hashable (typically string)
        Name of the node to add.
    recipe : hashable (typically string), optional
        Name of the recipe node to add, if any. Default is None.
    *args : hashables (typically strings)
        Names of nodes to add as positional dependencies.
    **kwargs : hashables (typically strings)
        Names of nodes to add as keyword dependencies. Keys in the dicts are the keywords to be used when calling the recipe.

    Raises
    ------
    ValueError
        If a node with dependencies is added without a recipe.
    """
    # Check that if a node has dependencies, it also has a recipe
    if recipe is None and (len(args) > 0 or len(kwargs.keys()) > 0):
        raise ValueError("Cannot add node with dependencies without a recipe")

    elif recipe is None:  # Accept nodes with no dependencies
        # Avoid adding existing node so as not to overwrite attributes
        if name not in graph.nodes:
            graph._nxdg.add_node(name, **starting_node_properties)

    else:  # Standard case
        # Add the node
        # Avoid adding existing node so as not to overwrite attributes
        if name not in graph.nodes:
            graph._nxdg.add_node(name, **starting_node_properties)
        # Set attributes
        # Note: This could be done in the constructor, but doing it separately adds flexibility
        # Indeed, we might want to change how attributes work, and we can do it by modifying setters
        set_recipe(graph, name, recipe)
        set_args(graph, name, args)
        set_kwargs(graph, name, kwargs)

        # Add and connect the recipe
        # Avoid adding existing recipe so as not to overwrite attributes
        if recipe not in graph.nodes:
            graph._nxdg.add_node(recipe, **starting_node_properties)
        set_is_recipe(graph, recipe, True)
        # Note: adding argument to the edges is elegant but impractical.
        # If relations were defined through edges attributes rather than stored inside nodes,
        # retrieving them would require iterating through all edges and selecting the ones with the right attributes.
        # Although feasible, this is much slower than simply accessing node attributes.
        graph._nxdg.add_edge(recipe, name)

        # Add and connect the other dependencies
        for arg in args:
            # Avoid adding existing dependencies so as not to overwrite attributes
            if arg not in graph.nodes:
                graph._nxdg.add_node(arg, **starting_node_properties)
            graph._nxdg.add_edge(arg, name)
        for value in kwargs.values():
            # Avoid adding existing dependencies so as not to overwrite attributes
            if value not in graph.nodes:
                graph._nxdg.add_node(value, **starting_node_properties)
            graph._nxdg.add_edge(value, name)

add_step_quick(graph, name, recipe)

Interface to quickly add a step by passing a name and a function.

The recipe node takes the name of the passed function. Dependency nodes are built from the args and kwonlyargs of the passed function.

Parameters

graph : grapes Graph The graph to which to add the step. name : hashable (typically string) Name of the node to add. recipe : function Function to be used as recipe.

Raises

TypeError If the passed recipe is not a function. ValueError If the passed function has varargs or varkwargs, which are not supported.

Notes

If the function is unnamed (i.e. a lambda), it is automatically renamed to "recipe_for_" + name.

Source code in grapes/design.py
def add_step_quick(graph, name, recipe):
    """
    Interface to quickly add a step by passing a name and a function.

    The recipe node takes the name of the passed function.
    Dependency nodes are built from the args and kwonlyargs of the passed function.

    Parameters
    ----------
    graph : grapes Graph
        The graph to which to add the step.
    name : hashable (typically string)
        Name of the node to add.
    recipe : function
        Function to be used as recipe.

    Raises
    ------
    TypeError
        If the passed recipe is not a function.
    ValueError
        If the passed function has varargs or varkwargs, which are not supported.

    Notes
    -----
    If the function is unnamed (i.e. a lambda), it is automatically renamed to "recipe_for_" + name.
    """
    # Check that the passed recipe is a valid function
    if not inspect.isfunction(recipe):
        raise TypeError(
            "The passed recipe should be a function, but it is a " + str(type(recipe))
        )
    argspec = inspect.getfullargspec(recipe)
    # varargs and varkw are not supported because add_step_quick needs parameter names to build nodes
    if argspec.varargs is not None or argspec.varkw is not None:
        raise ValueError(
            "Functions with varargs or varkwargs are not supported by add_step_quick because there would be no way to name dependency nodes"
        )

    # Get function name and parameters
    recipe_name = recipe.__name__
    # Lambdas are all automatically named "<lambda>" so we change this
    if recipe_name == "<lambda>":
        recipe_name = "recipe_for_" + name
    args = argspec.args
    kwargs_list = argspec.kwonlyargs
    # Build a dictionary with identical keys and values so that recipe is called all the keys are used are kwargs
    kwargs = {kw: kw for kw in kwargs_list}
    # Add the step: this will create nodes for name, recipe_name and all elements of args and kwargs_list
    add_step(graph, name, recipe_name, *args, **kwargs)
    # Directly set the value of recipe_name to recipe
    set_value(graph, recipe_name, recipe)

edit_step(graph, name, recipe=None, *args, **kwargs)

Interface to edit an existing node, changing its predecessors.

Parameters

graph : grapes Graph The graph to which to add the step. name : hashable (typically string) Name of the node to edit. recipe : hashable (typically string), optional Name of the recipe node to add, if any. Default is None. args : hashables (typically strings) Names of nodes to add as positional dependencies. *kwargs : hashables (typically strings) Names of nodes to add as keyword dependencies. Keys in the dicts are the keywords to be used when calling the recipe.

Raises

KeyError If the node does not exist.

Source code in grapes/design.py
def edit_step(graph, name, recipe=None, *args, **kwargs):
    """
    Interface to edit an existing node, changing its predecessors.

    Parameters
    ----------
    graph : grapes Graph
        The graph to which to add the step.
    name : hashable (typically string)
        Name of the node to edit.
    recipe : hashable (typically string), optional
        Name of the recipe node to add, if any. Default is None.
    *args : hashables (typically strings)
        Names of nodes to add as positional dependencies.
    **kwargs : hashables (typically strings)
        Names of nodes to add as keyword dependencies. Keys in the dicts are the keywords to be used when calling the recipe.

    Raises
    ------
    KeyError
        If the node does not exist.
    """
    if name not in graph.nodes:
        raise KeyError("Cannot edit non-existent node " + name)

    # Store old attributes
    was_recipe = get_is_recipe(graph, name)
    was_frozen = get_is_frozen(graph, name)
    had_value = get_has_value(graph, name)
    old_value = None
    if had_value:
        old_value = get_value(graph, name)

    # Remove in-edges from the node because we need to replace them
    # use of list() is to make a copy because in_edges() returns a view
    graph._nxdg.remove_edges_from(list(graph._nxdg.in_edges(name)))
    # Readd the step. This should not break anything
    add_step(graph, name, recipe, *args, **kwargs)

    # Readd attributes
    # Readding out-edges is not needed because we never removed them
    set_is_recipe(graph, name, was_recipe)
    set_is_frozen(graph, name, was_frozen)
    set_has_value(graph, name, had_value)
    if had_value:
        set_value(graph, name, old_value)

finalize_definition(graph)

Perform operations that should typically be done after the definition of a graph is completed.

Currently, this freezes all values, because it is assumed that values given during definition are to be frozen. It also marks dependencies of recipes as recipes themselves.

Parameters

graph : grapes Graph The graph to finalize.

Source code in grapes/design.py
def finalize_definition(graph):
    """
    Perform operations that should typically be done after the definition of a graph is completed.

    Currently, this freezes all values, because it is assumed that values given during definition are to be frozen.
    It also marks dependencies of recipes as recipes themselves.

    Parameters
    ----------
    graph : grapes Graph
        The graph to finalize.
    """
    make_recipe_dependencies_also_recipes(graph)
    compute_topological_generation_indexes(graph)
    freeze(graph)

remove_step(graph, name)

Interface to remove an existing node, without changing anything else.

Parameters

graph : grapes Graph The graph from which to remove the step. name : hashable (typically string) Name of the node to remove.

Raises

KeyError If the node does not exist.

Source code in grapes/design.py
def remove_step(graph, name):
    """
    Interface to remove an existing node, without changing anything else.

    Parameters
    ----------
    graph : grapes Graph
        The graph from which to remove the step.
    name : hashable (typically string)
        Name of the node to remove.

    Raises
    ------
    KeyError
        If the node does not exist.
    """
    if name not in graph.nodes:
        raise KeyError("Cannot edit non-existent node " + name)
    graph._nxdg.remove_node(name)

evaluate

This module contains functions to evaluate the graph, i.e. call the recipes or assign conditionals to compute values of nodes. Usually, it is unnecessary to call these functions directly, as the more convenient interface is in the util module.

evaluate_conditional(graph, conditional, continue_on_fail=False)

Evaluate a conditional node in the graph.

Parameters

graph : grapes Graph The graph containing the conditional node. conditional : hashable (typically string) The name of the conditional node to evaluate. continue_on_fail : bool, optional If True, continue evaluation even if an error occurs. Default is False.

Raises

ValueError If no condition is true or evaluation fails and continue_on_fail is False.

Source code in grapes/evaluate.py
def evaluate_conditional(graph, conditional, continue_on_fail=False):
    """
    Evaluate a conditional node in the graph.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the conditional node.
    conditional : hashable (typically string)
        The name of the conditional node to evaluate.
    continue_on_fail : bool, optional
        If True, continue evaluation even if an error occurs. Default is False.

    Raises
    ------
    ValueError
        If no condition is true or evaluation fails and continue_on_fail is False.
    """
    # Check if it already has a value
    if get_has_value(graph, conditional):
        get_value(graph, conditional)
        return
    # If not, check if one of the conditions already has a true value
    for index, condition in enumerate(get_conditions(graph, conditional)):
        if get_has_value(graph, condition) and get_value(graph, condition):
            break
    else:
        # Happens only if loop is never broken
        # In this case, evaluate the conditions until one is found true
        for index, condition in enumerate(get_conditions(graph, conditional)):
            evaluate_target(graph, condition, continue_on_fail)
            if get_has_value(graph, condition) and get_value(graph, condition):
                break
            elif not get_has_value(graph, condition):
                # Computing failed
                if continue_on_fail:
                    # Do nothing, we want to keep going
                    return
                else:
                    raise ValueError("Node " + condition + " could not be computed")
        else:  # Happens if loop is never broken, i.e. when no conditions are true
            index = -1

    # Actual computation happens here
    possibility = get_possibilities(graph, conditional)[index]
    try:
        evaluate_target(graph, possibility, continue_on_fail)
        res = get_value(graph, possibility)
    except:
        if continue_on_fail:
            # Do nothing, we want to keep going
            return
        else:
            raise ValueError("Node " + possibility + " could not be computed")
    # Save results and release
    set_value(graph, conditional, res)

evaluate_standard(graph, node, continue_on_fail=False)

Evaluate a standard node in the graph.

Parameters

graph : grapes Graph The graph containing the node to evaluate. node : hashable (typically string) The name of the standard node to evaluate. continue_on_fail : bool, optional If True, continue evaluation even if an error occurs. Default is False.

Raises

Exception If evaluation fails and continue_on_fail is False.

Source code in grapes/evaluate.py
def evaluate_standard(graph, node, continue_on_fail=False):
    """
    Evaluate a standard node in the graph.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node to evaluate.
    node : hashable (typically string)
        The name of the standard node to evaluate.
    continue_on_fail : bool, optional
        If True, continue evaluation even if an error occurs. Default is False.

    Raises
    ------
    Exception
        If evaluation fails and continue_on_fail is False.
    """
    # Check if it already has a value
    if get_has_value(graph, node):
        get_value(graph, node)
        return
    # If not, evaluate all arguments
    for dependency_name in graph._nxdg.predecessors(node):
        evaluate_target(graph, dependency_name, continue_on_fail)

    # Actual computation happens here
    try:
        recipe = get_recipe(graph, node)
        func = get_value(graph, recipe)
        res = func(
            *get_list_of_values(graph, get_args(graph, node)),
            **get_kwargs_values(graph, get_kwargs(graph, node))
        )
    except Exception as e:
        if continue_on_fail:
            # Do nothing, we want to keep going
            return
        else:
            if len(e.args) > 0:
                e.args = ("While evaluating " + node + ": " + str(e.args[0]),) + e.args[
                    1:
                ]
            raise
    # Save results
    set_value(graph, node, res)

evaluate_target(graph, target, continue_on_fail=False)

Evaluate a target node in the graph (any type of node).

Parameters

graph : grapes Graph The graph containing the node to evaluate. target : hashable (typically string) The name of the node to evaluate. continue_on_fail : bool, optional If True, continue evaluation even if an error occurs. Default is False.

Raises

ValueError If the node type is not supported.

Notes

If continue_on_fail is False, any error during evaluation will raise an exception.

Source code in grapes/evaluate.py
def evaluate_target(graph, target, continue_on_fail=False):
    """
    Evaluate a target node in the graph (any type of node).

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node to evaluate.
    target : hashable (typically string)
        The name of the node to evaluate.
    continue_on_fail : bool, optional
        If True, continue evaluation even if an error occurs. Default is False.

    Raises
    ------
    ValueError
        If the node type is not supported.

    Notes
    -----
    If continue_on_fail is False, any error during evaluation will raise an exception.
    """
    if get_type(graph, target) == "standard":
        return evaluate_standard(graph, target, continue_on_fail)
    elif get_type(graph, target) == "conditional":
        return evaluate_conditional(graph, target, continue_on_fail)
    else:
        raise ValueError(
            "Evaluation of nodes of type "
            + get_type(graph, target)
            + " is not supported"
        )

execute_to_targets(graph, *targets)

Evaluate all nodes in the graph required to reach the specified targets.

Parameters

graph : grapes Graph The graph containing the nodes to evaluate. *targets : hashables (typically strings) Names of one or more target nodes to evaluate.

Source code in grapes/evaluate.py
def execute_to_targets(graph, *targets):
    """
    Evaluate all nodes in the graph required to reach the specified targets.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes to evaluate.
    *targets : hashables (typically strings)
        Names of one or more target nodes to evaluate.
    """
    for target in targets:
        evaluate_target(graph, target, False)

execute_towards_all_conditions_of_conditional(graph, conditional)

Progress towards all conditions of a specific conditional node, stopping if one is found true.

Parameters

graph : grapes Graph The graph containing the conditional node. conditional : hashable (typically string) The name of the conditional node whose conditions are to be evaluated.

Source code in grapes/evaluate.py
def execute_towards_all_conditions_of_conditional(graph, conditional):
    """
    Progress towards all conditions of a specific conditional node, stopping if one is found true.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the conditional node.
    conditional : hashable (typically string)
        The name of the conditional node whose conditions are to be evaluated.
    """
    execute_towards_conditions(graph, *get_conditions(graph, conditional))

execute_towards_conditions(graph, *conditions)

Progress towards the specified conditions, stopping if one is found true.

Parameters

graph : grapes Graph The graph containing the conditions. *conditions : hashables (typically strings) Names of one or more (condition) nodes to evaluate.

Source code in grapes/evaluate.py
def execute_towards_conditions(graph, *conditions):
    """
    Progress towards the specified conditions, stopping if one is found true.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the conditions.
    *conditions : hashables (typically strings)
        Names of one or more (condition) nodes to evaluate.
    """
    for condition in conditions:
        evaluate_target(graph, condition, True)
        if get_has_value(graph, condition) and graph[condition]:
            break

progress_towards_targets(graph, *targets)

Progress towards the specified targets by evaluating nodes, continuing on failure.

Parameters

graph : grapes Graph The graph containing the nodes to evaluate. *targets : hashables (typically strings) Names of one or more target nodes to evaluate.

Source code in grapes/evaluate.py
def progress_towards_targets(graph, *targets):
    """
    Progress towards the specified targets by evaluating nodes, continuing on failure.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes to evaluate.
    *targets : hashables (typically strings)
        Names of one or more target nodes to evaluate.
    """
    for target in targets:
        evaluate_target(graph, target, True)

features

This module contains functions that manipulate the features of the nodes.

compute_topological_generation_indexes(graph)

Compute and set the topological generation indexes for all nodes in the graph.

Parameters

graph : grapes Graph The graph containing the nodes.

Source code in grapes/features.py
def compute_topological_generation_indexes(graph):
    """
    Compute and set the topological generation indexes for all nodes in the graph.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    """
    generations = get_topological_generations(graph)
    for node in graph.nodes:
        for index, generation in enumerate(generations):
            if node in generation:
                set_topological_generation_index(graph, node, index)
                break

freeze(graph, *args)

Freeze nodes in the graph, preventing their values from being changed.

Parameters

graph : grapes Graph The graph containing the nodes. *args : hashables (typically strings) Node names to freeze. If empty, all nodes are frozen.

Source code in grapes/features.py
def freeze(graph, *args):
    """
    Freeze nodes in the graph, preventing their values from being changed.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    *args : hashables (typically strings)
        Node names to freeze. If empty, all nodes are frozen.
    """
    if len(args) == 0:  # Interpret as "Freeze everything"
        nodes_to_freeze = graph.nodes
    else:
        nodes_to_freeze = args & graph.nodes  # Intersection

    for key in nodes_to_freeze:
        if get_has_value(graph, key):
            set_is_frozen(graph, key, True)

get_all_ancestors_target(graph, target)

Get all the ancestors of a node.

Parameters

graph : grapes Graph The graph containing the nodes. target : str The name of the target node.

Returns

set Set of ancestor node names.

Source code in grapes/features.py
def get_all_ancestors_target(graph, target):
    """
    Get all the ancestors of a node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    target : str
        The name of the target node.

    Returns
    -------
    set
        Set of ancestor node names.
    """
    return nx.ancestors(graph._nxdg, target)

get_all_conditionals(graph)

Get set of all conditional nodes in the graph.

Parameters

graph : grapes Graph The graph containing the nodes.

Returns

set Set of conditional node names.

Source code in grapes/features.py
def get_all_conditionals(graph):
    """
    Get set of all conditional nodes in the graph.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.

    Returns
    -------
    set
        Set of conditional node names.
    """
    conditionals = set()
    for node in graph.nodes:
        if get_type(graph, node) == "conditional":
            conditionals.add(node)
    return conditionals

get_all_nodes(graph, exclude_recipes=False)

Get all nodes in the graph.

Parameters

graph : grapes Graph The graph containing the nodes. exclude_recipes : bool, optional If True, exclude recipe nodes. Default is False.

Returns

set Set of node names.

Source code in grapes/features.py
def get_all_nodes(graph, exclude_recipes=False):
    """
    Get all nodes in the graph.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    exclude_recipes : bool, optional
        If True, exclude recipe nodes. Default is False.

    Returns
    -------
    set
        Set of node names.
    """
    nodes = set()
    for node in graph.nodes:
        if exclude_recipes and get_is_recipe(graph, node):
            continue
        nodes.add(node)
    return nodes

get_all_recipes(graph)

Get all the recipe nodes in the graph.

Parameters

graph : grapes Graph The graph containing the nodes.

Returns

set Set of recipe node names.

Source code in grapes/features.py
def get_all_recipes(graph):
    """
    Get all the recipe nodes in the graph.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.

    Returns
    -------
    set
        Set of recipe node names.
    """
    recipes = set()
    for node in graph.nodes:
        if get_is_recipe(graph, node):
            recipes.add(node)
    return recipes

get_all_sinks(graph, exclude_recipes=False)

Get all sink nodes in the graph (nodes with no outgoing edges).

Parameters

graph : grapes Graph The graph containing the nodes. exclude_recipes : bool, optional If True, exclude recipe nodes. Default is False.

Returns

set Set of sink node names.

Source code in grapes/features.py
def get_all_sinks(graph, exclude_recipes=False):
    """
    Get all sink nodes in the graph (nodes with no outgoing edges).

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    exclude_recipes : bool, optional
        If True, exclude recipe nodes. Default is False.

    Returns
    -------
    set
        Set of sink node names.
    """
    sinks = set()
    for node in graph.nodes:
        if exclude_recipes and get_is_recipe(graph, node):
            continue
        if graph._nxdg.out_degree(node) == 0:
            sinks.add(node)
    return sinks

get_all_sources(graph, exclude_recipes=False)

Get all source nodes in the graph (nodes with no incoming edges).

Parameters

graph : grapes Graph The graph containing the nodes. exclude_recipes : bool, optional If True, exclude recipe nodes. Default is False.

Returns

set Set of source node names.

Source code in grapes/features.py
def get_all_sources(graph, exclude_recipes=False):
    """
    Get all source nodes in the graph (nodes with no incoming edges).

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    exclude_recipes : bool, optional
        If True, exclude recipe nodes. Default is False.

    Returns
    -------
    set
        Set of source node names.
    """
    sources = set()
    for node in graph.nodes:
        if exclude_recipes and get_is_recipe(graph, node):
            continue
        if graph._nxdg.in_degree(node) == 0:
            sources.add(node)
    return sources

get_args(graph, node)

Get the positional arguments for a node's recipe.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

list List of argument names.

Source code in grapes/features.py
def get_args(graph, node):
    """
    Get the positional arguments for a node's recipe.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    list
        List of argument names.
    """
    return get_node_attribute(graph, node, "args")

get_conditions(graph, node)

Get the conditions associated with a conditional node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

list List of condition node names.

Source code in grapes/features.py
def get_conditions(graph, node):
    """
    Get the conditions associated with a conditional node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    list
        List of condition node names.
    """
    conditions = get_node_attribute(graph, node, "conditions")
    if not isinstance(conditions, list):
        conditions = list(conditions)
    return conditions

get_has_value(graph, node)

Check if a node has a value.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

bool True if the node has a value, False otherwise.

Source code in grapes/features.py
def get_has_value(graph, node):
    """
    Check if a node has a value.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    bool
        True if the node has a value, False otherwise.
    """
    return get_node_attribute(graph, node, "has_value")

get_is_frozen(graph, node)

Check if a node is frozen.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

bool True if the node is frozen, False otherwise.

Source code in grapes/features.py
def get_is_frozen(graph, node):
    """
    Check if a node is frozen.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    bool
        True if the node is frozen, False otherwise.
    """
    return get_node_attribute(graph, node, "is_frozen")

get_is_recipe(graph, node)

Check if a node is a recipe node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

bool True if the node is a recipe, False otherwise.

Source code in grapes/features.py
def get_is_recipe(graph, node):
    """
    Check if a node is a recipe node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    bool
        True if the node is a recipe, False otherwise.
    """
    return get_node_attribute(graph, node, "is_recipe")

get_kwargs(graph, node)

Get the keyword arguments for a node's recipe.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

dict Dictionary of keyword argument names and node names.

Source code in grapes/features.py
def get_kwargs(graph, node):
    """
    Get the keyword arguments for a node's recipe.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    dict
        Dictionary of keyword argument names and node names.
    """
    return get_node_attribute(graph, node, "kwargs")

get_node_attribute(graph, node, attribute)

Get the value of a specific attribute for a node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. attribute : str The attribute to retrieve.

Returns

Any The value of the attribute.

Raises

ValueError If the attribute is not present or is None.

Source code in grapes/features.py
def get_node_attribute(graph, node, attribute):
    """
    Get the value of a specific attribute for a node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    attribute : str
        The attribute to retrieve.

    Returns
    -------
    Any
        The value of the attribute.

    Raises
    ------
    ValueError
        If the attribute is not present or is None.
    """
    attributes = graph.nodes[node]
    if attribute in attributes and attributes[attribute] is not None:
        return attributes[attribute]
    else:
        raise ValueError("Node " + node + " has no " + attribute)

get_possibilities(graph, node)

Get the possible outcomes associated with a conditional node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

list List of possible outcome node names.

Source code in grapes/features.py
def get_possibilities(graph, node):
    """
    Get the possible outcomes associated with a conditional node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    list
        List of possible outcome node names.
    """
    possibilities = get_node_attribute(graph, node, "possibilities")
    if not isinstance(possibilities, list):
        possibilities = list(possibilities)
    return possibilities

get_recipe(graph, node)

Get the recipe associated with a node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

hashable (typically string) The name of the node that acts as recipe for the passed node.

Source code in grapes/features.py
def get_recipe(graph, node):
    """
    Get the recipe associated with a node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    hashable (typically string)
        The name of the node that acts as recipe for the passed node.
    """
    return get_node_attribute(graph, node, "recipe")

get_topological_generation_index(graph, node)

Get the topological generation index of a node. It does not compute it, just retrieves the stored value.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

int The topological generation index.

Source code in grapes/features.py
def get_topological_generation_index(graph, node):
    """
    Get the topological generation index of a node.
    It does not compute it, just retrieves the stored value.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    int
        The topological generation index.
    """
    return get_node_attribute(graph, node, "topological_generation_index")

get_topological_generations(graph)

Return list of topological generations of the graph.

Parameters

graph : grapes Graph The graph containing the nodes.

Returns

list List of generations, each generation is a list of node names.

Source code in grapes/features.py
def get_topological_generations(graph):
    """
    Return list of topological generations of the graph.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.

    Returns
    -------
    list
        List of generations, each generation is a list of node names.
    """
    return list(nx.topological_generations(graph._nxdg))

get_topological_order(graph)

Return list of nodes in topological order, i.e., from dependencies to targets.

Parameters

graph : grapes Graph The graph containing the nodes.

Returns

list List of node names in topological order.

Source code in grapes/features.py
def get_topological_order(graph):
    """
    Return list of nodes in topological order, i.e., from dependencies to targets.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.

    Returns
    -------
    list
        List of node names in topological order.
    """
    return list(nx.topological_sort(graph._nxdg))

get_type(graph, node)

Get the type of a node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

str The type of the node.

Source code in grapes/features.py
def get_type(graph, node):
    """
    Get the type of a node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    str
        The type of the node.
    """
    return get_node_attribute(graph, node, "type")

get_value(graph, node)

Get the value of a node if it has a value.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

Any The value of the node.

Raises

ValueError If the node does not have a value.

Source code in grapes/features.py
def get_value(graph, node):
    """
    Get the value of a node if it has a value.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    Any
        The value of the node.

    Raises
    ------
    ValueError
        If the node does not have a value.
    """
    if get_node_attribute(graph, node, "value") is not None and get_has_value(
        graph, node
    ):
        return get_node_attribute(graph, node, "value")
    else:
        raise ValueError("Node " + node + " has no value")

make_recipe_dependencies_also_recipes(graph)

Make dependencies (predecessors) of recipes also recipes, if they have only recipe successors.

Parameters

graph : grapes Graph The graph containing the nodes.

Source code in grapes/features.py
def make_recipe_dependencies_also_recipes(graph):
    """
    Make dependencies (predecessors) of recipes also recipes, if they have only recipe successors.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    """
    # Work in reverse topological order, to get successors before predecessors
    for node in reversed(get_topological_order(graph)):
        if get_is_recipe(graph, node):
            for parent in graph._nxdg.predecessors(node):
                if not get_is_recipe(graph, parent):
                    all_children_are_recipes = True
                    for child in graph._nxdg.successors(parent):
                        if not get_is_recipe(graph, child):
                            all_children_are_recipes = False
                            break
                    if all_children_are_recipes:
                        set_is_recipe(graph, parent, True)

set_args(graph, node, args)

Set the positional arguments for a node's recipe.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. args : list List of argument names.

Source code in grapes/features.py
def set_args(graph, node, args):
    """
    Set the positional arguments for a node's recipe.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    args : list
        List of argument names.
    """
    return set_node_attribute(graph, node, "args", args)

set_conditions(graph, node, conditions)

Set the conditions for a conditional node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. conditions : list List of condition node names.

Source code in grapes/features.py
def set_conditions(graph, node, conditions):
    """
    Set the conditions for a conditional node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    conditions : list
        List of condition node names.
    """
    if not isinstance(conditions, list):
        conditions = list(conditions)
    return set_node_attribute(graph, node, "conditions", conditions)

set_has_value(graph, node, has_value)

Set whether a node has a value.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. has_value : bool Whether the node has a value.

Source code in grapes/features.py
def set_has_value(graph, node, has_value):
    """
    Set whether a node has a value.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    has_value : bool
        Whether the node has a value.
    """
    return set_node_attribute(graph, node, "has_value", has_value)

set_is_frozen(graph, node, is_frozen)

Set whether a node is frozen.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. is_frozen : bool Whether the node is frozen.

Source code in grapes/features.py
def set_is_frozen(graph, node, is_frozen):
    """
    Set whether a node is frozen.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    is_frozen : bool
        Whether the node is frozen.
    """
    return set_node_attribute(graph, node, "is_frozen", is_frozen)

set_is_recipe(graph, node, is_recipe)

Set whether a node is a recipe node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. is_recipe : bool Whether the node is a recipe.

Source code in grapes/features.py
def set_is_recipe(graph, node, is_recipe):
    """
    Set whether a node is a recipe node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    is_recipe : bool
        Whether the node is a recipe.
    """
    return set_node_attribute(graph, node, "is_recipe", is_recipe)

set_kwargs(graph, node, kwargs)

Set the keyword arguments for a node's recipe.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. kwargs : dict Dictionary of keyword argument names and node names.

Source code in grapes/features.py
def set_kwargs(graph, node, kwargs):
    """
    Set the keyword arguments for a node's recipe.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    kwargs : dict
        Dictionary of keyword argument names and node names.
    """
    return set_node_attribute(graph, node, "kwargs", kwargs)

set_node_attribute(graph, node, attribute, value)

Set the value of a specific attribute for a node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. attribute : str The attribute to set. value : Any The value to assign.

Source code in grapes/features.py
def set_node_attribute(graph, node, attribute, value):
    """
    Set the value of a specific attribute for a node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    attribute : str
        The attribute to set.
    value : Any
        The value to assign.
    """
    graph.nodes[node][attribute] = value

set_possibilities(graph, node, possibilities)

Set the possible outcomes for a conditional node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. possibilities : list List of possible outcome node names.

Source code in grapes/features.py
def set_possibilities(graph, node, possibilities):
    """
    Set the possible outcomes for a conditional node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    possibilities : list
        List of possible outcome node names.
    """
    if not isinstance(possibilities, list):
        possibilities = list(possibilities)
    return set_node_attribute(graph, node, "possibilities", possibilities)

set_recipe(graph, node, recipe)

Set the recipe for a node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. recipe : hashable (typically string) The name of the node that will act as recipe.

Source code in grapes/features.py
def set_recipe(graph, node, recipe):
    """
    Set the recipe for a node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    recipe : hashable (typically string)
        The name of the node that will act as recipe.
    """
    return set_node_attribute(graph, node, "recipe", recipe)

set_topological_generation_index(graph, node, index)

Set the topological generation index of a node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. index : int The generation index to assign.

Source code in grapes/features.py
def set_topological_generation_index(graph, node, index):
    """
    Set the topological generation index of a node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    index : int
        The generation index to assign.
    """
    set_node_attribute(graph, node, "topological_generation_index", index)

set_type(graph, node, type)

Set the type of a node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. type : str The type to assign.

Source code in grapes/features.py
def set_type(graph, node, type):
    """
    Set the type of a node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    type : str
        The type to assign.
    """
    return set_node_attribute(graph, node, "type", type)

set_value(graph, node, value)

Set the value of a node and mark it as having a value.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. value : Any The value to assign.

Source code in grapes/features.py
def set_value(graph, node, value):
    """
    Set the value of a node and mark it as having a value.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    value : Any
        The value to assign.
    """
    # Note: This changes reachability
    set_node_attribute(graph, node, "value", value)
    set_has_value(graph, node, True)

unfreeze(graph, *args)

Unfreeze nodes in the graph, allowing their values to be changed.

Parameters

graph : grapes Graph The graph containing the nodes. *args : hashables (typically strings) Node names to unfreeze. If empty, all nodes are unfrozen.

Source code in grapes/features.py
def unfreeze(graph, *args):
    """
    Unfreeze nodes in the graph, allowing their values to be changed.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    *args : hashables (typically strings)
        Node names to unfreeze. If empty, all nodes are unfrozen.
    """
    if len(args) == 0:  # Interpret as "Unfreeze everything"
        nodes_to_unfreeze = graph.nodes.keys()
    else:
        nodes_to_unfreeze = args & graph.nodes  # Intersection

    for key in nodes_to_unfreeze:
        set_is_frozen(graph, key, False)

unset_value(graph, node)

Unset the value of a node and mark it as not having a value.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Source code in grapes/features.py
def unset_value(graph, node):
    """
    Unset the value of a node and mark it as not having a value.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    """
    # Note: This changes reachability
    set_has_value(graph, node, False)

function_composer

This module contains some tools to compose functions into one.

function_compose(func, subfuncs, func_dependencies, subfuncs_dependencies, func_signature, subfuncs_signatures)

Compose functions.

Parameters

func : callable External function subfuncs : list of callables List of internal functions to be composed with func. If identity_token is passed here, it is replaced by the value of the argument. func_dependencies : list of hashables Names of the arguments of func (usually they should correspond to node names in a graph). subfuncs_dependencies : list of lists of hashables Names of the arguments that are passed to the subfuncs when calling the new function (usually they should correspond to node names in a graph). func_signature : list of hashables Names of the arguments of the old func. subfuncs_signatures : list of lists of hashables Names of the arguments of the old subfuncs.

Returns

callable A new function that represents the composition of func and subfuncs.

Source code in grapes/function_composer.py
def function_compose(
    func,
    subfuncs,
    func_dependencies,
    subfuncs_dependencies,
    func_signature,
    subfuncs_signatures,
):
    """
    Compose functions.

    Parameters
    ----------
    func : callable
        External function
    subfuncs : list of callables
        List of internal functions to be composed with func. If identity_token is passed here, it is replaced by the value of the argument.
    func_dependencies : list of hashables
        Names of the arguments of func (usually they should correspond to node names in a graph).
    subfuncs_dependencies : list of lists of hashables
        Names of the arguments that are passed to the subfuncs when calling the new function (usually they should correspond to node names in a graph).
    func_signature : list of hashables
        Names of the arguments of the old func.
    subfuncs_signatures : list of lists of hashables
        Names of the arguments of the old subfuncs.

    Returns
    -------
    callable
        A new function that represents the composition of func and subfuncs.
    """
    return lambda **kwargs: func(
        **{
            func_signature[i]: (
                subfuncs[i](
                    **{
                        signature_name: kwargs[dependency_name]
                        for signature_name, dependency_name in zip(
                            subfuncs_signatures[i], subfuncs_dependencies[i]
                        )
                    }
                )
                if subfuncs[i] is not identity_token
                else kwargs[func_dependencies[i]]
            )
            for i in range(len(subfuncs))
        }
    )

function_compose_simple(func, subfuncs, func_dependencies, subfuncs_dependencies, func_signature=None, subfuncs_signatures=None)

Compose functions, without the need to pass signatures, as they are found automatically.

Parameters

func : callable External function. subfuncs : list of callables List of internal functions to be composed with func. func_dependencies : list of hashables Names of the arguments of the new function (usually they should correspond to node names in a graph). subfuncs_dependencies : list of lists of hashables Names of the arguments that are passed to the subfuncs when calling the new function (usually they should correspond to node names in a graph). func_signature : list of hashables Names of the arguments of the old func. subfuncs_signatures : list of lists of hashables Names of the arguments of the old subfuncs.

Returns

callable A new function that represents the composition of func and subfuncs.

Raises

ValueError If func or any of the subfuncs have varargs, as they cannot be handled automatically.

Source code in grapes/function_composer.py
def function_compose_simple(
    func,
    subfuncs,
    func_dependencies,
    subfuncs_dependencies,
    func_signature=None,
    subfuncs_signatures=None,
):
    """
    Compose functions, without the need to pass signatures, as they are found automatically.

    Parameters
    ----------
    func : callable
        External function.
    subfuncs : list of callables
        List of internal functions to be composed with func.
    func_dependencies : list of hashables
        Names of the arguments of the new function (usually they should correspond to node names in a graph).
    subfuncs_dependencies : list of lists of hashables
        Names of the arguments that are passed to the subfuncs when calling the new function (usually they should correspond to node names in a graph).
    func_signature : list of hashables
        Names of the arguments of the old func.
    subfuncs_signatures : list of lists of hashables
        Names of the arguments of the old subfuncs.

    Returns
    -------
    callable
        A new function that represents the composition of func and subfuncs.

    Raises
    ------
    ValueError
        If func or any of the subfuncs have varargs, as they cannot be handled automatically.
    """
    if func_signature is None:
        if inspect.getfullargspec(func).varargs is not None:
            raise ValueError(
                "Functions with varargs are not supported by Function Composer"
            )
        elif (
            inspect.getfullargspec(func).varargs is None
            and inspect.getfullargspec(func).varkw is None
        ):  # Well defined spec
            func_signature = list(inspect.signature(func).parameters.keys())
        else:
            func_signature = func_dependencies
    if subfuncs_signatures is None:
        subfuncs_signatures = []
        for index, subfunc in enumerate(subfuncs):
            if inspect.getfullargspec(func).varargs is not None:
                raise ValueError(
                    "Functions with varargs are not supported by Function Composer"
                )
            elif (
                inspect.getfullargspec(subfunc).varargs is None
                and inspect.getfullargspec(subfunc).varkw is None
            ):  # Well defined spec
                this_signature = list(inspect.signature(subfunc).parameters.keys())
            else:
                this_signature = subfuncs_dependencies[index]
            subfuncs_signatures.append(this_signature)
    return function_compose(
        func,
        subfuncs,
        func_dependencies,
        subfuncs_dependencies,
        func_signature,
        subfuncs_signatures,
    )

identity_token()

A trivial token that has the only purpose of being identifiable

Source code in grapes/function_composer.py
def identity_token():
    """
    A trivial token that has the only purpose of being identifiable
    """
    pass

merge

This module contains functions to merge, split and verify the compatibility of graphs.

check_compatibility(first, second)

Check if two graphs can be composed (merged).

Parameters

first : grapes Graph The first graph. second : grapes Graph The second graph.

Returns

bool True if the graphs are compatible, False otherwise.

Source code in grapes/merge.py
def check_compatibility(first, second):
    """
    Check if two graphs can be composed (merged).

    Parameters
    ----------
    first : grapes Graph
        The first graph.
    second : grapes Graph
        The second graph.

    Returns
    -------
    bool
        True if the graphs are compatible, False otherwise.
    """
    if not isinstance(first, Graph) or not isinstance(second, Graph):
        return False
    common_nodes = first.nodes & second._nxdg.nodes  # Intersection
    for key in common_nodes:
        if not check_compatibility_nodes(first, key, second, key):
            return False
    return True

check_compatibility_nodes(first_graph, first_node, second_graph, second_node)

Check if two nodes from different graphs are compatible for merging. Nodes are compatible if they have the same type, and if they have values or dependencies, those values or dependencies must be the same. However, if only one of the nodes has dependencies, they are still considered compatible.

Parameters

first_graph : grapes Graph The first graph containing the node. first_node : hashable (typically string) The name of the node in the first graph. second_graph : grapes Graph The second graph containing the node. second_node : hashable (typically string) The name of the node in the second graph.

Returns

bool True if the nodes are compatible, False otherwise.

Source code in grapes/merge.py
def check_compatibility_nodes(first_graph, first_node, second_graph, second_node):
    """
    Check if two nodes from different graphs are compatible for merging.
    Nodes are compatible if they have the same type, and if they have values or dependencies, those values or dependencies must be the same.
    However, if only one of the nodes has dependencies, they are still considered compatible.

    Parameters
    ----------
    first_graph : grapes Graph
        The first graph containing the node.
    first_node : hashable (typically string)
        The name of the node in the first graph.
    second_graph : grapes Graph
        The second graph containing the node.
    second_node : hashable (typically string)
        The name of the node in the second graph.

    Returns
    -------
    bool
        True if the nodes are compatible, False otherwise.
    """
    # If types differ, return False
    if get_type(first_graph, first_node) != get_type(second_graph, second_node):
        return False
    # If nodes are equal, return True
    if first_graph.nodes[first_node] == second_graph._nxdg.nodes[second_node]:
        return True
    # If they both have values but they differ, return False. If only one has a value, proceed
    if (
        get_has_value(first_graph, first_node)
        and get_has_value(second_graph, second_node)
        and get_value(first_graph, first_node) != get_value(second_graph, second_node)
    ):
        # Plot twist! Both are functions and have the same code: proceed
        if (
            inspect.isfunction(get_value(first_graph, first_node))
            and inspect.isfunction(get_value(second_graph, second_node))
            and get_value(first_graph, first_node).__code__.co_code
            == get_value(second_graph, second_node).__code__.co_code
        ):
            pass
        else:
            return False
    # If they both have dependencies but they differ, return False. If only one has dependencies, proceed
    predecessors = list(first_graph._nxdg.predecessors(first_node))
    other_predecessors = list(second_graph._nxdg.predecessors(second_node))
    if (
        len(predecessors) != 0
        and len(other_predecessors) != 0
        and predecessors != other_predecessors
    ):
        return False
    # Return True if at least one has no dependencies (or they are the same), at least one has no value (or they are the same)
    return True

get_subgraph(graph, nodes)

Get a subgraph containing only the specified nodes.

Parameters

graph : grapes Graph The original graph. nodes : list or set of hashables (typically strings) The node names to include in the subgraph.

Returns

grapes Graph A new graph containing only the specified nodes.

Source code in grapes/merge.py
def get_subgraph(graph, nodes):
    """
    Get a subgraph containing only the specified nodes.

    Parameters
    ----------
    graph : grapes Graph
        The original graph.
    nodes : list or set of hashables (typically strings)
        The node names to include in the subgraph.

    Returns
    -------
    grapes Graph
        A new graph containing only the specified nodes.
    """
    res = copy.deepcopy(graph)
    res._nxdg.remove_nodes_from([n for n in graph._nxdg if n not in nodes])
    return res

merge(first, second, *others)

Merge any number (>=2) of graphs into a single graph.

Parameters

first : grapes Graph The first graph. second : grapes Graph The second graph. *others : grapes Graph Any other graphs to merge.

Returns

grapes Graph The merged graph.

Raises

ValueError If any pair of graphs are not compatible.

Source code in grapes/merge.py
def merge(first, second, *others):
    """
    Merge any number (>=2) of graphs into a single graph.

    Parameters
    ----------
    first : grapes Graph
        The first graph.
    second : grapes Graph
        The second graph.
    *others : grapes Graph
        Any other graphs to merge.

    Returns
    -------
    grapes Graph
        The merged graph.

    Raises
    ------
    ValueError
        If any pair of graphs are not compatible.
    """
    if len(others) == 0:
        return merge_two(first, second)
    else:
        return merge(merge_two(first, second), *others)

merge_two(first, second)

Merge two compatible graphs into a new graph.

Parameters

first : grapes Graph The first graph. second : grapes Graph The second graph.

Returns

grapes Graph The merged graph.

Raises

ValueError If the graphs are not compatible.

Source code in grapes/merge.py
def merge_two(first, second):
    """
    Merge two compatible graphs into a new graph.

    Parameters
    ----------
    first : grapes Graph
        The first graph.
    second : grapes Graph
        The second graph.

    Returns
    -------
    grapes Graph
        The merged graph.

    Raises
    ------
    ValueError
        If the graphs are not compatible.
    """
    if not check_compatibility(first, second):
        raise ValueError("Cannot merge incompatible graphs")
    res = nx.compose(first._nxdg, second._nxdg)
    # If a node in first has value, but its counterpart in second has not, the value must be recovered
    for key in nx.intersection(first._nxdg, second._nxdg):
        if (
            not second._nxdg.nodes[key]["has_value"]
            and first._nxdg.nodes[key]["has_value"]
        ):
            res.nodes[key]["has_value"] = True
            res.nodes[key]["value"] = first._nxdg.nodes[key]["value"]
    return Graph(nx_digraph=res)

path

This module contains functions to get the path needed to reach a target from valued nodes.

get_path_to_conditional(graph, conditional)

Get the path from the last valued nodes to a conditional node.

Parameters

graph : grapes Graph The graph containing the nodes. conditional : hashable (typically string) The name of the conditional node.

Returns

set Set of node names representing the path from valued nodes to the conditional node.

Source code in grapes/path.py
def get_path_to_conditional(graph, conditional):
    """
    Get the path from the last valued nodes to a conditional node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    conditional : hashable (typically string)
        The name of the conditional node.

    Returns
    -------
    set
        Set of node names representing the path from valued nodes to the conditional node.
    """
    result = set((conditional,))
    if get_has_value(graph, conditional):
        return result
    # If not, evaluate the conditions until one is found true
    for index, condition in enumerate(get_conditions(graph, conditional)):
        if get_has_value(graph, condition) and get_value(graph, condition):
            # A condition is true
            possibility = get_possibilities(graph, conditional)[index]
            result = result | get_path_to_standard(graph, condition)
            result = result | get_path_to_standard(graph, possibility)
            return result
    # If no conditions are true, we need to compute them, so all ancestors are in the path
    result = get_path_to_standard(graph, conditional)
    return result

get_path_to_standard(graph, node)

Get the path from the last valued nodes to a standard node.

Parameters

graph : grapes Graph The graph containing the nodes. node : hashable (typically string) The name of the standard node.

Returns

set Set of node names representing the path from valued nodes to the standard node.

Source code in grapes/path.py
def get_path_to_standard(graph, node):
    """
    Get the path from the last valued nodes to a standard node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    node : hashable (typically string)
        The name of the standard node.

    Returns
    -------
    set
        Set of node names representing the path from valued nodes to the standard node.
    """
    result = set((node,))
    if get_has_value(graph, node):
        return result
    dependencies = graph._nxdg.predecessors(node)
    for dependency in dependencies:
        result = result | get_path_to_target(graph, dependency)
    return result

get_path_to_target(graph, target)

Get the path from the last valued nodes to a target node.

Parameters

graph : grapes Graph The graph containing the nodes. target : hashable (typically string) The name of the target node.

Returns

set Set of node names representing the path from valued nodes to the target.

Raises

ValueError If the node type is not supported.

Source code in grapes/path.py
def get_path_to_target(graph, target):
    """
    Get the path from the last valued nodes to a target node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    target : hashable (typically string)
        The name of the target node.

    Returns
    -------
    set
        Set of node names representing the path from valued nodes to the target.

    Raises
    ------
    ValueError
        If the node type is not supported.
    """
    if get_type(graph, target) == "standard":
        return get_path_to_standard(graph, target)
    elif get_type(graph, target) == "conditional":
        return get_path_to_conditional(graph, target)
    else:
        raise ValueError(
            "Getting the ancestors of nodes of type "
            + get_type(graph, target)
            + " is not supported"
        )

reachability

This module contains functions to manipulate the reachability status of targets from valued nodes.

clear_reachabilities(graph, *args)

Clear reachabilities in the graph nodes.

Parameters

graph : grapes Graph The graph containing the nodes. *args : hashable (typically string) Node names to clear. If empty, all nodes are cleared.

Source code in grapes/reachability.py
def clear_reachabilities(graph, *args):
    """
    Clear reachabilities in the graph nodes.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    *args : hashable (typically string)
        Node names to clear. If empty, all nodes are cleared.
    """
    if len(args) == 0:  # Interpret as "Clear everything"
        nodes_to_clear = graph.nodes
    else:
        nodes_to_clear = args & graph.nodes  # Intersection

    for node in nodes_to_clear:
        if get_is_frozen(graph, node):
            continue
        unset_reachability(graph, node)

compute_reachability_conditional(graph, conditional)

Compute the reachability of a conditional node.

Parameters

graph : grapes Graph The graph containing the node. conditional : hashable (typically string) The name of the conditional node.

Source code in grapes/reachability.py
def compute_reachability_conditional(graph, conditional):
    """
    Compute the reachability of a conditional node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    conditional : hashable (typically string)
        The name of the conditional node.
    """
    # Check if it already has a reachability
    if get_has_reachability(graph, conditional):
        return
    # Check if it already has a value
    if get_has_value(graph, conditional):
        get_value(graph, conditional)
        set_reachability(graph, conditional, "reachable")
        return
    # If not, evaluate the conditions until one is found true
    for index, condition in enumerate(get_conditions(graph, conditional)):
        if get_has_value(graph, condition) and get_value(graph, condition):
            # A condition is true
            possibility = get_possibilities(graph, conditional)[index]
            compute_reachability_target(graph, possibility)
            set_reachability(graph, conditional, get_reachability(graph, possibility))
            return
    else:
        # Happens if loop is never broken, i.e. when no conditions are true
        # If all conditions and possibilities are reachable -> reachable
        # If all conditions and possibilities are unreachable -> unreachable
        # If some conditions are reachable or uncertain but the corresponding possibilities are all unreachable -> unreachable
        # In all other cases -> uncertain
        compute_reachability_targets(graph, *get_conditions(graph, conditional))
        compute_reachability_targets(graph, *get_possibilities(graph, conditional))

        if (
            get_worst_reachability(
                graph,
                *(
                    get_conditions(graph, conditional)
                    + get_possibilities(graph, conditional)
                )
            )
            == "reachable"
        ):
            # All conditions and possibilities are reachable -> reachable
            set_reachability(graph, conditional, "reachable")
        elif (
            get_best_reachability(
                graph,
                *(
                    get_conditions(graph, conditional)
                    + get_possibilities(graph, conditional)
                )
            )
            == "unreachable"
        ):
            # All conditions and possibilities are unreachable -> unreachable
            set_reachability(graph, conditional, "unreachable")
        else:
            not_unreachable_condition_possibilities = []
            for index, condition in enumerate(get_conditions(graph, conditional)):
                if get_reachability(graph, condition) != "unreachable":
                    not_unreachable_condition_possibilities.append(
                        get_possibilities(graph, conditional)[index]
                    )
            if (
                get_best_reachability(graph, *not_unreachable_condition_possibilities)
                == "unreachable"
            ):
                # All corresponding possibilities are unreachable -> unreachable
                set_reachability(graph, conditional, "unreachable")
            else:
                set_reachability(graph, conditional, "uncertain")

compute_reachability_standard(graph, node)

Compute the reachability of a standard node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the standard node.

Source code in grapes/reachability.py
def compute_reachability_standard(graph, node):
    """
    Compute the reachability of a standard node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the standard node.
    """
    # Check if it already has a reachability
    if get_has_reachability(graph, node):
        return
    # Check if it already has a value
    if get_has_value(graph, node):
        set_reachability(graph, node, "reachable")
        return
    # If not, check the missing dependencies of all arguments
    dependencies = set(graph._nxdg.predecessors(node))
    if len(dependencies) == 0:
        # If this node does not have predecessors (and does not have a value itgraph), it is not reachable
        set_reachability(graph, node, "unreachable")
        return
    # Otherwise, dependencies must be checked
    compute_reachability_targets(graph, *dependencies)
    set_reachability(graph, node, get_worst_reachability(graph, *dependencies))

compute_reachability_target(graph, target)

Compute the reachability of a target node.

Parameters

graph : grapes Graph The graph containing the node. target : hashable (typically string) The name of the target node.

Raises

ValueError If the node type is not supported.

Source code in grapes/reachability.py
def compute_reachability_target(graph, target):
    """
    Compute the reachability of a target node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    target : hashable (typically string)
        The name of the target node.

    Raises
    ------
    ValueError
        If the node type is not supported.
    """
    if get_type(graph, target) == "standard":
        return compute_reachability_standard(graph, target)
    elif get_type(graph, target) == "conditional":
        return compute_reachability_conditional(graph, target)
    else:
        raise ValueError(
            "Computing the reachability of nodes of type "
            + get_type(graph, target)
            + " is not supported"
        )

compute_reachability_targets(graph, *targets)

Compute the reachability of multiple target nodes.

Parameters

graph : grapes Graph The graph containing the nodes. *targets : hashables (typically strings) Node names to check reachability for.

Source code in grapes/reachability.py
def compute_reachability_targets(graph, *targets):
    """
    Compute the reachability of multiple target nodes.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    *targets : hashables (typically strings)
        Node names to check reachability for.
    """
    for target in targets:
        compute_reachability_target(graph, target)

get_best_reachability(graph, *nodes)

Get the best (most reachable) reachability value among a set of nodes.

Parameters

graph : grapes Graph The graph containing the nodes. *nodes : hashables (typically strings) Node names to check.

Returns

str The best reachability value ("reachable", "uncertain", or "unreachable").

Source code in grapes/reachability.py
def get_best_reachability(graph, *nodes):
    """
    Get the best (most reachable) reachability value among a set of nodes.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    *nodes : hashables (typically strings)
        Node names to check.

    Returns
    -------
    str
        The best reachability value ("reachable", "uncertain", or "unreachable").
    """
    list_of_reachabilities = []
    for node in nodes:
        list_of_reachabilities.append(get_reachability(graph, node))
    if "reachable" in list_of_reachabilities:
        return "reachable"
    elif "uncertain" in list_of_reachabilities:
        return "uncertain"
    else:
        return "unreachable"

get_has_reachability(graph, node)

Check if a node has a reachability value.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

bool True if the node has a reachability value, False otherwise.

Source code in grapes/reachability.py
def get_has_reachability(graph, node):
    """
    Check if a node has a reachability value.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    bool
        True if the node has a reachability value, False otherwise.
    """
    return get_node_attribute(graph, node, "has_reachability")

get_reachability(graph, node)

Get the reachability value of a node. It does not compute it, just retrieves it.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Returns

str The reachability value ("unreachable", "uncertain", or "reachable").

Raises

KeyError If the node does not have a reachability value.

Source code in grapes/reachability.py
def get_reachability(graph, node):
    """
    Get the reachability value of a node.
    It does not compute it, just retrieves it.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.

    Returns
    -------
    str
        The reachability value ("unreachable", "uncertain", or "reachable").

    Raises
    ------
    KeyError
        If the node does not have a reachability value.
    """
    if get_node_attribute(
        graph, node, "reachability"
    ) is not None and get_node_attribute(graph, node, "has_reachability"):
        return get_node_attribute(graph, node, "reachability")
    else:
        raise KeyError("Node " + node + " has no reachability")

get_worst_reachability(graph, *nodes)

Get the worst (least reachable) reachability value among a set of nodes.

Parameters

graph : grapes Graph The graph containing the nodes. *nodes : hashables (typically strings) Node names to check.

Returns

str The worst reachability value ("unreachable", "uncertain", or "reachable").

Source code in grapes/reachability.py
def get_worst_reachability(graph, *nodes):
    """
    Get the worst (least reachable) reachability value among a set of nodes.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    *nodes : hashables (typically strings)
        Node names to check.

    Returns
    -------
    str
        The worst reachability value ("unreachable", "uncertain", or "reachable").
    """
    list_of_reachabilities = []
    for node in nodes:
        list_of_reachabilities.append(get_reachability(graph, node))
    if "unreachable" in list_of_reachabilities:
        return "unreachable"
    elif "uncertain" in list_of_reachabilities:
        return "uncertain"
    else:
        return "reachable"

set_has_reachability(graph, node, has_reachability)

Set whether a node has a reachability value.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. has_reachability : bool Whether the node has a reachability value.

Source code in grapes/reachability.py
def set_has_reachability(graph, node, has_reachability):
    """
    Set whether a node has a reachability value.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    has_reachability : bool
        Whether the node has a reachability value.
    """
    return set_node_attribute(graph, node, "has_reachability", has_reachability)

set_reachability(graph, node, reachability)

Set the reachability value of a node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node. reachability : str The reachability value ("unreachable", "uncertain", or "reachable").

Raises

ValueError If the reachability value is not valid.

Source code in grapes/reachability.py
def set_reachability(graph, node, reachability):
    """
    Set the reachability value of a node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    reachability : str
        The reachability value ("unreachable", "uncertain", or "reachable").

    Raises
    ------
    ValueError
        If the reachability value is not valid.
    """
    if reachability not in ("unreachable", "uncertain", "reachable"):
        raise ValueError(reachability + " is not a valid reachability value.")
    set_node_attribute(graph, node, "reachability", reachability)
    set_node_attribute(graph, node, "has_reachability", True)

unset_reachability(graph, node)

Unset the reachability value of a node.

Parameters

graph : grapes Graph The graph containing the node. node : hashable (typically string) The name of the node.

Source code in grapes/reachability.py
def unset_reachability(graph, node):
    """
    Unset the reachability value of a node.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the node.
    node : hashable (typically string)
        The name of the node.
    """
    set_node_attribute(graph, node, "has_reachability", False)

simplify

This module contains functions to simplify the graph, reducing the number of nodes or converting conditional to standard nodes.

convert_all_conditionals_to_trivial_steps(graph, execute_towards_conditions=False)

Convert all conditional nodes in the graph to trivial steps.

Parameters

graph : grapes Graph The graph containing the nodes. execute_towards_conditions : bool, optional Whether to execute the graph towards the conditions until one is found true. Default is False.

Source code in grapes/simplify.py
def convert_all_conditionals_to_trivial_steps(graph, execute_towards_conditions=False):
    """
    Convert all conditional nodes in the graph to trivial steps.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    execute_towards_conditions : bool, optional
        Whether to execute the graph towards the conditions until one is found true. Default is False.
    """
    conditionals = get_all_conditionals(graph)
    for conditional in conditionals:
        convert_conditional_to_trivial_step(
            graph, conditional, execute_towards_conditions
        )

convert_conditional_to_trivial_step(graph, conditional, execute_towards_conditions=False)

Convert a conditional node to a trivial step that returns the dependency corresponding to the true condition.

Parameters

graph : grapes Graph The graph containing the nodes. conditional : hashable (typically string) The name of the conditional node to be converted. execute_towards_conditions : bool, optional Whether to execute the graph towards the conditions until one is found true. Default is False.

Raises

ValueError If no condition is true and there is no default possibility.

Source code in grapes/simplify.py
def convert_conditional_to_trivial_step(
    graph, conditional, execute_towards_conditions=False
):
    """
    Convert a conditional node to a trivial step that returns the dependency corresponding to the true condition.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    conditional : hashable (typically string)
        The name of the conditional node to be converted.
    execute_towards_conditions : bool, optional
        Whether to execute the graph towards the conditions until one is found true. Default is False.

    Raises
    ------
    ValueError
        If no condition is true and there is no default possibility.
    """
    if execute_towards_conditions:
        execute_towards_all_conditions_of_conditional(graph, conditional)

    for index, condition in enumerate(get_conditions(graph, conditional)):
        if get_has_value(graph, condition) and get_value(graph, condition):
            break
    else:  # Happens if loop is never broken, i.e. when no conditions are true
        if (
            len(get_conditions(graph, conditional))
            == len(get_possibilities(graph, conditional)) - 1
        ):
            # We assume that the last possibility is considered a default
            index = -1
        else:
            raise ValueError(
                "Cannot convert conditional " + conditional + " if no condition is true"
            )
    # Get the correct possibility
    selected_possibility = get_possibilities(graph, conditional)[index]

    # Remove all previous edges (the correct one will be readded later)
    for condition in get_conditions(graph, conditional):
        graph._nxdg.remove_edge(condition, conditional)
    for possibility in get_possibilities(graph, conditional):
        graph._nxdg.remove_edge(possibility, conditional)
    # Rewrite node attributes
    nx.set_node_attributes(graph._nxdg, {conditional: starting_node_properties})
    # Add a trivial recipe
    recipe = "trivial_recipe_for_" + conditional
    set_recipe(graph, conditional, recipe)
    set_args(graph, conditional, (selected_possibility,))
    set_kwargs(graph, conditional, dict())

    # Add and connect the recipe
    # Avoid adding existing recipe so as not to overwrite attributes
    if recipe not in graph.nodes:
        graph._nxdg.add_node(recipe, **starting_node_properties)
    set_is_recipe(graph, recipe, True)
    graph._nxdg.add_edge(recipe, conditional)
    # Assign value of identity function to recipe
    set_value(graph, recipe, lambda x: x)

    # Add and connect the possibility
    graph._nxdg.add_edge(selected_possibility, conditional)

simplify_all_dependencies(graph, node, exclude=set())

Simplify all dependencies of a node except those in the exclude set.

Parameters

graph : grapes Graph The graph containing the nodes. node : hashable (typically string) The name of the node to simplify. exclude : set or iterable, optional Dependencies to exclude from simplification. Default is empty set.

Source code in grapes/simplify.py
def simplify_all_dependencies(graph, node, exclude=set()):
    """
    Simplify all dependencies of a node except those in the exclude set.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    node : hashable (typically string)
        The name of the node to simplify.
    exclude : set or iterable, optional
        Dependencies to exclude from simplification. Default is empty set.
    """
    if not isinstance(exclude, set):
        exclude = set(exclude)
    # If a dependency is a source, it cannot be simplified
    exclude |= get_all_sources(graph)
    dependencies = get_args(graph, node) + tuple(get_kwargs(graph, node).values())
    for dependency in dependencies:
        if dependency not in exclude:
            simplify_dependency(graph, node, dependency)

simplify_dependency(graph, node, dependency)

Simplify a dependency of a node by merging its computation into that of the node. For example if the graph g is a->b->c and we simplify b as a dependency of c (calling simplify_dependency(g, c, b)), the graph becomes a->c, with c now computing what b used to compute as well.

Parameters

graph : grapes Graph The graph containing the nodes. node : hashable (typically string) The name of the node that will include in its computation that of the dependency. dependency : hashable (typically string) The name of the dependency to eliminate and merge into the node.

Raises

TypeError If the dependency or its recipe is not a standard node.

Source code in grapes/simplify.py
def simplify_dependency(graph, node, dependency):
    """
    Simplify a dependency of a node by merging its computation into that of the node.
    For example if the graph g is a->b->c and we simplify b as a dependency of c (calling simplify_dependency(g, c, b)), the graph becomes a->c, with c now computing what b used to compute as well.

    Parameters
    ----------
    graph : grapes Graph
        The graph containing the nodes.
    node : hashable (typically string)
        The name of the node that will include in its computation that of the dependency.
    dependency : hashable (typically string)
        The name of the dependency to eliminate and merge into the node.

    Raises
    ------
    TypeError
        If the dependency or its recipe is not a standard node.
    """
    # Make everything a keyword argument. This is the fate of a simplified node
    get_kwargs(graph, node).update(
        {argument: argument for argument in get_args(graph, node)}
    )
    # Build lists of dependencies
    func_dependencies = list(get_kwargs(graph, node).values())
    subfuncs = []
    subfuncs_dependencies = []
    for argument in get_kwargs(graph, node):
        if argument == dependency:
            if get_type(graph, dependency) != "standard":
                raise TypeError(
                    "Simplification only supports standard nodes, while the type of "
                    + dependency
                    + " is "
                    + get_type(graph, dependency)
                )
            if get_type(graph, get_recipe(graph, dependency)) != "standard":
                raise TypeError(
                    "Simplification only supports standard nodes, while the type of "
                    + get_recipe(graph, dependency)
                    + " is "
                    + get_type(graph, get_recipe(graph, dependency))
                )
            subfuncs.append(graph[get_recipe(graph, dependency)])  # Get python function
            subfuncs_dependencies.append(
                list(get_args(graph, dependency))
                + list(get_kwargs(graph, dependency).values())
            )
        else:
            subfuncs.append(function_composer.identity_token)
            subfuncs_dependencies.append([argument])
    # Compose the functions
    graph[get_recipe(graph, node)] = function_composer.function_compose_simple(
        graph[get_recipe(graph, node)],
        subfuncs,
        func_dependencies,
        subfuncs_dependencies,
    )
    # Change edges
    graph._nxdg.remove_edge(dependency, node)
    for argument in get_args(graph, dependency) + tuple(
        get_kwargs(graph, dependency).values()
    ):
        graph._nxdg.add_edge(argument, node, accessor=argument)
    # Update node
    set_args(graph, node, ())
    new_kwargs = get_kwargs(graph, node)
    new_kwargs.update(
        {
            argument: argument
            for argument in get_args(graph, dependency)
            + tuple(get_kwargs(graph, dependency).values())
        }
    )
    new_kwargs = {
        key: value for key, value in new_kwargs.items() if value != dependency
    }
    set_kwargs(graph, node, new_kwargs)

util

This module contains utility functions for grapes. These are the main functions that an user (not designer) should use to interact with a grapes graph.

check_feasibility_of_execution(graph, context, *targets, inplace=False)

Check the feasibility of executing a graph up to the desired targets given a context.

Parameters

graph : grapes Graph Graph of the computation. context : dict Dictionary of the initial context of the computation (input). targets : hashables (typically strings) Indicator of what to compute (desired output). inplace : bool Whether to modify graph and context inplace (default: False).

Returns

feasibility : str One of "reachable", "unreachable", "uncertain". missing_dependencies : set Set of nodes that are missing in the context and prevent the computation from being feasible.

Source code in grapes/util.py
def check_feasibility_of_execution(graph, context, *targets, inplace=False):
    """
    Check the feasibility of executing a graph up to the desired targets given a context.

    Parameters
    ----------
    graph : grapes Graph
        Graph of the computation.
    context : dict
        Dictionary of the initial context of the computation (input).
    targets : hashables (typically strings)
        Indicator of what to compute (desired output).
    inplace : bool
        Whether to modify graph and context inplace (default: False).

    Returns
    -------
    feasibility : str
        One of "reachable", "unreachable", "uncertain".
    missing_dependencies : set
        Set of nodes that are missing in the context and prevent the computation from being feasible.
    """
    # No target is interpreted as compute everything
    if len(targets) == 0:
        targets = graph.get_all_sinks(exclude_recipes=True)

    if not inplace:
        graph = copy.deepcopy(graph)
        context = copy.deepcopy(context)

    clear_reachabilities(graph)
    set_internal_context(graph, context)
    compute_reachability_targets(graph, *targets)
    feasibility = get_worst_reachability(graph, *targets)
    missing_dependencies = set()
    if feasibility in {"unreachable", "uncertain"}:
        for node in graph.nodes:
            if (
                get_topological_generation_index(graph, node) == 0
                and get_has_reachability(graph, node)
                and get_reachability(graph, node) != "reachable"
            ):
                missing_dependencies.add(node)
    return feasibility, missing_dependencies

context_from_file(file_name)

Load a file (any of the supported formats) into a dictionary.

Parameters

file_name : str Path to the file.

Returns

dict Content of the file as dictionary.

Source code in grapes/util.py
def context_from_file(file_name):
    """
    Load a file (any of the supported formats) into a dictionary.

    Parameters
    ----------
    file_name : str
        Path to the file.

    Returns
    -------
    dict
        Content of the file as dictionary.
    """
    supported_formats = ["JSON", "TOML"]
    reading_functions = [context_from_json_file, context_from_toml_file]
    for func in reading_functions:
        try:
            data = func(file_name)
            return data
        except (json.decoder.JSONDecodeError, tomllib.TOMLDecodeError):
            pass
    # If we arrive here, there has been an issue in all reading functions
    raise ValueError(
        "File "
        + file_name
        + " is not valid in any of the supported formats ("
        + ",".join(supported_formats)
        + ")"
    )

context_from_json_file(file_name)

Load a json file into a dictionary.

Parameters

file_name : str Path to the json file.

Returns

dict Content of the file as dictionary.

Source code in grapes/util.py
def context_from_json_file(file_name):
    """
    Load a json file into a dictionary.

    Parameters
    ----------
    file_name : str
        Path to the json file.

    Returns
    -------
    dict
        Content of the file as dictionary.
    """
    with open(file_name, encoding="utf-8") as json_file:
        data = json.load(json_file)
    return data

context_from_toml_file(file_name)

Load a toml file into a dictionary.

Parameters

file_name : str Path to the toml file.

Returns

dict Content of the file as dictionary.

Source code in grapes/util.py
def context_from_toml_file(file_name):
    """
    Load a toml file into a dictionary.

    Parameters
    ----------
    file_name : str
        Path to the toml file.

    Returns
    -------
    dict
        Content of the file as dictionary.
    """
    with open(file_name, "rb") as toml_file:
        data = tomllib.load(toml_file)
    return data

execute_graph_from_context(graph, context, *targets, inplace=False, check_feasibility=True)

Execute a graph up to the desired targets given a context.

Parameters

graph : grapes Graph Graph of the computation. context : dict Dictionary of the initial context of the computation (input). targets : hashables (typically strings) Indicator of what to compute (desired output). inplace : bool Whether to modify graph and context inplace (default: False). check_feasibility : bool Whether to check the feasibility of the computation, which slows performance (default: True).

Returns

grapes Graph Graph with context updated after computation.

Source code in grapes/util.py
def execute_graph_from_context(
    graph, context, *targets, inplace=False, check_feasibility=True
):
    """
    Execute a graph up to the desired targets given a context.

    Parameters
    ----------
    graph : grapes Graph
        Graph of the computation.
    context : dict
        Dictionary of the initial context of the computation (input).
    targets : hashables (typically strings)
        Indicator of what to compute (desired output).
    inplace : bool
        Whether to modify graph and context inplace (default: False).
    check_feasibility : bool
        Whether to check the feasibility of the computation, which slows performance (default: True).

    Returns
    -------
    grapes Graph
        Graph with context updated after computation.
    """
    # No target is interpreted as compute everything
    if len(targets) == 0:
        targets = get_all_sinks(graph, exclude_recipes=True)

    if check_feasibility:
        feasibility, missing_dependencies = check_feasibility_of_execution(
            graph, context, *targets, inplace=inplace
        )
        if feasibility == "unreachable":
            raise ValueError(
                "The requested computation is unfeasible because of the following missing dependencies: "
                + ", ".join(missing_dependencies)
            )
        elif feasibility == "uncertain":
            warnings.warn(
                "The feasibility of the requested computation is uncertain because of the following missing dependencies: "
                + ", ".join(missing_dependencies)
            )

    if not inplace:
        graph = copy.deepcopy(graph)
        context = copy.deepcopy(context)

    set_internal_context(graph, context)
    execute_to_targets(graph, *targets)

    return graph

get_execution_subgraph(graph, context, *targets)

Get the subgraph that would be executed to compute the desired targets given a context.

Parameters

graph : grapes Graph Graph of the computation. context : dict Dictionary of the initial context of the computation (input). targets : hashables (typically strings) Indicator of what to compute (desired output).

Returns

grapes Graph Subgraph that would be executed to compute the desired targets given the context.

Source code in grapes/util.py
def get_execution_subgraph(graph, context, *targets):
    """
    Get the subgraph that would be executed to compute the desired targets given a context.

    Parameters
    ----------
    graph : grapes Graph
        Graph of the computation.
    context : dict
        Dictionary of the initial context of the computation (input).
    targets : hashables (typically strings)
        Indicator of what to compute (desired output).

    Returns
    -------
    grapes Graph
        Subgraph that would be executed to compute the desired targets given the context.
    """
    graph = copy.deepcopy(graph)
    context = copy.deepcopy(context)
    update_internal_context(graph, context)
    path = set()
    for target in targets:
        path = path | get_path_to_target(graph, target)
    return get_subgraph(graph, path)

json_from_graph(graph)

Get a JSON string representing the context of a graph.

Parameters

graph : grapes Graph Graph containing the context to convert to JSON.

Returns

str JSON string that prettily represents the context of the graph.

Source code in grapes/util.py
def json_from_graph(graph):
    """
    Get a JSON string representing the context of a graph.

    Parameters
    ----------
    graph : grapes Graph
        Graph containing the context to convert to JSON.

    Returns
    -------
    str
        JSON string that prettily represents the context of the graph.
    """

    context = get_internal_context(graph, exclude_recipes=True)
    non_serializable_items = {}
    for key, value in context.items():
        try:
            json.dumps(value)
        except:
            non_serializable_items.update({key: str(value)})
    if (
        len(non_serializable_items) > 0
    ):  # We must copy the context, to preserve it, and dump a modified version of it
        res = copy.deepcopy(context)
        res.update(non_serializable_items)
    else:
        res = context
    return json.dumps(res, sort_keys=True, indent=4, separators=(",", ": "))

lambdify_graph(graph, input_keys, target, constants={})

Convert a graph into a function that can be called with the desired inputs and returns the desired output. This function is independent from the rest of grapes, and does not require any other grapes function to work.

Parameters

graph : grapes Graph Graph of the computation. input_keys : list or set of strings Keys in the graph that will be treated as inputs of the function. target : string (or name of a node in the graph) Key in the graph that will be treated as output of the function. constants : dict Keys and values that are assigned to the graph before the function creation and are present when the function is called, default: {}. If a key is both in constants and input_keys, it is treated as input (i.e., the value in constants is ignored).

Returns

function A function that can be called with the desired inputs and returns the desired output.

Source code in grapes/util.py
def lambdify_graph(graph, input_keys, target, constants={}):
    """
    Convert a graph into a function that can be called with the desired inputs and returns the desired output.
    This function is independent from the rest of grapes, and does not require any other grapes function to work.

    Parameters
    ----------
    graph : grapes Graph
        Graph of the computation.
    input_keys : list or set of strings
        Keys in the graph that will be treated as inputs of the function.
    target : string (or name of a node in the graph)
        Key in the graph that will be treated as output of the function.
    constants : dict
        Keys and values that are assigned to the graph before the function creation and are present when the function is called, default: {}. If a key is both in constants and input_keys, it is treated as input (i.e., the value in constants is ignored).

    Returns
    -------
    function
        A function that can be called with the desired inputs and returns the desired output.
    """
    # Copy graph so as not to pollute the original
    operational_graph = copy.deepcopy(graph)
    # Pass all constants to the graph
    update_internal_context(operational_graph, constants)
    # Freeze so that the constants are fixed
    freeze(operational_graph)
    # Unfreeze the input.
    # Note that this has precedence over constants (i.e., if a key is both input and constant, it is treated as input)
    if len(input_keys) > 0:
        unfreeze(operational_graph, *input_keys)
        clear_values(operational_graph, *input_keys)
    # Convert all conditional, progressing to the conditions
    convert_all_conditionals_to_trivial_steps(
        operational_graph, execute_towards_conditions=True
    )
    # Progress as much as possible
    progress_towards_targets(operational_graph, target)
    # The starting point of the computation will include the constants
    initial_keys = set(input_keys) | set(constants.keys())
    # Simplify until the graph is a single function
    while not set(
        get_args(operational_graph, target)
        + tuple(get_kwargs(operational_graph, target).values())
    ).issubset(initial_keys):
        simplify_all_dependencies(operational_graph, target, exclude=initial_keys)
    # Get the function representing the graph
    function = operational_graph[get_recipe(operational_graph, target)]
    # If needed, get a function only of the input keys
    if len(constants) > 0:

        def function_only_input_keys(**kwargs):
            kwargs.update(constants)
            return function(**kwargs)

        return function_only_input_keys
    else:
        return function

wrap_graph_with_function(graph, input_keys, *targets, constants={}, input_as_kwargs=True, output_as_dict=True)

Wrap a the execution of a graph into a function that can be called with the desired inputs and returns the desired outputs.

Parameters

graph : grapes Graph Graph of the computation. input_keys : hashables (typically strings) Keys in the graph that will be treated as inputs of the function. targets : hashables (typically strings) Keys in the graph that will be treated as outputs of the function. constants : dict Keys and values that are assigned to the graph before the function creation and are present when the function is called, default is empty set. If a key is both in constants and input_keys, it is treated as input (i.e., the value in constants is ignored). input_as_kwargs : bool Whether the input of the function is a set of keyword arguments (True) or a list (False), default: True. output_as_dict : bool Whether the output of the function is a dictionary (True) or a list (False), default: True.

Returns

function A function that can be called with the desired inputs and returns the desired outputs.

Source code in grapes/util.py
def wrap_graph_with_function(
    graph, input_keys, *targets, constants={}, input_as_kwargs=True, output_as_dict=True
):
    """
    Wrap a the execution of a graph into a function that can be called with the desired inputs and returns the desired outputs.

    Parameters
    ----------
    graph : grapes Graph
        Graph of the computation.
    input_keys : hashables (typically strings)
        Keys in the graph that will be treated as inputs of the function.
    targets : hashables (typically strings)
        Keys in the graph that will be treated as outputs of the function.
    constants : dict
        Keys and values that are assigned to the graph before the function creation and are present when the function is called, default is empty set. If a key is both in constants and input_keys, it is treated as input (i.e., the value in constants is ignored).
    input_as_kwargs : bool
        Whether the input of the function is a set of keyword arguments (True) or a list (False), default: True.
    output_as_dict : bool
        Whether the output of the function is a dictionary (True) or a list (False), default: True.

    Returns
    -------
    function
        A function that can be called with the desired inputs and returns the desired outputs.
    """
    # Copy graph so as not to pollute the original
    operational_graph = copy.deepcopy(graph)
    # Pass all constants to the graph
    update_internal_context(operational_graph, constants)
    # Freeze so that the constants are fixed
    freeze(operational_graph)
    # Unfreeze the input.
    # Note that this has precedence over constants (i.e., if a key is both input and constant, it is treated as input)
    if len(input_keys) > 0:
        unfreeze(operational_graph, *input_keys)
        clear_values(operational_graph, *input_keys)
    # No target is interpreted as compute everything
    if len(targets) == 0:
        targets = get_all_sinks(operational_graph, exclude_recipes=True)
    # Move as much as possible towards targets
    progress_towards_targets(operational_graph, *targets)
    # Check feasibility
    placeholder_value = 0
    context = {key: placeholder_value for key in input_keys}
    feasibility, missing_dependencies = check_feasibility_of_execution(
        operational_graph, context, *targets
    )
    if feasibility == "unreachable":
        raise ValueError(
            "The requested computation is unfeasible because of the following missing dependencies: "
            + ", ".join(missing_dependencies)
        )
    elif feasibility == "uncertain":
        warnings.warn(
            "The feasibility of the requested computation is uncertain because of the following missing dependencies: "
            + ", ".join(missing_dependencies)
        )

    if input_as_kwargs and output_as_dict:

        def specific_function_input_as_kwargs_output_as_dict(**kwargs):
            # Use for loop rather than dict comprehension because it is a more basic operation
            for key in input_keys:
                operational_graph[key] = kwargs[key]
            execute_to_targets(operational_graph, *targets)
            dict_of_values = get_dict_of_values(operational_graph, targets)
            # Clear values so that the function can be called again
            clear_values(operational_graph)
            if len(dict_of_values.keys()) == 1:
                return dict_of_values[list(dict_of_values.keys())[0]]
            else:
                return dict_of_values

        return specific_function_input_as_kwargs_output_as_dict

    elif input_as_kwargs and not output_as_dict:

        def specific_function_input_as_kwargs_output_as_list(**kwargs):
            # Use for loop rather than dict comprehension because it is a more basic operation
            for key in input_keys:
                operational_graph[key] = kwargs[key]
            execute_to_targets(operational_graph, *targets)
            list_of_values = get_list_of_values(operational_graph, targets)
            # Clear values so that the function can be called again
            clear_values(operational_graph)
            if len(list_of_values) == 1:
                return list_of_values[0]
            else:
                return list_of_values

        return specific_function_input_as_kwargs_output_as_list

    elif not input_as_kwargs and output_as_dict:
        input_keys = list(input_keys)

        def specific_function_input_as_args_output_as_dict(*args):
            # Use for loop rather than dict comprehension because it is a more basic operation
            for i in range(len(input_keys)):
                operational_graph[input_keys[i]] = args[i]
            execute_to_targets(operational_graph, *targets)
            dict_of_values = get_dict_of_values(operational_graph, targets)
            # Clear values so that the function can be called again
            clear_values(operational_graph)
            if len(dict_of_values.keys()) == 1:
                return dict_of_values[list(dict_of_values.keys())[0]]
            else:
                return dict_of_values

        return specific_function_input_as_args_output_as_dict

    else:  # not input_as_kwargs and not output_as_dict
        input_keys = list(input_keys)

        def specific_function_input_as_args_output_as_list(*args):
            # Use for loop rather than dict comprehension because it is a more basic operation
            for i in range(len(input_keys)):
                operational_graph[input_keys[i]] = args[i]
            execute_to_targets(operational_graph, *targets)
            list_of_values = get_list_of_values(operational_graph, targets)
            # Clear values so that the function can be called again
            clear_values(operational_graph)
            if len(list_of_values) == 1:
                return list_of_values[0]
            else:
                return list_of_values

        return specific_function_input_as_args_output_as_list

visualize

This module contains tools that allow the visualization of a graph using graphviz and pygraphviz.

best_text_from_background_color(r, g, b, a=1.0)

Get the best text color (white or black) for a given background color.

Parameters

r : float Red color channel in [0, 1]. g : float Green color channel in [0, 1]. b : float Blue color channel in [0, 1]. a : float, optional Alpha color channel in [0, 1]. Default is 1.0. This value is ignored.

Returns

tuple Tuple of RGBA values representing pure white (1, 1, 1, 1) or pure black (0, 0, 0, 1) depending on luminance of input color.

Source code in grapes/visualize.py
def best_text_from_background_color(r, g, b, a=1.0):
    """
    Get the best text color (white or black) for a given background color.

    Parameters
    ----------
    r : float
        Red color channel in [0, 1].
    g : float
        Green color channel in [0, 1].
    b : float
        Blue color channel in [0, 1].
    a : float, optional
        Alpha color channel in [0, 1]. Default is 1.0. This value is ignored.

    Returns
    -------
    tuple
        Tuple of RGBA values representing pure white (1, 1, 1, 1) or pure black (0, 0, 0, 1) depending on luminance of input color.
    """
    luminance = 0.212 * r + 0.701 * g + 0.087 * b
    return (1, 1, 1, 1) if luminance < 0.5 else (0, 0, 0, 1)

draw_to_file(graphviz_graph, filename, format='pdf', prog='dot')

Draw a pygraphviz AGraph to a file.

Parameters

graphviz_graph : pygraphviz.AGraph The graph to be drawn. filename : str The name of the file where to save the drawing. format : str, optional The format of the output file (default: "pdf"). See graphviz documentation for supported formats. prog : str, optional The layout program to use (default: "dot"). See graphviz documentation for supported programs.

Source code in grapes/visualize.py
def draw_to_file(graphviz_graph, filename, format="pdf", prog="dot"):
    """
    Draw a pygraphviz AGraph to a file.

    Parameters
    ----------
    graphviz_graph : pygraphviz.AGraph
        The graph to be drawn.
    filename : str
        The name of the file where to save the drawing.
    format : str, optional
        The format of the output file (default: "pdf"). See graphviz documentation for supported formats.
    prog : str, optional
        The layout program to use (default: "dot"). See graphviz documentation for supported programs.
    """
    # Process filename
    if ("." + format) not in filename:
        filename += "." + format
    graphviz_graph.layout(prog=prog)
    graphviz_graph.draw(filename, format=format)

draw_to_screen(graphviz_graph, format='png', prog='dot')

Draw a pygraphviz AGraph to an image and display it using matplotlib.

Parameters

graphviz_graph : pygraphviz.AGraph The graph to be drawn. format : str, optional The format of the output image (default: "png"). See graphviz documentation for supported formats. prog : str, optional The layout program to use (default: "dot"). See graphviz documentation for supported programs.

Source code in grapes/visualize.py
def draw_to_screen(graphviz_graph, format="png", prog="dot"):
    """
    Draw a pygraphviz AGraph to an image and display it using matplotlib.

    Parameters
    ----------
    graphviz_graph : pygraphviz.AGraph
        The graph to be drawn.
    format : str, optional
        The format of the output image (default: "png"). See graphviz documentation for supported formats.
    prog : str, optional
        The layout program to use (default: "dot"). See graphviz documentation for supported programs.
    """
    graphviz_graph.layout(prog=prog)
    buffer = io.BytesIO()
    graphviz_graph.draw(buffer, format=format)
    buffer.seek(0)
    img = Image.open(buffer)
    plt.imshow(img)
    plt.axis("off")  # Hide axes
    plt.show()

get_graphviz_digraph(graph, hide_recipes=False, pretty_names=False, include_values=False, color_mode='none', colormap='viridis', **attrs)

Create a pygraphviz AGraph from a grapes graph.

Parameters

graph : grapes Graph The graph to visualize. hide_recipes : bool, optional If True, recipe nodes are hidden. Default is False. pretty_names : bool, optional If True, node labels are prettified. Default is False. include_values : bool, optional If True, node values are included in labels. Default is False. color_mode : str, optional Coloring mode for nodes ("none", "by_generation", "sources_and_sinks", default is "none"). colormap : str, optional Name of matplotlib colormap to use. Default is "viridis". **attrs Additional attributes for the AGraph.

Returns

pygraphviz.AGraph The constructed pygraphviz AGraph.

Source code in grapes/visualize.py
def get_graphviz_digraph(
    graph,
    hide_recipes=False,
    pretty_names=False,
    include_values=False,
    color_mode="none",
    colormap="viridis",
    **attrs,
):
    """
    Create a pygraphviz AGraph from a grapes graph.

    Parameters
    ----------
    graph : grapes Graph
        The graph to visualize.
    hide_recipes : bool, optional
        If True, recipe nodes are hidden. Default is False.
    pretty_names : bool, optional
        If True, node labels are prettified. Default is False.
    include_values : bool, optional
        If True, node values are included in labels. Default is False.
    color_mode : str, optional
        Coloring mode for nodes ("none", "by_generation", "sources_and_sinks", default is "none").
    colormap : str, optional
        Name of matplotlib colormap to use. Default is "viridis".
    **attrs
        Additional attributes for the AGraph.

    Returns
    -------
    pygraphviz.AGraph
        The constructed pygraphviz AGraph.
    """
    # Get a graphviz AGraph
    g = nx.drawing.nx_agraph.to_agraph(graph._nxdg)
    # Add attributes to the AGraph
    g.graph_attr.update(**attrs)
    # Save some values that will be useful later
    max_topological_generation_index = len(get_topological_generations(graph)) - 1
    sources = get_all_sources(graph)
    sinks = get_all_sinks(graph)
    cmap = matplotlib.colormaps[colormap]

    for node_name in g.nodes():
        new_attrs = {}
        node = graph._nxdg.nodes[node_name]

        # Remove recipes if needed, or eliminate attribute of function
        if node["is_recipe"] and hide_recipes:
            g.remove_node(node_name)
            continue
        elif node["is_recipe"] and node["has_value"]:
            new_attrs.update(value="function")

        # Prettify label if required
        label = prettify_label(node_name) if pretty_names else node_name

        # Add values to the label if required
        if include_values and node.attr["has_value"]:
            if isinstance(node.attr["value"], float):
                value_in_label = "{:.2e}".format(node.attr["value"])
            else:
                value_in_label = str(node.attr["value"])
            label += "\n" + value_in_label.partition("\n")[0]
            if value_in_label.find("\n") != -1:
                label += "\n..."
        new_attrs.update(label=label)

        # Manipulate shapes
        if node["is_recipe"]:
            shape = "ellipse"
        elif node["type"] == "conditional":
            shape = "diamond"
        else:
            shape = "box"
        new_attrs.update(shape=shape)

        # Manipulate colors
        must_be_colored = False
        color_rgba = (1, 1, 1, 1)  # Default to white
        if color_mode.lower() == "by_generation":
            topological_generation_index = get_topological_generation_index(
                graph, node_name
            )
            # The __call__ method of the colormap returns a tuple of rgba values in [0, 1]
            # We call it passing the ratio between the topological_generation_index of this node and the max of the graph
            color_rgba = cmap(
                topological_generation_index / max_topological_generation_index
            )
            must_be_colored = True
        elif color_mode.lower() == "sources_and_sinks":
            if node_name in sources:
                color_rgba = cmap(0.0)
                must_be_colored = True
            elif node_name in sinks:
                color_rgba = cmap(1.0)
                must_be_colored = True
        if must_be_colored:
            # Note that graphviz wants colors in hex form
            new_attrs.update(
                style="filled",
                fillcolor=hex_string_from_rgba(*color_rgba),
                fontcolor=hex_string_from_rgba(
                    *best_text_from_background_color(*color_rgba)
                ),
            )
        # Pass these attributes to the actual AGraph
        g.get_node(node_name).attr.update(new_attrs)

        # Handle edge shapes
        if node["type"] == "standard" and "recipe" in node:
            # This condition might be false for example because of hide_recipes
            if node["recipe"] in g.nodes():
                g.get_edge(node["recipe"], node_name).attr.update(arrowhead="dot")
        elif node["type"] == "conditional":
            for condition in node["conditions"]:
                if condition in g.nodes():
                    g.get_edge(condition, node_name).attr.update(arrowhead="diamond")

    # Return the AGraph (no layout is computed yet)
    return g

hex_string_from_rgba(r, g, b, a)

Get a formatted hex string from RGBA values.

Parameters

r : float Red color channel in [0, 1]. g : float Green color channel in [0, 1]. b : float Blue color channel in [0, 1]. a : float Alpha color channel in [0, 1].

Returns

str Formatted hex string starting with # and then 8 hex characters (4 bytes).

Source code in grapes/visualize.py
def hex_string_from_rgba(r, g, b, a):
    """
    Get a formatted hex string from RGBA values.

    Parameters
    ----------
    r : float
        Red color channel in [0, 1].
    g : float
        Green color channel in [0, 1].
    b : float
        Blue color channel in [0, 1].
    a : float
        Alpha color channel in [0, 1].

    Returns
    -------
    str
        Formatted hex string starting with # and then 8 hex characters (4 bytes).
    """
    # Convert float values in [0, 1] to hex strings of two characters, e.g. 1->ff
    r_hex = f"{int(r*255):02x}"
    g_hex = f"{int(g*255):02x}"
    b_hex = f"{int(b*255):02x}"
    a_hex = f"{int(a*255):02x}"
    return "#" + r_hex + g_hex + b_hex + a_hex

prettify_label(name)

Prettify a node label by capitalizing and replacing underscores with spaces.

Parameters

name : str The original node name.

Returns

str The prettified label.

Source code in grapes/visualize.py
def prettify_label(name):
    """
    Prettify a node label by capitalizing and replacing underscores with spaces.

    Parameters
    ----------
    name : str
        The original node name.

    Returns
    -------
    str
        The prettified label.
    """
    return "".join(
        c.upper() if ((i > 0 and name[i - 1] == "_") or i == 0) else c
        for i, c in enumerate(name)
    ).replace("_", " ")

write_dotfile(graphviz_graph, filename)

Write a pygraphviz AGraph to a dot file.

Parameters

graphviz_graph : pygraphviz.AGraph The graph to be written. filename : str The name of the file where to save the dot file.

Source code in grapes/visualize.py
def write_dotfile(graphviz_graph, filename):
    """
    Write a pygraphviz AGraph to a dot file.

    Parameters
    ----------
    graphviz_graph : pygraphviz.AGraph
        The graph to be written.
    filename : str
        The name of the file where to save the dot file.
    """
    graphviz_graph.write(filename)

write_string(graphviz_graph)

Get the string representation of a pygraphviz AGraph.

Parameters

graphviz_graph : pygraphviz.AGraph The graph to be converted to string.

Returns

str The string representation of the graph in dot format.

Source code in grapes/visualize.py
def write_string(graphviz_graph):
    """
    Get the string representation of a pygraphviz AGraph.

    Parameters
    ----------
    graphviz_graph : pygraphviz.AGraph
        The graph to be converted to string.

    Returns
    -------
    str
        The string representation of the graph in dot format.
    """
    return graphviz_graph.string()

options: show_submodules: true