turbograph package#

TurboGraph is a simple Python library, for quick dependency-based computation.

TurboGraph automatically builds a dependency graph from function argument names or explicit specifications, ensuring computations run in the correct order.

Here’s a simple example showing how TurboGraph automatically infers dependencies:

from turbograph import compute

specifications = {
    "a": 2,
    "sum": lambda a, b: a + b,  # Depends on "a" and "b"
}

result = compute(specifications, ["sum"], {"b": 3})
print(result)  # {"sum": 5}

TurboGraph analyzes the function signatures and determines that "sum" depends on "a" and "b", executing the computations in the correct order.

turbograph.build_graph(raw_specifications, vertices=None, *, call_mode='args', backend=None)[source]#

Build a validated directed acyclic graph (DAG) from raw vertex specifications.

This function processes raw vertex specifications to construct a dependency graph. Each vertex represents a quantity, and directed edges indicate dependencies. The graph is validated to ensure it is acyclic.

Return type:

GraphWrapper[Any, TypeVar(V, bound= Hashable)]

Parameters:
  • raw_specifications (Mapping[V, RawVertexSpecification[V]]) –

    A mapping of vertex names to their specifications, defining how each vertex is computed and its dependencies. Specifications can take one of four forms:

    • Function: The function’s signature determines dependencies:

      • If call_mode='args', positional arguments are considered predecessors.

      • If call_mode='kwargs', keyword arguments are considered predecessors.

    • Value: A non-callable value for the vertex.

    • Dictionary:
      • func is an optional function computing the vertex (default: None).

      • predecessors is an optional list of dependency names (default: ()).

      • value is an optional precomputed value (default: turbograph.NA).

    • Sequence:: A tuple of up to three elements (func, predecessors, value).

  • vertices (Iterable[V] | None) – An optional subset of vertices to include in the graph. If None, all vertices in raw_specifications are included. Otherwise, only the specified vertices and their ancestors are retained.

  • call_mode (CallMode) –

    The mode used for passing input values to functions, stored as a graph attribute:

    • "args" (default): Inputs are passed as positional arguments.

    • "kwargs": Inputs are passed as keyword arguments.

    • "arg": Inputs are passed as a single dictionary argument.

  • backend (Backend | None) – The graph backend to use. If None, the first available backend is used.

Returns:

A GraphWrapper object representing the constructed dependency graph.

The graph contains:

  • Vertices with attributes:
    • "func": Function computing the vertex (or None if absent).

    • "value": Precomputed value, or a default placeholder turbograph.NA if absent.

    • "predecessors": Ordered list of predecessor vertices.

  • Directed edges representing dependencies.

  • The call_mode stored as a graph attribute.

Raises:
  • ValueError – If the specified dependencies create a cyclic graph.

  • ValueError – If the graph contains isolated vertices.

Example

Construct a simple graph computing a - b:

>>> graph = build_graph({"sub": lambda a, b: a - b})
>>> compute_from_graph(graph, values={"a": 5, "b": 3}, vertices=["sub"])
{'sub': 2}

Construct a graph with precomputed values:

>>> graph = build_graph(
...     {"a": 10, "b": 5, "sub": lambda a, b: a - b}, vertices=["sub"]
... )
>>> compute_from_graph(graph)
{'a': 10, 'b': 5, 'sub': 5}
turbograph.compute(raw_specifications, vertices=None, values=None, funcs=None, *, call_mode='args', auto_prune=True, backend=None)[source]#

Compute values from vertex specifications.

This function constructs a directed acyclic graph (DAG) from the given raw_specifications and determines the correct execution order for computing values. It allows users to specify predefined values or override functions for specific vertices.

Return type:

dict[TypeVar(V, bound= Hashable), Any]

