graph.dag
- class dolfinx_graph_ad.graph.dag.Graph
Bases:
objectClass for the computational graph
The computational graph is a directed acyclic graph (DAG) that represents the operations, objects and dependencies in a forward simulation in DOLFINx.
Example
The graph object can be initialised and the default nodes and edges can be added as follows: >>> graph = Graph() >>> node1 = Node(object_1, name = “Node 1”) >>> node2 = Node(object_2, name = “Node 2”) >>> edge = Edge(node1, node2) >>> graph.add_node(node1) >>> graph.add_node(node2) >>> graph.add_edge(edge)
The derivatives can then be caculated using the backpropagation method: >>> graph.backprop(object_2, object_1)
- add_edge(edge: Edge)
Add an edge to the graph
- Parameters:
edge (Edge) – The edge to be added to the graph
- Raises:
ValueError – If an edge already connects the predecessor to the successor
Note
Both nodes of the edge have to be part of the graph, since the graph is what keeps them alive: an edge references its successor weakly, so that the graph stays free of reference cycles.
- add_node(node: Node)
Add a node to the graph
- Parameters:
node (Node) – The node to be added to the graph
- backprop(outputs: Any | Sequence[Any], inputs: Any | Sequence[Any] | None = None, grad_outputs: Any | Sequence[Any] = 1.0) tuple[Any, ...] | None
Perform backpropagation in the graph
The gradients of all previous calls are reset before the propagation is started.
Multiple outputs produce one gradient of their weighted sum per control. For scalar objectives J_j and seeds s_j, the gradient for control m_i is sum_j s_j * dJ_j/dm_i. To obtain separate gradients for each objective, call backprop once per objective with all requested controls.
If controls are given, only the edges on the paths from the controls to the outputs are executed, in a single propagation. The propagation therefore ends at the controls, whose gradients are stored and returned, even if a control is an intermediate node of the graph. A control that depends on another control stores its gradient and also passes it on, so that each control receives its total derivative.
If no control is given, all edges the outputs depend on are executed and the propagation continues until it reaches the leaves of this dependency subgraph. Gradients are then stored only in these leaves; intermediate nodes are passed through without storing their gradients. The gradients can be read afterwards from
Node.gradof the respective nodes.- Parameters:
outputs – The recorded object to differentiate, or its exact Node, or a sequence of them. An object selects its latest version; a Node selects that version.
inputs – A control object or Node, or a sequence of control objects and Nodes in the desired return order. Defaults to None. If None, the propagation is carried out down to the dependency leaves of the outputs.
grad_outputs (float or PETSc.Vec, or a sequence of them, optional) – The seed, i.e. the adjoint value the propagation is started with, or one per output.
- Returns:
One gradient of the weighted output sum per requested control, in control order. An entry is None for a control no output depends on. Without controls, returns None and stores leaf gradients. A PETSc.Vec gradient has the control’s vector layout, including ghost entries, but only its entries owned by the calling rank are valid.
- Return type:
tuple or None
- Raises:
TypeError – If a control does not represent a numerical value and can therefore not store a gradient.
Example
Given two recorded scalar objectives and three recorded controls:
>>> controls = (m1, m2, m3) >>> summed = graph_.backprop((J1, J2), controls) >>> weighted = graph_.backprop((J1, J2), controls, grad_outputs=(1.0, 0.5))
summed and weighted each contain three gradients.
- capture(value)
Reference the exact version an operation reads, see
save().- Parameters:
value – A Function or Constant, or an exact node owned by this graph. Objects resolve to their latest recorded version; exact nodes retain their version. Untracked values remain constant for this graph and are neither registered nor copied.
- Returns:
A weak reference to the node, whose snapshot holds its saved value, or the value itself when it is untracked.
- property edges: ValuesView[Edge]
The recorded edges, in insertion order.
- Returns:
A live view of all edges. Add edges with
add_edge().- Return type:
ValuesView
- get_dependencies(end_nodes: Sequence[AbstractNode]) set[Edge]
Get the edges of all operations the end nodes are the result of
- Parameters:
end_nodes (Sequence[AbstractNode]) – The end nodes, whose dependencies are collected together.
- Returns:
The edges into the end nodes and into the nodes they depend on, see
get_path().- Return type:
set[Edge]
- get_node(value, version=None) AbstractNode | None
Get the node of a recorded object or an exact node owned by this graph.
- Parameters:
value – The recorded object, mesh or exact node.
version (int, optional) – The recorded version of an object. Defaults to its latest version. Must be None when value is an exact node, which already identifies its version.
- Returns:
The matching node, or None if it is not recorded in this graph. An exact node must match by identity.
- Return type:
AbstractNode or None
- Raises:
TypeError – If a non-None version is supplied with an exact node.
- get_path(start_nodes: Sequence[AbstractNode], end_nodes: Sequence[AbstractNode]) set[Edge]
Get the edges on the paths from the start nodes to the end nodes
- Parameters:
start_nodes (Sequence[AbstractNode]) – The start nodes, whose paths are collected together.
end_nodes (Sequence[AbstractNode]) – The end nodes, whose paths are collected together.
- Returns:
The edges along the paths.
- Return type:
set[Edge]
- property nodes: ValuesView[AbstractNode]
The recorded nodes, in insertion order.
- Returns:
A live view of all versions. Add nodes with
add_node().- Return type:
ValuesView
- property nx_graph: DiGraph
The cached NetworkX representation of the graph.
Nodes themselves are the keys. The representation is cached and cleared when the graph is deleted, so it is empty if kept past the graph.
- Returns:
The networkx graph representation of the graph
- Return type:
nx_graph (nx.DiGraph)
- print(detailed=False) None
Print the graph structure
- Parameters:
detailed (bool, optional) – Whether to print the graph in detailed mode. Defaults to False.
- release()
Release the values saved in the graph
- reset_grads()
Reset the gradients in the graph
- save(node: Node)
Save a new version before any later recorded call overwrites it.
- Parameters:
node – The Function or Constant version to save.
Note
The saved copy includes owned and ghost entries. Every rank saves and releases corresponding versions in the same order.
- track(value) Node
Register and save an existing Function or Constant at its current value.
- Parameters:
value – A DOLFINx Function or Constant owned by the caller.
- Returns:
The control node. A value that is already recorded in this graph returns its latest node unchanged.
- Return type:
- Raises:
TypeError – If the value is not a DOLFINx Function or Constant.
Note
Saving preserves owned and ghost entries locally.
- visualise(ax=None, style: str | Callable = 'planar', print_edge_labels=True, path=None)
Visualise the graph
- Parameters:
ax (matplotlib.axes.Axes, optional) – The axes to draw the graph in. If None, a new figure and axes are created. Defaults to None.
style (str, optional) – The style of the visualisation. Defaults to “planar”.
print_edge_labels (bool, optional) – Whether to print the edge labels. Defaults to True.
path (set, optional) – The set of edges to be highlighted in the visualisation. Defaults to None.
- Returns:
The axes containing the visualisation of the graph.
- Return type:
matplotlib.axes.Axes