Graph transformations and mappings - functor operations on graphs.
This module provides operations that transform graphs while preserving or reshaping their structure. These are useful for adapting graph data types, creating derived graphs, and preparing graphs for specific algorithms.
Available Transformations
| Transformation | Function | Complexity | Use Case |
|---|---|---|---|
| Transpose | transpose/1 | $\mathcal{O}(1)$ | Reverse edge directions |
| Add Self-Loops | add_self_loops/2 | $\mathcal{O}(V)$ | Add reflexivity to nodes |
| Remove Self-Loops | remove_self_loops/1 | $\mathcal{O}(E)$ | Remove reflexive edges |
| To Directed | to_directed/1 | $\mathcal{O}(1)$ | Convert undirected to directed |
| To Undirected | to_undirected/2 | $\mathcal{O}(E)$ | Mirror directed edges symmetrically |
| Map Nodes | map_nodes/2 | $\mathcal{O}(V)$ | Transform node data |
| Map Nodes Indexed | map_nodes_indexed/2 | $\mathcal{O}(V)$ | Transform node data with node ID |
| Map Nodes Async | map_nodes_async/3 | $\mathcal{O}(V / \text{cores})$ | Parallel node transforms |
| Relabel Nodes | relabel_nodes/2 | $\mathcal{O}(V + E)$ | Rename node IDs |
| Normalize Node IDs | normalize_node_ids/1 | $\mathcal{O}(V \log V + E)$ | Reindex node IDs to 0..n-1 |
| Update Node | update_node/4 | $\mathcal{O}(1)$ | Update specific node payload |
| Filter Nodes | filter_nodes/2 | $\mathcal{O}(V + E)$ | Subgraph node predicate filtering |
| Filter Nodes Indexed | filter_nodes_indexed/2 | $\mathcal{O}(V + E)$ | Subgraph node/ID predicate filtering |
| Map Edges | map_edges/2 | $\mathcal{O}(E)$ | Transform edge weights |
| Map Edges Indexed | map_edges_indexed/2 | $\mathcal{O}(E)$ | Transform edge weights with endpoints |
| Map Edges Async | map_edges_async/3 | $\mathcal{O}(E / \text{cores})$ | Parallel edge weight transforms |
| Update Edge | update_edge/5 | $\mathcal{O}(1)$ | Update specific edge weight |
| Filter Edges | filter_edges/2 | $\mathcal{O}(E)$ | Remove edges by predicate |
| Merge | merge/2 | $\mathcal{O}(V + E)$ | Union two graphs |
| Complement | complement/2 | $\mathcal{O}(V^2 + E)$ | Inverse graph edges |
| Subgraph | subgraph/2 | $\mathcal{O}(V + E)$ | Extract subset of nodes |
| Ego Graph | ego_graph/4 | $\mathcal{O}(V + E)$ | Extract k-hop neighborhood |
| Contract | contract/4 | $\mathcal{O}(\text{deg}(a) + \text{deg}(b))$ | Merge node pair |
| Quotient Graph | quotient_graph/4 | $\mathcal{O}(V + E)$ | Condense partition blocks |
| Transitive Closure | transitive_closure/1 | $\mathcal{O}(V \cdot (V + E))$ | Compute all reachable pairs |
| Transitive Reduction | transitive_reduction/1 | $\mathcal{O}(V \cdot (V + E))$ | Compute minimal reachability DAG |
The O(1) Transpose Operation
Due to Yog's dual-map representation (storing both outgoing and incoming edges), transposing a graph is a single pointer swap - dramatically faster than $\mathcal{O}(E)$ implementations in traditional adjacency list libraries.
Functor Laws
The mapping operations satisfy standard functor laws:
- Identity:
map_nodes(g, fn x -> x end) == g - Composition:
map_nodes(map_nodes(g, f), h) == map_nodes(g, fn x -> h.(f.(x)) end)
Summary
Functions
Adds self-loops (edges from a node to itself) to all nodes in the graph.
Returns the complement of a graph.
Contracts an edge by merging node b into node a.
Returns the ego graph of a node within radius hops.
Filters edges by a predicate receiving (src, dst, weight).
Filters nodes by a predicate, automatically pruning connected edges.
Filters nodes by a predicate that receives (node_id, node_data).
Transforms edge weights using a function, preserving graph structure.
Transforms edge weights using a function in parallel.
Transforms edge weights using a function that receives (src, dst, weight).
Transforms node data using a function, preserving graph structure.
Transforms node data using a function in parallel.
Transforms node data using a function that also takes the node ID.
Combines two graphs, with the second graph's data taking precedence on conflicts.
Normalizes all node IDs to a continuous range of integers 0..n-1.
Contracts nodes according to a partition map, producing a quotient graph.
Relabels all node IDs in the graph using a mapping function.
Removes all self-loops (edges from a node to itself) from the graph.
Extracts a subgraph containing only the specified nodes and their connecting edges.
Converts an undirected graph to a directed graph.
Converts a directed graph to an undirected graph using resolve for weight conflicts.
Computes the transitive closure of the graph.
Computes the transitive reduction of a DAG.
Reverses the direction of every edge in the graph (graph transpose).
Updates a specific edge's weight using an updater function.
Updates a specific node's data using an updater function.
Functions
@spec add_self_loops(Yog.Graph.t(), term()) :: Yog.Graph.t()
Adds self-loops (edges from a node to itself) to all nodes in the graph.
Existing self-loops are kept as-is. New self-loops are created with the
supplied default_weight.
Time Complexity: O(V)
@spec complement(Yog.Graph.t(), term()) :: Yog.Graph.t()
Returns the complement of a graph.
Time Complexity: O(V² + E)
@spec contract( Yog.Graph.t(), Yog.node_id(), Yog.node_id(), (term(), term() -> term()) ) :: Yog.Graph.t()
Contracts an edge by merging node b into node a.
Time Complexity: O(deg(a) + deg(b))
@spec ego_graph(Yog.Graph.t(), Yog.node_id(), non_neg_integer(), keyword()) :: Yog.Graph.t()
Returns the ego graph of a node within radius hops.
Time Complexity: O(V + E)
@spec filter_edges(Yog.Graph.t(), (Yog.node_id(), Yog.node_id(), term() -> boolean())) :: Yog.Graph.t()
Filters edges by a predicate receiving (src, dst, weight).
Time Complexity: O(E)
@spec filter_nodes(Yog.Graph.t(), (term() -> boolean())) :: Yog.Graph.t()
Filters nodes by a predicate, automatically pruning connected edges.
Time Complexity: O(V + E)
@spec filter_nodes_indexed(Yog.Graph.t(), (Yog.node_id(), term() -> boolean())) :: Yog.Graph.t()
Filters nodes by a predicate that receives (node_id, node_data).
Time Complexity: O(V + E)
@spec map_edges(Yog.Graph.t(), (term() -> term())) :: Yog.Graph.t()
Transforms edge weights using a function, preserving graph structure.
Time Complexity: O(E)
@spec map_edges_async(Yog.Graph.t(), (term() -> term()), keyword()) :: Yog.Graph.t()
Transforms edge weights using a function in parallel.
Time Complexity: O(E/cores)
@spec map_edges_indexed(Yog.Graph.t(), (Yog.node_id(), Yog.node_id(), term() -> term())) :: Yog.Graph.t()
Transforms edge weights using a function that receives (src, dst, weight).
Time Complexity: O(E)
@spec map_nodes(Yog.Graph.t(), (term() -> term())) :: Yog.Graph.t()
Transforms node data using a function, preserving graph structure.
Time Complexity: O(V)
Errors
- Raises
ArgumentErroriffunis not an arity-1 function.
@spec map_nodes_async(Yog.Graph.t(), (term() -> term()), keyword()) :: Yog.Graph.t()
Transforms node data using a function in parallel.
Time Complexity: O(V/cores)
Options
:max_concurrency- Maximum concurrent tasks (default:System.schedulers_online()):timeout- Task timeout in milliseconds (default: 5000):ordered- Preserve order (default: false)
@spec map_nodes_indexed(Yog.Graph.t(), (Yog.node_id(), term() -> term())) :: Yog.Graph.t()
Transforms node data using a function that also takes the node ID.
Time Complexity: O(V)
Errors
- Raises
ArgumentErroriffunis not an arity-2 function.
@spec merge(Yog.Graph.t(), Yog.Graph.t()) :: Yog.Graph.t()
Combines two graphs, with the second graph's data taking precedence on conflicts.
Time Complexity: O(V + E)
@spec normalize_node_ids(Yog.Graph.t()) :: Yog.Graph.t()
Normalizes all node IDs to a continuous range of integers 0..n-1.
Time Complexity: O(V log V + E)
@spec quotient_graph( Yog.Graph.t(), %{required(Yog.node_id()) => Yog.node_id()}, (term(), term() -> term()), (term(), term() -> term()) ) :: Yog.Graph.t()
Contracts nodes according to a partition map, producing a quotient graph.
Time Complexity: O(V + E)
@spec relabel_nodes(Yog.Graph.t(), (Yog.node_id() -> Yog.node_id())) :: Yog.Graph.t()
Relabels all node IDs in the graph using a mapping function.
Time Complexity: O(V + E)
Errors
- Raises
ArgumentErroriffunis not an arity-1 function.
@spec remove_self_loops(Yog.Graph.t()) :: Yog.Graph.t()
Removes all self-loops (edges from a node to itself) from the graph.
Time Complexity: O(E)
@spec subgraph(Yog.Graph.t(), [Yog.node_id()]) :: Yog.Graph.t()
Extracts a subgraph containing only the specified nodes and their connecting edges.
Time Complexity: O(V + E)
@spec to_directed(Yog.Graph.t()) :: Yog.Graph.t()
Converts an undirected graph to a directed graph.
Since Yog internally stores undirected edges as bidirectional directed edges,
this is essentially free — it just changes the kind flag.
If the graph is already directed, it is returned unchanged.
Time Complexity: O(1)
@spec to_undirected(Yog.Graph.t(), (term(), term() -> term())) :: Yog.Graph.t()
Converts a directed graph to an undirected graph using resolve for weight conflicts.
If the graph is already undirected, it is returned unchanged.
Time Complexity: O(E)
Errors
- Raises
ArgumentErrorifresolveis not an arity-2 function.
@spec transitive_closure(Yog.Graph.t()) :: Yog.Graph.t()
Computes the transitive closure of the graph.
Time Complexity: O(V × (V + E))
@spec transitive_reduction(Yog.Graph.t()) :: {:ok, Yog.Graph.t()} | {:error, :contains_cycle}
Computes the transitive reduction of a DAG.
Time Complexity: O(V × (V + E))
@spec transpose(Yog.Graph.t()) :: Yog.Graph.t()
Reverses the direction of every edge in the graph (graph transpose).
Due to the dual-map representation (storing both out_edges and in_edges), this is an O(1) operation - just a pointer swap! This is dramatically faster than most graph libraries where transpose is O(E).
Time Complexity: O(1)
Property: transpose(transpose(G)) = G
Example
iex> {:ok, graph} =
...> Yog.directed()
...> |> Yog.add_node(1, "A")
...> |> Yog.add_node(2, "B")
...> |> Yog.add_node(3, "C")
...> |> Yog.add_edges([{1, 2, 10}, {2, 3, 20}])
iex> reversed = Yog.Transform.transpose(graph)
iex> Yog.successors(reversed, 2)
[{1, 10}]
@spec update_edge(Yog.Graph.t(), Yog.node_id(), Yog.node_id(), term(), (term() -> term())) :: Yog.Graph.t()
Updates a specific edge's weight using an updater function.
Time Complexity: O(1)
@spec update_node(Yog.Graph.t(), Yog.node_id(), term(), (term() -> term())) :: Yog.Graph.t()
Updates a specific node's data using an updater function.
Time Complexity: O(1)