Parameters:
  • raw_specifications (Mapping[V, RawVertexSpecification[V]]) –

    A mapping of vertex names to their specifications, defining how each vertex is computed and its dependencies. Specifications can take one of four forms:

    • Function: The function’s signature determines dependencies:

      • If call_mode='args', positional arguments are considered predecessors.

      • If call_mode='kwargs', keyword arguments are considered predecessors.

    • Value: A non-callable value for the vertex.

    • Dictionary:
      • func is an optional function computing the vertex (default: None).

      • predecessors is an optional list of dependency names (default: ()).

      • value is an optional precomputed value (default: turbograph.NA).

    • Sequence:: A tuple of up to three elements (func, predecessors, value).

  • vertices (Iterable[V] | None) – The set of vertices to compute. If specified, only the listed vertices and their dependencies are computed.

  • values (Mapping[V, VertexValue] | None) – A mapping of vertex names to pre-defined values. If a vertex has an entry in this mapping, the given value is used instead of computing it from the graph.

  • funcs (Mapping[V, VertexFunc] | None) – A mapping of vertex names to functions. If a vertex has an entry in this mapping, the provided function is used instead of the one in the graph.

  • call_mode (CallMode) –

    Determines how dependencies are passed to functions

    • "args" (default): Dependencies are passed as positional arguments.

    • "kwargs": Dependencies are passed as keyword arguments.

    • "arg": All dependencies are passed as a single dictionary argument.

  • auto_prune (bool) – If True, automatically removes intermediate vertices that are no longer needed after computation to optimize memory usage.

  • backend (Backend | None) – The graph backend to use. If None, the first available backend is

  • used.

Returns:

A mapping from vertex names to their computed values.

Raises:
  • ValueError – If the specified dependencies introduce a cyclic dependency,

  • preventing execution.

Example

Compute the sum of two numbers:

>>> compute({"x": lambda: 3, "y": lambda: 4, "sum": lambda x, y: x + y})
{'x': 3, 'y': 4, 'sum': 7}

Compute only the sum, supplying values for a and b, and explicitly defining dependencies:

>>> compute(
...     {"sum": (lambda x, y: x + y, ["a", "b"])}, ["sum"], {"a": 1, "b": 2}
... )
{'sum': 3}

Use the "arg" call mode to pass dependencies as a single dictionary argument:

>>> compute(
...     {
...         "sum": {
...             "func": lambda v: v["x"] + v["y"],
...             "predecessors": ["x", "y"],
...         },
...     },
...     ["sum"],
...     values={"x": 1, "y": 2},
...     call_mode="arg",
... )
{'sum': 7}
turbograph.compute_from_graph(graph, vertices=None, values=None, funcs=None, *, call_mode=None, auto_prune=True)[source]#

Compute values in a dependency graph.

This function evaluates the specified vertices in a directed acyclic graph (DAG), ensuring that computations follow the correct dependency order. It allows overriding values, updating functions dynamically, and pruning unnecessary intermediate values.

Return type:

dict[TypeVar(V, bound= Hashable), Any]

Parameters:
  • graph (GraphWrapper[Any, V]) – The dependency graph.

  • vertices (Iterable[V] | None) –

    The set of vertices to compute.

    • If None, all vertices in the graph are computed.

    • If specified, only the listed vertices and their dependencies are computed.

  • values (Mapping[V, VertexValue] | None) – A mapping from vertex names to precomputed values. If provided, these values override the existing ones in the graph.

  • funcs (Mapping[V, VertexFunc] | None) – A mapping from vertex names to updated functions. If provided, these functions override the existing ones in the graph.

  • call_mode (CallMode | None) –

    The mode used to pass dependencies to functions. If None, the mode is inferred from the graph.

    • "args": Dependencies are passed as positional arguments.

    • "kwargs": Dependencies are passed as keyword arguments.

    • "arg": Dependencies are passed as a single dictionary.

  • auto_prune (bool) – If True, automatically removes intermediate vertices that are no longer needed after computation to optimize memory usage.

Returns:

A dictionary mapping vertex names to their computed values.

Raises:

ValueError – If cycles are detected in the graph.

Example

Compute values from a dependency graph.

>>> from turbograph import compute_from_graph, build_graph
>>> deps = {"a": [], "b": [], "c": ["a", "b"], "d": ["c"]}
>>> funcs = {
...     "a": lambda: 5,
...     "b": lambda: 10,
...     "c": lambda a, b: a + b,
...     "d": lambda c: c * 2,
... }
>>> graph = build_graph(deps, funcs=funcs)
>>> compute_from_graph(graph)
{'a': 5, 'b': 10, 'c': 15, 'd': 30}

Override a vertex value before computation:

>>> compute_from_graph(graph, values={"a": 7})
{'a': 7, 'b': 10, 'c': 17, 'd': 34}

Modify a function before computation:

>>> compute_from_graph(graph, funcs={"c": lambda a, b: a * b})
{'a': 5, 'b': 10, 'c': 50, 'd': 100}

Compute only a specific vertex:

>>> compute_from_graph(graph, vertices=["c"])
{'c': 50}

Notes

If both a function update and a precomputed value are provided for a vertex, the precomputed value takes precedence.

turbograph.rebuild_graph(graph, vertices=None, values=None, funcs=None, *, reduce=False, call_mode=None)[source]#

Rebuild an existing dependency graph with new functions, values, or vertices.

This function allows modifying a computation graph by: :rtype: TypeVar(GW, bound= GraphWrapper[Any, Any])

  • Retaining only a subset of vertices.

  • Updating functions assigned to vertices.

  • Assigning or overriding precomputed values.

  • Setting a new function call mode.

  • Reducing the graph by treating known values as constants.

Parameters:
  • graph (GW) – The dependency graph to update.

  • vertices (Iterable[V] | None) – The set of vertices to retain. If specified, only these vertices and their ancestors are kept.

  • values (Mapping[V, Any | NACLS] | None) – A mapping from vertex names to precomputed values. If provided, these values override the existing ones in the graph.

  • funcs (Mapping[V, Callable[..., Any] | None] | None) – A mapping from vertex names to updated functions. If provided, these functions override the existing ones in the graph.

  • reduce (bool) – If True, removes inbound edges for vertices with known values. This treats these vertices as constants and prevents unnecessary recomputation.

  • call_mode (CallMode | None) –

    The mode used to pass inputs to functions. If None, the call mode remains unchanged.

    • "args" (default): Dependencies are passed as positional arguments.

    • "kwargs": Dependencies are passed as keyword arguments.

    • "arg": Dependencies are passed as a single dictionary.

  • reduce – If True, removes inbound edges for vertices with known values, treating them as constants. This ensures that these vertices no longer depend on their predecessors, preventing unnecessary computations

Returns:

The updated graph object with the modified functions, values, and vertices.

Return type:

GW

Example

Update a graph with new functions and values.

>>> from turbograph import rebuild_graph, compute_from_graph, build_graph
>>> graph = build_graph(
...     {"sum": ["a", "b"]},
...     funcs={"sum": lambda a, b: a + b},
...     values={"a": 1, "b": 2},
... )
>>> compute_from_graph(graph)  # 1 + 2 = 3
{'a': 1, 'b': 2, 'sum': 3}
>>> updated_graph = rebuild_graph(
...     graph, funcs={"sum": lambda a, b: a * b}, values={"a": 10}
... )
>>> compute_from_graph(updated_graph)  # 10 * 2 = 20
{'a': 10, 'b': 2, 'sum': 20}

Reduce the graph to retain only “b” and its dependencies.

>>> graph = build_graph(
...     {"b": ["a"], "c": ["b"]},
...     funcs={"b": lambda x: x + 1, "c": lambda x: x + 2},
...     values={"a": 1},
... )
>>> smaller_graph = rebuild_graph(graph, vertices=["b"])
>>> compute_from_graph(smaller_graph)
{'a': 1, 'b': 2}

Notes

  • If both a function update and a precomputed value are provided for a vertex, the precomputed value takes precedence.

  • If vertices is specified, computations are limited to those vertices and their ancestors.

  • If reduce is enabled, vertices with known values will not be recomputed.

turbograph.NA = <NA>#

Sentinel value representing a missing vertex value.

This singleton is used to distinguish between explicitly set None values and missing values. It helps differentiate uninitialized vertex attributes from those intentionally assigned None.